From 179bb85c4fea3b563e58337a453f1ee582edf26c Mon Sep 17 00:00:00 2001 From: DigiJ Date: Wed, 11 Mar 2026 19:11:25 -0700 Subject: [PATCH] Initial project scaffold for Setec Partition Wizard C++17/Qt6 application for disk recovery, repair, flashing, formatting, and USB security key creation on Windows. Includes CMake build system, tabbed UI shell with 6 main tabs, core type system with Result monadic error handling, admin elevation, and dark Catppuccin theme. --- .clang-format | 11 +++ .gitignore | 11 +++ CMakeLists.txt | 28 ++++++ CMakePresets.json | 54 ++++++++++ cmake/CompilerWarnings.cmake | 13 +++ cmake/FindWMI.cmake | 9 ++ cmake/QtDeployHelper.cmake | 16 +++ cmake/Version.cmake | 12 +++ resources/setec.manifest | 29 ++++++ resources/setec.rc | 37 +++++++ resources/styles/default.qss | 165 +++++++++++++++++++++++++++++++ src/CMakeLists.txt | 3 + src/app/CMakeLists.txt | 26 +++++ src/app/SingleInstance.cpp | 36 +++++++ src/app/SingleInstance.h | 32 ++++++ src/app/main.cpp | 124 +++++++++++++++++++++++ src/core/CMakeLists.txt | 51 ++++++++++ src/core/common/Constants.h | 71 +++++++++++++ src/core/common/Error.h | 114 +++++++++++++++++++++ src/core/common/Logging.cpp | 97 ++++++++++++++++++ src/core/common/Logging.h | 47 +++++++++ src/core/common/Result.h | 121 +++++++++++++++++++++++ src/core/common/Types.h | 140 ++++++++++++++++++++++++++ src/core/common/Version.h | 28 ++++++ src/ui/CMakeLists.txt | 30 ++++++ src/ui/MainWindow.cpp | 149 ++++++++++++++++++++++++++++ src/ui/MainWindow.h | 51 ++++++++++ src/ui/tabs/DiagnosticsTab.cpp | 90 +++++++++++++++++ src/ui/tabs/DiagnosticsTab.h | 20 ++++ src/ui/tabs/DiskPartitionTab.cpp | 107 ++++++++++++++++++++ src/ui/tabs/DiskPartitionTab.h | 39 ++++++++ src/ui/tabs/ImagingTab.cpp | 96 ++++++++++++++++++ src/ui/tabs/ImagingTab.h | 20 ++++ src/ui/tabs/MaintenanceTab.cpp | 106 ++++++++++++++++++++ src/ui/tabs/MaintenanceTab.h | 20 ++++ src/ui/tabs/RecoveryTab.cpp | 91 +++++++++++++++++ src/ui/tabs/RecoveryTab.h | 20 ++++ src/ui/tabs/SecurityTab.cpp | 154 +++++++++++++++++++++++++++++ src/ui/tabs/SecurityTab.h | 20 ++++ tests/CMakeLists.txt | 16 +++ tests/test_result.cpp | 72 ++++++++++++++ third_party/CMakeLists.txt | 23 +++++ 42 files changed, 2399 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json create mode 100644 cmake/CompilerWarnings.cmake create mode 100644 cmake/FindWMI.cmake create mode 100644 cmake/QtDeployHelper.cmake create mode 100644 cmake/Version.cmake create mode 100644 resources/setec.manifest create mode 100644 resources/setec.rc create mode 100644 resources/styles/default.qss create mode 100644 src/CMakeLists.txt create mode 100644 src/app/CMakeLists.txt create mode 100644 src/app/SingleInstance.cpp create mode 100644 src/app/SingleInstance.h create mode 100644 src/app/main.cpp create mode 100644 src/core/CMakeLists.txt create mode 100644 src/core/common/Constants.h create mode 100644 src/core/common/Error.h create mode 100644 src/core/common/Logging.cpp create mode 100644 src/core/common/Logging.h create mode 100644 src/core/common/Result.h create mode 100644 src/core/common/Types.h create mode 100644 src/core/common/Version.h create mode 100644 src/ui/CMakeLists.txt create mode 100644 src/ui/MainWindow.cpp create mode 100644 src/ui/MainWindow.h create mode 100644 src/ui/tabs/DiagnosticsTab.cpp create mode 100644 src/ui/tabs/DiagnosticsTab.h create mode 100644 src/ui/tabs/DiskPartitionTab.cpp create mode 100644 src/ui/tabs/DiskPartitionTab.h create mode 100644 src/ui/tabs/ImagingTab.cpp create mode 100644 src/ui/tabs/ImagingTab.h create mode 100644 src/ui/tabs/MaintenanceTab.cpp create mode 100644 src/ui/tabs/MaintenanceTab.h create mode 100644 src/ui/tabs/RecoveryTab.cpp create mode 100644 src/ui/tabs/RecoveryTab.h create mode 100644 src/ui/tabs/SecurityTab.cpp create mode 100644 src/ui/tabs/SecurityTab.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/test_result.cpp create mode 100644 third_party/CMakeLists.txt diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d14ddcf --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +BasedOnStyle: Microsoft +IndentWidth: 4 +ColumnLimit: 120 +PointerAlignment: Left +ReferenceAlignment: Left +BreakBeforeBraces: Allman +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +SortIncludes: CaseInsensitive +IncludeBlocks: Regroup diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c10a157 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +build/ +.vs/ +.vscode/ +*.user +*.suo +*.sdf +*.opensdf +*.db +*.opendb +CMakeUserPresets.json +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..eae2fb9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.25) +project(SetecPartitionWizard VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +# Find Qt6 +find_package(Qt6 REQUIRED COMPONENTS Widgets Core) + +# CMake helpers +include(cmake/CompilerWarnings.cmake) +include(cmake/Version.cmake) + +# Third-party dependencies +add_subdirectory(third_party) + +# Main source tree +add_subdirectory(src) + +# Tests +option(SPW_BUILD_TESTS "Build tests" ON) +if(SPW_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..f3f3ba5 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,54 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "default", + "displayName": "Default (Debug)", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "release", + "displayName": "Release", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "relwithdebinfo", + "displayName": "Release with Debug Info", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + } + ], + "buildPresets": [ + { + "name": "default", + "configurePreset": "default" + }, + { + "name": "release", + "configurePreset": "release" + } + ], + "testPresets": [ + { + "name": "default", + "configurePreset": "default", + "output": { "outputOnFailure": true } + }, + { + "name": "release", + "configurePreset": "release", + "output": { "outputOnFailure": true } + } + ] +} diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 0000000..ae35a0e --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,13 @@ +# Compiler warning settings for MSVC and GCC/Clang +if(MSVC) + add_compile_options(/W4 /permissive- /utf-8) + # Treat warnings as errors in CI builds + if(DEFINED ENV{CI}) + add_compile_options(/WX) + endif() +else() + add_compile_options(-Wall -Wextra -Wpedantic) + if(DEFINED ENV{CI}) + add_compile_options(-Werror) + endif() +endif() diff --git a/cmake/FindWMI.cmake b/cmake/FindWMI.cmake new file mode 100644 index 0000000..5bb8c61 --- /dev/null +++ b/cmake/FindWMI.cmake @@ -0,0 +1,9 @@ +# FindWMI.cmake - Locate WMI libraries on Windows +# Sets WMI_FOUND, WMI_LIBRARIES + +if(WIN32) + set(WMI_LIBRARIES wbemuuid ole32 oleaut32 setupapi) + set(WMI_FOUND TRUE) +else() + set(WMI_FOUND FALSE) +endif() diff --git a/cmake/QtDeployHelper.cmake b/cmake/QtDeployHelper.cmake new file mode 100644 index 0000000..29a8ee6 --- /dev/null +++ b/cmake/QtDeployHelper.cmake @@ -0,0 +1,16 @@ +# QtDeployHelper.cmake - Post-build windeployqt integration +function(spw_deploy_qt TARGET) + if(WIN32) + find_program(WINDEPLOYQT windeployqt HINTS "${Qt6_DIR}/../../../bin") + if(WINDEPLOYQT) + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND "${WINDEPLOYQT}" + --no-translations + --no-system-d3d-compiler + --no-opengl-sw + "$" + COMMENT "Running windeployqt on ${TARGET}..." + ) + endif() + endif() +endfunction() diff --git a/cmake/Version.cmake b/cmake/Version.cmake new file mode 100644 index 0000000..5f94945 --- /dev/null +++ b/cmake/Version.cmake @@ -0,0 +1,12 @@ +# Version.cmake - Parse version from project and expose as definitions +set(SPW_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(SPW_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(SPW_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(SPW_VERSION_STRING "${PROJECT_VERSION}") + +add_compile_definitions( + SPW_VERSION_MAJOR=${SPW_VERSION_MAJOR} + SPW_VERSION_MINOR=${SPW_VERSION_MINOR} + SPW_VERSION_PATCH=${SPW_VERSION_PATCH} + SPW_VERSION_STRING="${SPW_VERSION_STRING}" +) diff --git a/resources/setec.manifest b/resources/setec.manifest new file mode 100644 index 0000000..e7cc67b --- /dev/null +++ b/resources/setec.manifest @@ -0,0 +1,29 @@ + + + + Setec Partition Wizard + + + + + + + + + + + + + + + + true/pm + PerMonitorV2 + + + diff --git a/resources/setec.rc b/resources/setec.rc new file mode 100644 index 0000000..aa235b9 --- /dev/null +++ b/resources/setec.rc @@ -0,0 +1,37 @@ +#include + +// Application icon +IDI_APPICON ICON "icons\\app.ico" + +// UAC manifest +1 RT_MANIFEST "setec.manifest" + +// Version info +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0x0L + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Setec" + VALUE "FileDescription", "Setec Partition Wizard" + VALUE "FileVersion", "1.0.0.0" + VALUE "InternalName", "SetecPartitionWizard" + VALUE "OriginalFilename", "SetecPartitionWizard.exe" + VALUE "ProductName", "Setec Partition Wizard" + VALUE "ProductVersion", "1.0.0.0" + VALUE "LegalCopyright", "Copyright (C) 2026 Setec" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END +END diff --git a/resources/styles/default.qss b/resources/styles/default.qss new file mode 100644 index 0000000..6d04d13 --- /dev/null +++ b/resources/styles/default.qss @@ -0,0 +1,165 @@ +/* Setec Partition Wizard - Default Stylesheet */ + +QMainWindow { + background-color: #1e1e2e; + color: #cdd6f4; +} + +QTabWidget::pane { + border: 1px solid #45475a; + background-color: #1e1e2e; +} + +QTabBar::tab { + background-color: #313244; + color: #bac2de; + padding: 8px 20px; + margin-right: 2px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + min-width: 120px; +} + +QTabBar::tab:selected { + background-color: #45475a; + color: #cdd6f4; + border-bottom: 2px solid #89b4fa; +} + +QTabBar::tab:hover:!selected { + background-color: #3b3d52; +} + +QTreeView, QTableView, QListView { + background-color: #181825; + color: #cdd6f4; + border: 1px solid #45475a; + selection-background-color: #45475a; + selection-color: #cdd6f4; + alternate-background-color: #1e1e2e; +} + +QTreeView::item:hover, QTableView::item:hover { + background-color: #313244; +} + +QHeaderView::section { + background-color: #313244; + color: #bac2de; + padding: 4px 8px; + border: 1px solid #45475a; +} + +QPushButton { + background-color: #45475a; + color: #cdd6f4; + border: 1px solid #585b70; + border-radius: 4px; + padding: 6px 16px; + min-width: 80px; +} + +QPushButton:hover { + background-color: #585b70; +} + +QPushButton:pressed { + background-color: #313244; +} + +QPushButton#applyButton { + background-color: #a6e3a1; + color: #1e1e2e; + font-weight: bold; +} + +QPushButton#applyButton:hover { + background-color: #94e2d5; +} + +QPushButton#cancelButton { + background-color: #f38ba8; + color: #1e1e2e; +} + +QProgressBar { + background-color: #313244; + border: 1px solid #45475a; + border-radius: 4px; + text-align: center; + color: #cdd6f4; +} + +QProgressBar::chunk { + background-color: #89b4fa; + border-radius: 3px; +} + +QMenuBar { + background-color: #181825; + color: #cdd6f4; +} + +QMenuBar::item:selected { + background-color: #45475a; +} + +QMenu { + background-color: #1e1e2e; + color: #cdd6f4; + border: 1px solid #45475a; +} + +QMenu::item:selected { + background-color: #45475a; +} + +QToolBar { + background-color: #181825; + border-bottom: 1px solid #45475a; + spacing: 4px; + padding: 2px; +} + +QStatusBar { + background-color: #181825; + color: #a6adc8; + border-top: 1px solid #45475a; +} + +QSplitter::handle { + background-color: #45475a; +} + +QGroupBox { + border: 1px solid #45475a; + border-radius: 4px; + margin-top: 8px; + padding-top: 8px; + color: #cdd6f4; +} + +QGroupBox::title { + subcontrol-origin: margin; + left: 10px; + padding: 0 4px; +} + +QLineEdit, QSpinBox, QComboBox { + background-color: #313244; + color: #cdd6f4; + border: 1px solid #45475a; + border-radius: 4px; + padding: 4px 8px; +} + +QLineEdit:focus, QSpinBox:focus, QComboBox:focus { + border-color: #89b4fa; +} + +QToolTip { + background-color: #313244; + color: #cdd6f4; + border: 1px solid #45475a; + padding: 4px; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..95eca72 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(core) +add_subdirectory(ui) +add_subdirectory(app) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt new file mode 100644 index 0000000..c97e4e1 --- /dev/null +++ b/src/app/CMakeLists.txt @@ -0,0 +1,26 @@ +set(APP_SOURCES + main.cpp + SingleInstance.cpp +) + +set(APP_HEADERS + SingleInstance.h +) + +add_executable(SetecPartitionWizard WIN32 + ${APP_SOURCES} + ${APP_HEADERS} + ${CMAKE_SOURCE_DIR}/resources/setec.rc +) + +target_include_directories(SetecPartitionWizard PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(SetecPartitionWizard PRIVATE + spw_ui +) + +# Deploy Qt DLLs on install +include(${CMAKE_SOURCE_DIR}/cmake/QtDeployHelper.cmake) +spw_deploy_qt(SetecPartitionWizard) diff --git a/src/app/SingleInstance.cpp b/src/app/SingleInstance.cpp new file mode 100644 index 0000000..2da57b6 --- /dev/null +++ b/src/app/SingleInstance.cpp @@ -0,0 +1,36 @@ +#include "SingleInstance.h" + +namespace spw +{ + +SingleInstance::SingleInstance(const QString& mutexName) +{ +#ifdef _WIN32 + m_mutex = CreateMutexW(nullptr, FALSE, mutexName.toStdWString().c_str()); + if (m_mutex != nullptr) + { + m_isUnique = (GetLastError() != ERROR_ALREADY_EXISTS); + } +#else + Q_UNUSED(mutexName); + m_isUnique = true; // Non-Windows: always unique (placeholder) +#endif +} + +SingleInstance::~SingleInstance() +{ +#ifdef _WIN32 + if (m_mutex != nullptr) + { + ReleaseMutex(m_mutex); + CloseHandle(m_mutex); + } +#endif +} + +bool SingleInstance::isUnique() const +{ + return m_isUnique; +} + +} // namespace spw diff --git a/src/app/SingleInstance.h b/src/app/SingleInstance.h new file mode 100644 index 0000000..d36f071 --- /dev/null +++ b/src/app/SingleInstance.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#ifdef _WIN32 +#include +#endif + +namespace spw +{ + +// Prevents multiple instances of the application using a named kernel mutex. +class SingleInstance +{ +public: + explicit SingleInstance(const QString& mutexName); + ~SingleInstance(); + + SingleInstance(const SingleInstance&) = delete; + SingleInstance& operator=(const SingleInstance&) = delete; + + // Returns true if this is the only running instance. + bool isUnique() const; + +private: +#ifdef _WIN32 + HANDLE m_mutex = nullptr; +#endif + bool m_isUnique = false; +}; + +} // namespace spw diff --git a/src/app/main.cpp b/src/app/main.cpp new file mode 100644 index 0000000..71ca60c --- /dev/null +++ b/src/app/main.cpp @@ -0,0 +1,124 @@ +#include "SingleInstance.h" +#include "core/common/Logging.h" +#include "core/common/Version.h" +#include "ui/MainWindow.h" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +static bool isRunningAsAdmin() +{ +#ifdef _WIN32 + BOOL isAdmin = FALSE; + PSID adminGroup = nullptr; + SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY; + + if (AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroup)) + { + CheckTokenMembership(nullptr, adminGroup, &isAdmin); + FreeSid(adminGroup); + } + return isAdmin != FALSE; +#else + return geteuid() == 0; +#endif +} + +static bool relaunchAsAdmin() +{ +#ifdef _WIN32 + wchar_t path[MAX_PATH]; + GetModuleFileNameW(nullptr, path, MAX_PATH); + + SHELLEXECUTEINFOW sei = {}; + sei.cbSize = sizeof(sei); + sei.lpVerb = L"runas"; + sei.lpFile = path; + sei.nShow = SW_SHOWNORMAL; + + return ShellExecuteExW(&sei) != FALSE; +#else + return false; +#endif +} + +static void applyStylesheet(QApplication& app) +{ + QFile styleFile(":/styles/default.qss"); + if (styleFile.open(QFile::ReadOnly | QFile::Text)) + { + app.setStyleSheet(styleFile.readAll()); + styleFile.close(); + } +} + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + app.setApplicationName(spw::AppName); + app.setApplicationVersion(spw::VersionString); + app.setOrganizationName("Setec"); + + // Single instance check + spw::SingleInstance instance(QStringLiteral("SetecPartitionWizard_SingleInstance_Mutex")); + if (!instance.isUnique()) + { + QMessageBox::warning(nullptr, spw::AppName, + "Setec Partition Wizard is already running."); + return 1; + } + + // Initialize logging + auto logDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir().mkpath(logDir); + auto logPath = logDir + "/setec_partition_wizard.log"; + spw::log::init(logPath.toStdString()); + + spw::log::info(QString("Starting %1 v%2").arg(spw::AppName, spw::VersionString)); + + // Admin privilege check (defense-in-depth; manifest should handle UAC) + if (!isRunningAsAdmin()) + { + spw::log::warn("Not running as administrator — attempting elevation"); + auto answer = QMessageBox::question( + nullptr, spw::AppName, + "Setec Partition Wizard requires administrator privileges for disk operations.\n\n" + "Restart as administrator?", + QMessageBox::Yes | QMessageBox::No); + + if (answer == QMessageBox::Yes) + { + if (relaunchAsAdmin()) + { + return 0; // Exit this instance; elevated one will start + } + QMessageBox::critical(nullptr, spw::AppName, "Failed to elevate privileges."); + } + // Continue anyway — some read-only features may still work + spw::log::warn("Continuing without admin privileges — write operations will fail"); + } + + // Apply stylesheet + applyStylesheet(app); + + // Show main window + spw::MainWindow mainWindow; + mainWindow.show(); + + spw::log::info("Application ready"); + + int result = app.exec(); + + spw::log::info("Application shutting down"); + spw::log::shutdown(); + + return result; +} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 0000000..8960df9 --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,51 @@ +set(CORE_SOURCES + # Common + common/Logging.cpp + + # Disk (stubs — will be implemented in Phase 2) + # disk/RawDiskHandle.cpp + # disk/VolumeHandle.cpp + # disk/DiskEnumerator.cpp + # disk/PartitionTable.cpp + # disk/DiskGeometry.cpp + # disk/VolumeManager.cpp + + # Filesystem (stubs — will be implemented in Phase 4) + # filesystem/FilesystemFactory.cpp + # filesystem/FilesystemDetector.cpp + # filesystem/NtfsDriver.cpp + # filesystem/Fat32Driver.cpp + + # Operations (stubs — will be implemented in Phase 3) + # operations/OperationQueue.cpp + # operations/OperationRunner.cpp +) + +set(CORE_HEADERS + common/Types.h + common/Result.h + common/Error.h + common/Constants.h + common/Logging.h + common/Version.h +) + +add_library(spw_core STATIC ${CORE_SOURCES} ${CORE_HEADERS}) + +target_include_directories(spw_core PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +target_link_libraries(spw_core PUBLIC + Qt6::Core +) + +# Windows system libraries +if(WIN32) + target_link_libraries(spw_core PUBLIC + setupapi + wbemuuid + ole32 + oleaut32 + ) +endif() diff --git a/src/core/common/Constants.h b/src/core/common/Constants.h new file mode 100644 index 0000000..3642515 --- /dev/null +++ b/src/core/common/Constants.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +namespace spw +{ + +// Sector sizes +constexpr uint32_t SECTOR_SIZE_512 = 512; +constexpr uint32_t SECTOR_SIZE_4K = 4096; +constexpr uint32_t DEFAULT_SECTOR_SIZE = SECTOR_SIZE_512; + +// Partition alignment (1 MiB default — optimal for SSDs and 4K drives) +constexpr uint64_t DEFAULT_ALIGNMENT_BYTES = 1048576; // 1 MiB +constexpr uint64_t DEFAULT_ALIGNMENT_SECTORS_512 = DEFAULT_ALIGNMENT_BYTES / SECTOR_SIZE_512; + +// MBR constants +constexpr uint16_t MBR_SIGNATURE = 0xAA55; +constexpr uint32_t MBR_SIZE = 512; +constexpr int MBR_MAX_PRIMARY_PARTITIONS = 4; +constexpr uint8_t MBR_PARTITION_ENTRY_OFFSET = 446; +constexpr uint8_t MBR_PARTITION_ENTRY_SIZE = 16; + +// GPT constants +constexpr uint64_t GPT_HEADER_SIGNATURE = 0x5452415020494645ULL; // "EFI PART" +constexpr uint32_t GPT_HEADER_SIZE = 92; +constexpr uint32_t GPT_ENTRY_SIZE = 128; +constexpr int GPT_MAX_PARTITIONS = 128; +constexpr uint64_t GPT_HEADER_LBA = 1; + +// Apple Partition Map constants +constexpr uint16_t APM_SIGNATURE = 0x504D; // "PM" +constexpr uint16_t APM_DDM_SIGNATURE = 0x4552; // "ER" + +// Filesystem magic bytes +constexpr uint32_t NTFS_MAGIC_OFFSET = 3; // "NTFS " at byte 3 +constexpr uint16_t EXT_SUPER_MAGIC = 0xEF53; // ext2/3/4 superblock magic at offset 1080 +constexpr uint32_t BTRFS_MAGIC_OFFSET = 0x10040; // "_BHRfS_M" at 64K + 64 +constexpr uint32_t XFS_MAGIC = 0x58465342; // "XFSB" +constexpr uint16_t HFS_PLUS_MAGIC = 0x482B; // "H+" +constexpr uint16_t HFSX_MAGIC = 0x4858; // "HX" +constexpr uint32_t APFS_MAGIC = 0x4253584E; // "NXSB" (little-endian) +constexpr uint16_t FAT_SIGNATURE = 0xAA55; +constexpr uint32_t REFS_MAGIC = 0x53465265; // "ReFS" +constexpr uint16_t HPFS_SUPER_MAGIC = 0xF995E849; +constexpr uint16_t MINIX_SUPER_MAGIC = 0x137F; +constexpr uint16_t MINIX2_SUPER_MAGIC = 0x2468; +constexpr uint32_t UFS_MAGIC = 0x00011954; +constexpr uint32_t UDF_MAGIC_BEA = 0x00424541; // BEA01 +constexpr uint32_t ISO9660_MAGIC = 0x30304443; // "CD001" identifier +constexpr uint32_t BEOS_SUPER_MAGIC = 0x42465331; // BFS1 +constexpr uint16_t QNX4_SUPER_MAGIC = 0x002F; +constexpr uint32_t REISERFS_MAGIC_OFFSET = 0x10034; +constexpr uint32_t JFS_MAGIC = 0x3153464A; // "JFS1" + +// Imaging +constexpr uint32_t IMAGE_CHUNK_SIZE = 4 * 1024 * 1024; // 4 MiB chunks for compressed images +constexpr char SPW_IMAGE_MAGIC[] = "SPWIMG01"; + +// Benchmark +constexpr uint32_t BENCH_BLOCK_SEQ = 1024 * 1024; // 1 MiB sequential +constexpr uint32_t BENCH_BLOCK_RND = 4096; // 4 KiB random +constexpr int BENCH_DEFAULT_DURATION_SEC = 5; + +// Secure erase +constexpr int ERASE_PASS_ZERO = 1; +constexpr int ERASE_PASS_DOD_3 = 3; // DoD 5220.22-M (3-pass) +constexpr int ERASE_PASS_DOD_7 = 7; // DoD 5220.22-M ECE (7-pass) +constexpr int ERASE_PASS_GUTMANN = 35; // Gutmann method + +} // namespace spw diff --git a/src/core/common/Error.h b/src/core/common/Error.h new file mode 100644 index 0000000..1b42569 --- /dev/null +++ b/src/core/common/Error.h @@ -0,0 +1,114 @@ +#pragma once + +#include +#include + +namespace spw +{ + +enum class ErrorCode +{ + Success = 0, + + // General + Unknown, + InvalidArgument, + NotImplemented, + OperationCanceled, + InsufficientPrivileges, + + // Disk I/O + DiskNotFound, + DiskAccessDenied, + DiskReadError, + DiskWriteError, + DiskLockFailed, + DiskDismountFailed, + DiskBusy, + SectorOutOfRange, + + // Partition + PartitionNotFound, + PartitionTableCorrupt, + PartitionTableFull, + PartitionOverlap, + PartitionTooSmall, + PartitionTooLarge, + InvalidPartitionType, + AlignmentError, + + // Filesystem + FilesystemNotSupported, + FilesystemCorrupt, + FormatFailed, + ResizeFailed, + FilesystemTooSmallToShrink, + FilesystemCheckFailed, + + // Recovery + NoPartitionsFound, + NoFilesRecovered, + RecoveryScanFailed, + + // Imaging + ImageCorrupt, + ImageChecksumMismatch, + ImageReadError, + ImageWriteError, + IsoParseError, + InsufficientDiskSpace, + + // Security + Fido2DeviceNotFound, + Fido2PinRequired, + Fido2AuthFailed, + EncryptionFailed, + DecryptionFailed, + KeyGenerationFailed, + + // SMART / Diagnostics + SmartNotSupported, + SmartReadFailed, + BenchmarkFailed, + + // Boot + BootRepairFailed, + BcdNotFound, + MbrRepairFailed, + + // System + OutOfMemory, + FileNotFound, + FileCreateFailed, + WmiQueryFailed, +}; + +struct ErrorInfo +{ + ErrorCode code = ErrorCode::Success; + std::string message; + uint32_t win32Error = 0; // GetLastError() value + long hresult = 0; // COM HRESULT if applicable + + bool isOk() const { return code == ErrorCode::Success; } + bool isError() const { return code != ErrorCode::Success; } + + static ErrorInfo ok() { return {}; } + + static ErrorInfo fromCode(ErrorCode code, const std::string& msg = "") + { + return {code, msg, 0, 0}; + } + + static ErrorInfo fromWin32(ErrorCode code, uint32_t win32Err, const std::string& msg = "") + { + return {code, msg, win32Err, 0}; + } + + static ErrorInfo fromHResult(ErrorCode code, long hr, const std::string& msg = "") + { + return {code, msg, 0, hr}; + } +}; + +} // namespace spw diff --git a/src/core/common/Logging.cpp b/src/core/common/Logging.cpp new file mode 100644 index 0000000..349fd5e --- /dev/null +++ b/src/core/common/Logging.cpp @@ -0,0 +1,97 @@ +#include "Logging.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace spw +{ +namespace log +{ + +static Level s_minLevel = Level::Info; +static std::ofstream s_logFile; +static QMutex s_mutex; + +static const char* levelToString(Level level) +{ + switch (level) + { + case Level::Trace: + return "TRACE"; + case Level::Debug: + return "DEBUG"; + case Level::Info: + return "INFO "; + case Level::Warn: + return "WARN "; + case Level::Error: + return "ERROR"; + case Level::Critical: + return "CRIT "; + } + return "?????"; +} + +static void logImpl(Level level, const char* msg) +{ + if (level < s_minLevel) + return; + + QMutexLocker lock(&s_mutex); + + auto timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"); + auto line = QString("[%1] [%2] %3").arg(timestamp, levelToString(level), msg); + + // Write to debug output + qDebug().noquote() << line; + + // Write to file if open + if (s_logFile.is_open()) + { + s_logFile << line.toStdString() << "\n"; + s_logFile.flush(); + } +} + +void init(const std::string& logFilePath) +{ + if (!logFilePath.empty()) + { + s_logFile.open(logFilePath, std::ios::app); + } + info("Setec Partition Wizard logging initialized"); +} + +void shutdown() +{ + info("Logging shutdown"); + if (s_logFile.is_open()) + { + s_logFile.close(); + } +} + +void setLevel(Level level) { s_minLevel = level; } + +void trace(const char* msg) { logImpl(Level::Trace, msg); } +void debug(const char* msg) { logImpl(Level::Debug, msg); } +void info(const char* msg) { logImpl(Level::Info, msg); } +void warn(const char* msg) { logImpl(Level::Warn, msg); } +void error(const char* msg) { logImpl(Level::Error, msg); } +void critical(const char* msg) { logImpl(Level::Critical, msg); } + +void trace(const QString& msg) { logImpl(Level::Trace, msg.toUtf8().constData()); } +void debug(const QString& msg) { logImpl(Level::Debug, msg.toUtf8().constData()); } +void info(const QString& msg) { logImpl(Level::Info, msg.toUtf8().constData()); } +void warn(const QString& msg) { logImpl(Level::Warn, msg.toUtf8().constData()); } +void error(const QString& msg) { logImpl(Level::Error, msg.toUtf8().constData()); } +void critical(const QString& msg) { logImpl(Level::Critical, msg.toUtf8().constData()); } + +} // namespace log +} // namespace spw diff --git a/src/core/common/Logging.h b/src/core/common/Logging.h new file mode 100644 index 0000000..a1a1f93 --- /dev/null +++ b/src/core/common/Logging.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace spw +{ +namespace log +{ + +enum class Level +{ + Trace, + Debug, + Info, + Warn, + Error, + Critical, +}; + +// Initialize logging (call once from main) +void init(const std::string& logFilePath = ""); + +// Shutdown logging +void shutdown(); + +// Set minimum log level +void setLevel(Level level); + +// Log functions +void trace(const char* msg); +void debug(const char* msg); +void info(const char* msg); +void warn(const char* msg); +void error(const char* msg); +void critical(const char* msg); + +// QString overloads +void trace(const QString& msg); +void debug(const QString& msg); +void info(const QString& msg); +void warn(const QString& msg); +void error(const QString& msg); +void critical(const QString& msg); + +} // namespace log +} // namespace spw diff --git a/src/core/common/Result.h b/src/core/common/Result.h new file mode 100644 index 0000000..21232fc --- /dev/null +++ b/src/core/common/Result.h @@ -0,0 +1,121 @@ +#pragma once + +#include "Error.h" + +#include +#include +#include + +namespace spw +{ + +// Monadic result type for disk operations. +// Forces callers to handle errors — no exceptions can skip past a partial write. +template +class Result +{ +public: + // Construct success + Result(const T& value) : m_data(value) {} + Result(T&& value) : m_data(std::move(value)) {} + + // Construct failure + Result(const ErrorInfo& error) : m_data(error) + { + assert(error.isError()); + } + + Result(ErrorInfo&& error) : m_data(std::move(error)) + { + assert(std::get(m_data).isError()); + } + + bool isOk() const { return std::holds_alternative(m_data); } + bool isError() const { return std::holds_alternative(m_data); } + + const T& value() const& + { + assert(isOk()); + return std::get(m_data); + } + + T& value() & + { + assert(isOk()); + return std::get(m_data); + } + + T&& value() && + { + assert(isOk()); + return std::get(std::move(m_data)); + } + + const ErrorInfo& error() const + { + assert(isError()); + return std::get(m_data); + } + + // Value or default + T valueOr(const T& defaultVal) const + { + if (isOk()) + return value(); + return defaultVal; + } + + // Monadic map + template + auto map(Func&& func) const -> Result()))> + { + if (isOk()) + return func(value()); + return error(); + } + + // Monadic flatMap / andThen + template + auto andThen(Func&& func) const -> decltype(func(std::declval())) + { + if (isOk()) + return func(value()); + return error(); + } + + explicit operator bool() const { return isOk(); } + +private: + std::variant m_data; +}; + +// Specialization for void results +template <> +class Result +{ +public: + Result() : m_error(std::nullopt) {} + + Result(const ErrorInfo& error) : m_error(error) + { + assert(error.isError()); + } + + bool isOk() const { return !m_error.has_value(); } + bool isError() const { return m_error.has_value(); } + + const ErrorInfo& error() const + { + assert(isError()); + return m_error.value(); + } + + explicit operator bool() const { return isOk(); } + + static Result ok() { return {}; } + +private: + std::optional m_error; +}; + +} // namespace spw diff --git a/src/core/common/Types.h b/src/core/common/Types.h new file mode 100644 index 0000000..726b7ee --- /dev/null +++ b/src/core/common/Types.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include + +namespace spw +{ + +// Sector-level addressing (LBA) +using SectorOffset = uint64_t; +using SectorCount = uint64_t; + +// Byte-level sizes +using ByteCount = uint64_t; + +// Identifiers +using DiskId = int; // Physical drive index (0, 1, 2, ...) +using PartitionId = int; // Partition index within a disk + +// GUID as 16-byte array +struct Guid +{ + uint8_t data[16] = {}; + + bool operator==(const Guid& other) const; + bool operator!=(const Guid& other) const; + bool isZero() const; + std::string toString() const; + static Guid fromString(const std::string& str); + static Guid generate(); +}; + +// Partition table type +enum class PartitionTableType +{ + Unknown, + MBR, + GPT, + APM, // Apple Partition Map +}; + +// Filesystem types — comprehensive list including legacy systems +enum class FilesystemType +{ + Unknown, + + // Modern Windows + NTFS, + FAT32, + FAT16, + FAT12, + ExFAT, + ReFS, + + // Linux + Ext2, + Ext3, + Ext4, + Btrfs, + XFS, + ZFS, + JFS, + ReiserFS, + Reiser4, + + // Apple + HFSPlus, + APFS, + HFS, // Classic Mac OS (legacy) + MFS, // Macintosh File System (1984) + + // Legacy / retro (1980s-1990s) + FAT8, // CP/M-86 era + HPFS, // OS/2 High Performance File System + UFS, // Unix File System (BSD) + FFS, // Berkeley Fast File System + Minix, // MINIX filesystem + Xiafs, // Linux early filesystem + ADFS, // Acorn Disc Filing System + AfFS, // Amiga Fast File System + OFS, // Amiga Old File System + BFS_BeOS, // BeOS File System + QNX4, // QNX4 filesystem + QNX6, // QNX6 filesystem + SysV, // System V filesystem + Coherent, // Coherent filesystem + Xenix, // Xenix filesystem + VxFS, // Veritas File System + UDF, // Universal Disk Format (optical media) + ISO9660, // CD-ROM filesystem + RomFS, // Read-only filesystem + CramFS, // Compressed ROM filesystem + SquashFS, // Compressed read-only filesystem + VFAT, // Virtual FAT (long filename extension) + UMSDOS, // Unix on MS-DOS filesystem + + // Network / special (read-only detection) + NFS, + SMB, + SWAP_LINUX, + SWAP_SOLARIS, + + // Raw / unformatted + Raw, + Unallocated, +}; + +// Disk interface types +enum class DiskInterfaceType +{ + Unknown, + SATA, + NVMe, + USB, + SCSI, + SAS, + IDE, + MMC, // SD cards, eMMC + Firewire, + Thunderbolt, + Virtual, // VHD, VHDX, etc. +}; + +// Media types +enum class MediaType +{ + Unknown, + HDD, + SSD, + NVMe, + USBFlash, + SDCard, + CompactFlash, + OpticalDrive, + FloppyDisk, + MemoryStick, + Virtual, +}; + +} // namespace spw diff --git a/src/core/common/Version.h b/src/core/common/Version.h new file mode 100644 index 0000000..cbecb01 --- /dev/null +++ b/src/core/common/Version.h @@ -0,0 +1,28 @@ +#pragma once + +#ifndef SPW_VERSION_MAJOR +#define SPW_VERSION_MAJOR 1 +#endif + +#ifndef SPW_VERSION_MINOR +#define SPW_VERSION_MINOR 0 +#endif + +#ifndef SPW_VERSION_PATCH +#define SPW_VERSION_PATCH 0 +#endif + +#ifndef SPW_VERSION_STRING +#define SPW_VERSION_STRING "1.0.0" +#endif + +namespace spw +{ + +constexpr int VersionMajor = SPW_VERSION_MAJOR; +constexpr int VersionMinor = SPW_VERSION_MINOR; +constexpr int VersionPatch = SPW_VERSION_PATCH; +constexpr const char* VersionString = SPW_VERSION_STRING; +constexpr const char* AppName = "Setec Partition Wizard"; + +} // namespace spw diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt new file mode 100644 index 0000000..69991e8 --- /dev/null +++ b/src/ui/CMakeLists.txt @@ -0,0 +1,30 @@ +set(UI_SOURCES + MainWindow.cpp + tabs/DiskPartitionTab.cpp + tabs/RecoveryTab.cpp + tabs/ImagingTab.cpp + tabs/DiagnosticsTab.cpp + tabs/SecurityTab.cpp + tabs/MaintenanceTab.cpp +) + +set(UI_HEADERS + MainWindow.h + tabs/DiskPartitionTab.h + tabs/RecoveryTab.h + tabs/ImagingTab.h + tabs/DiagnosticsTab.h + tabs/SecurityTab.h + tabs/MaintenanceTab.h +) + +add_library(spw_ui STATIC ${UI_SOURCES} ${UI_HEADERS}) + +target_include_directories(spw_ui PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +target_link_libraries(spw_ui PUBLIC + spw_core + Qt6::Widgets +) diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp new file mode 100644 index 0000000..ecc3a46 --- /dev/null +++ b/src/ui/MainWindow.cpp @@ -0,0 +1,149 @@ +#include "MainWindow.h" +#include "tabs/DiskPartitionTab.h" +#include "tabs/RecoveryTab.h" +#include "tabs/ImagingTab.h" +#include "tabs/DiagnosticsTab.h" +#include "tabs/SecurityTab.h" +#include "tabs/MaintenanceTab.h" +#include "core/common/Version.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace spw +{ + +MainWindow::MainWindow(QWidget* parent) + : QMainWindow(parent) +{ + setWindowTitle(QString("%1 v%2").arg(AppName, VersionString)); + resize(1280, 800); + setMinimumSize(1024, 600); + + setupMenuBar(); + setupToolBar(); + setupTabs(); + setupStatusBar(); +} + +MainWindow::~MainWindow() = default; + +void MainWindow::setupMenuBar() +{ + auto* fileMenu = menuBar()->addMenu(tr("&File")); + fileMenu->addAction(tr("&Refresh Disks"), this, &MainWindow::onRefreshDisks, QKeySequence::Refresh); + fileMenu->addSeparator(); + fileMenu->addAction(tr("E&xit"), qApp, &QApplication::quit, QKeySequence::Quit); + + auto* diskMenu = menuBar()->addMenu(tr("&Disk")); + diskMenu->addAction(tr("&Clone Disk...")); + diskMenu->addAction(tr("Create &Image...")); + diskMenu->addAction(tr("&Restore Image...")); + diskMenu->addAction(tr("&Flash ISO/IMG...")); + diskMenu->addSeparator(); + diskMenu->addAction(tr("&Secure Erase...")); + + auto* partitionMenu = menuBar()->addMenu(tr("&Partition")); + partitionMenu->addAction(tr("&Create...")); + partitionMenu->addAction(tr("&Delete")); + partitionMenu->addAction(tr("&Resize/Move...")); + partitionMenu->addAction(tr("&Format...")); + partitionMenu->addSeparator(); + partitionMenu->addAction(tr("&Merge...")); + partitionMenu->addAction(tr("&Split...")); + + auto* toolsMenu = menuBar()->addMenu(tr("&Tools")); + toolsMenu->addAction(tr("&S.M.A.R.T. Info...")); + toolsMenu->addAction(tr("&Benchmark...")); + toolsMenu->addAction(tr("S&urface Scan...")); + toolsMenu->addSeparator(); + toolsMenu->addAction(tr("&Boot Repair...")); + + auto* helpMenu = menuBar()->addMenu(tr("&Help")); + helpMenu->addAction(tr("&About..."), this, &MainWindow::onAbout); +} + +void MainWindow::setupToolBar() +{ + m_toolBar = addToolBar(tr("Main Toolbar")); + m_toolBar->setMovable(false); + m_toolBar->setIconSize(QSize(24, 24)); + + m_toolBar->addAction(tr("Refresh")); + m_toolBar->addSeparator(); + m_toolBar->addAction(tr("Create")); + m_toolBar->addAction(tr("Delete")); + m_toolBar->addAction(tr("Resize")); + m_toolBar->addAction(tr("Format")); + m_toolBar->addSeparator(); + m_toolBar->addAction(tr("Clone")); + m_toolBar->addAction(tr("Flash")); + m_toolBar->addSeparator(); + + // Apply button (prominent) + auto* applyAction = m_toolBar->addAction(tr("Apply")); + if (auto* widget = m_toolBar->widgetForAction(applyAction)) + { + widget->setObjectName("applyButton"); + } + + auto* cancelAction = m_toolBar->addAction(tr("Undo All")); + if (auto* widget = m_toolBar->widgetForAction(cancelAction)) + { + widget->setObjectName("cancelButton"); + } +} + +void MainWindow::setupTabs() +{ + m_tabWidget = new QTabWidget(this); + m_tabWidget->setTabPosition(QTabWidget::North); + m_tabWidget->setDocumentMode(true); + + m_diskPartitionTab = new DiskPartitionTab(this); + m_recoveryTab = new RecoveryTab(this); + m_imagingTab = new ImagingTab(this); + m_diagnosticsTab = new DiagnosticsTab(this); + m_securityTab = new SecurityTab(this); + m_maintenanceTab = new MaintenanceTab(this); + + m_tabWidget->addTab(m_diskPartitionTab, tr("Disks && Partitions")); + m_tabWidget->addTab(m_recoveryTab, tr("Recovery")); + m_tabWidget->addTab(m_imagingTab, tr("Imaging && Flashing")); + m_tabWidget->addTab(m_diagnosticsTab, tr("Diagnostics")); + m_tabWidget->addTab(m_securityTab, tr("Security Keys")); + m_tabWidget->addTab(m_maintenanceTab, tr("Maintenance")); + + setCentralWidget(m_tabWidget); +} + +void MainWindow::setupStatusBar() +{ + statusBar()->showMessage(tr("Ready — No pending operations")); +} + +void MainWindow::onAbout() +{ + QMessageBox::about(this, tr("About %1").arg(AppName), + tr("

%1

" + "

Version %2

" + "

A comprehensive disk recovery, repair, flashing, and formatting tool " + "for Windows.

" + "

Supports all major and legacy filesystems, partition table formats " + "(MBR, GPT, APM), USB security key creation, and media imaging.

" + "

Copyright © 2026 Setec

") + .arg(AppName, VersionString)); +} + +void MainWindow::onRefreshDisks() +{ + statusBar()->showMessage(tr("Refreshing disk list..."), 2000); + // TODO: Call DiskController::refresh() +} + +} // namespace spw diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h new file mode 100644 index 0000000..db1d825 --- /dev/null +++ b/src/ui/MainWindow.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +class QTabWidget; +class QMenuBar; +class QToolBar; +class QStatusBar; + +namespace spw +{ + +class DiskPartitionTab; +class RecoveryTab; +class ImagingTab; +class DiagnosticsTab; +class SecurityTab; +class MaintenanceTab; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget* parent = nullptr); + ~MainWindow() override; + +private: + void setupMenuBar(); + void setupToolBar(); + void setupTabs(); + void setupStatusBar(); + +private slots: + void onAbout(); + void onRefreshDisks(); + +private: + QTabWidget* m_tabWidget = nullptr; + QToolBar* m_toolBar = nullptr; + + // Tabs + DiskPartitionTab* m_diskPartitionTab = nullptr; + RecoveryTab* m_recoveryTab = nullptr; + ImagingTab* m_imagingTab = nullptr; + DiagnosticsTab* m_diagnosticsTab = nullptr; + SecurityTab* m_securityTab = nullptr; + MaintenanceTab* m_maintenanceTab = nullptr; +}; + +} // namespace spw diff --git a/src/ui/tabs/DiagnosticsTab.cpp b/src/ui/tabs/DiagnosticsTab.cpp new file mode 100644 index 0000000..f7429bf --- /dev/null +++ b/src/ui/tabs/DiagnosticsTab.cpp @@ -0,0 +1,90 @@ +#include "DiagnosticsTab.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spw +{ + +DiagnosticsTab::DiagnosticsTab(QWidget* parent) + : QWidget(parent) +{ + setupUi(); +} + +DiagnosticsTab::~DiagnosticsTab() = default; + +void DiagnosticsTab::setupUi() +{ + auto* layout = new QVBoxLayout(this); + + // Disk selector + auto* selectorLayout = new QHBoxLayout(); + selectorLayout->addWidget(new QLabel(tr("Select Disk:"))); + auto* diskCombo = new QComboBox(); + selectorLayout->addWidget(diskCombo, 1); + auto* refreshBtn = new QPushButton(tr("Refresh")); + selectorLayout->addWidget(refreshBtn); + layout->addLayout(selectorLayout); + + auto* splitter = new QSplitter(Qt::Horizontal); + + // S.M.A.R.T. panel + auto* smartGroup = new QGroupBox(tr("S.M.A.R.T. Health")); + auto* smartLayout = new QVBoxLayout(smartGroup); + + auto* healthLabel = new QLabel(tr("Overall Health: —")); + healthLabel->setStyleSheet("font-size: 16px; font-weight: bold; padding: 8px;"); + smartLayout->addWidget(healthLabel); + + auto* smartTable = new QTableWidget(0, 5); + smartTable->setHorizontalHeaderLabels( + {tr("ID"), tr("Attribute"), tr("Value"), tr("Worst"), tr("Threshold")}); + smartTable->setAlternatingRowColors(true); + smartLayout->addWidget(smartTable); + + splitter->addWidget(smartGroup); + + // Benchmark & Surface Scan panel + auto* rightPanel = new QWidget(); + auto* rightLayout = new QVBoxLayout(rightPanel); + + auto* benchGroup = new QGroupBox(tr("Benchmark")); + auto* benchLayout = new QVBoxLayout(benchGroup); + auto* benchResults = new QLabel( + tr("Sequential Read: — MB/s\n" + "Sequential Write: — MB/s\n" + "Random 4K Read: — IOPS\n" + "Random 4K Write: — IOPS")); + benchResults->setStyleSheet("font-family: monospace; padding: 8px;"); + benchLayout->addWidget(benchResults); + auto* benchBtn = new QPushButton(tr("Run Benchmark")); + benchBtn->setObjectName("applyButton"); + benchLayout->addWidget(benchBtn); + rightLayout->addWidget(benchGroup); + + auto* scanGroup = new QGroupBox(tr("Surface Scan")); + auto* scanLayout = new QVBoxLayout(scanGroup); + auto* scanInfo = new QLabel(tr("Sectors: — total, — bad, — pending")); + scanLayout->addWidget(scanInfo); + auto* scanProgress = new QProgressBar(); + scanLayout->addWidget(scanProgress); + auto* scanBtn = new QPushButton(tr("Start Surface Scan")); + scanBtn->setObjectName("applyButton"); + scanLayout->addWidget(scanBtn); + rightLayout->addWidget(scanGroup); + + rightLayout->addStretch(); + splitter->addWidget(rightPanel); + + layout->addWidget(splitter); +} + +} // namespace spw diff --git a/src/ui/tabs/DiagnosticsTab.h b/src/ui/tabs/DiagnosticsTab.h new file mode 100644 index 0000000..5f51700 --- /dev/null +++ b/src/ui/tabs/DiagnosticsTab.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace spw +{ + +class DiagnosticsTab : public QWidget +{ + Q_OBJECT + +public: + explicit DiagnosticsTab(QWidget* parent = nullptr); + ~DiagnosticsTab() override; + +private: + void setupUi(); +}; + +} // namespace spw diff --git a/src/ui/tabs/DiskPartitionTab.cpp b/src/ui/tabs/DiskPartitionTab.cpp new file mode 100644 index 0000000..3bd195f --- /dev/null +++ b/src/ui/tabs/DiskPartitionTab.cpp @@ -0,0 +1,107 @@ +#include "DiskPartitionTab.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spw +{ + +DiskPartitionTab::DiskPartitionTab(QWidget* parent) + : QWidget(parent) +{ + setupUi(); +} + +DiskPartitionTab::~DiskPartitionTab() = default; + +void DiskPartitionTab::setupUi() +{ + auto* layout = new QHBoxLayout(this); + layout->setContentsMargins(4, 4, 4, 4); + + m_mainSplitter = new QSplitter(Qt::Horizontal, this); + + // Left panel: Disk tree view + auto* leftPanel = new QWidget(); + auto* leftLayout = new QVBoxLayout(leftPanel); + leftLayout->setContentsMargins(0, 0, 0, 0); + + auto* diskLabel = new QLabel(tr("Physical Disks")); + diskLabel->setStyleSheet("font-weight: bold; padding: 4px;"); + leftLayout->addWidget(diskLabel); + + m_diskTree = new QTreeView(); + m_diskTree->setHeaderHidden(false); + m_diskTree->setAlternatingRowColors(true); + m_diskTree->setMinimumWidth(250); + leftLayout->addWidget(m_diskTree); + + m_mainSplitter->addWidget(leftPanel); + + // Center + Bottom: partition map and table + m_rightSplitter = new QSplitter(Qt::Vertical); + + // Disk map placeholder (will be replaced by DiskMapWidget) + m_diskMapPlaceholder = new QWidget(); + m_diskMapPlaceholder->setMinimumHeight(120); + auto* mapLayout = new QVBoxLayout(m_diskMapPlaceholder); + auto* mapLabel = new QLabel(tr("Partition Map")); + mapLabel->setAlignment(Qt::AlignCenter); + mapLabel->setStyleSheet("color: #6c7086; font-size: 14px;"); + mapLayout->addWidget(mapLabel); + m_rightSplitter->addWidget(m_diskMapPlaceholder); + + // Partition detail table + m_partitionTable = new QTableView(); + m_partitionTable->setAlternatingRowColors(true); + m_partitionTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_partitionTable->setSelectionMode(QAbstractItemView::SingleSelection); + m_partitionTable->horizontalHeader()->setStretchLastSection(true); + m_rightSplitter->addWidget(m_partitionTable); + + m_rightSplitter->setStretchFactor(0, 2); + m_rightSplitter->setStretchFactor(1, 3); + + m_mainSplitter->addWidget(m_rightSplitter); + + // Right panel: pending operations list + m_operationList = new QWidget(); + auto* opLayout = new QVBoxLayout(m_operationList); + opLayout->setContentsMargins(0, 0, 0, 0); + + auto* opLabel = new QLabel(tr("Pending Operations")); + opLabel->setStyleSheet("font-weight: bold; padding: 4px;"); + opLayout->addWidget(opLabel); + + auto* opListWidget = new QListWidget(); + opListWidget->setMinimumWidth(220); + opLayout->addWidget(opListWidget); + + auto* buttonLayout = new QHBoxLayout(); + auto* applyBtn = new QPushButton(tr("Apply")); + applyBtn->setObjectName("applyButton"); + auto* undoBtn = new QPushButton(tr("Undo")); + auto* clearBtn = new QPushButton(tr("Clear")); + buttonLayout->addWidget(applyBtn); + buttonLayout->addWidget(undoBtn); + buttonLayout->addWidget(clearBtn); + opLayout->addLayout(buttonLayout); + + m_mainSplitter->addWidget(m_operationList); + + // Set splitter proportions + m_mainSplitter->setStretchFactor(0, 1); // Disk tree + m_mainSplitter->setStretchFactor(1, 3); // Center content + m_mainSplitter->setStretchFactor(2, 1); // Operation list + + layout->addWidget(m_mainSplitter); +} + +} // namespace spw diff --git a/src/ui/tabs/DiskPartitionTab.h b/src/ui/tabs/DiskPartitionTab.h new file mode 100644 index 0000000..4731732 --- /dev/null +++ b/src/ui/tabs/DiskPartitionTab.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +class QSplitter; +class QTreeView; +class QTableView; + +namespace spw +{ + +class DiskPartitionTab : public QWidget +{ + Q_OBJECT + +public: + explicit DiskPartitionTab(QWidget* parent = nullptr); + ~DiskPartitionTab() override; + +private: + void setupUi(); + + QSplitter* m_mainSplitter = nullptr; + QSplitter* m_rightSplitter = nullptr; + + // Left panel: disk tree + QTreeView* m_diskTree = nullptr; + + // Center: partition map (placeholder for DiskMapWidget) + QWidget* m_diskMapPlaceholder = nullptr; + + // Bottom: partition detail table + QTableView* m_partitionTable = nullptr; + + // Right: operation list + QWidget* m_operationList = nullptr; +}; + +} // namespace spw diff --git a/src/ui/tabs/ImagingTab.cpp b/src/ui/tabs/ImagingTab.cpp new file mode 100644 index 0000000..ec5f9af --- /dev/null +++ b/src/ui/tabs/ImagingTab.cpp @@ -0,0 +1,96 @@ +#include "ImagingTab.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spw +{ + +ImagingTab::ImagingTab(QWidget* parent) + : QWidget(parent) +{ + setupUi(); +} + +ImagingTab::~ImagingTab() = default; + +void ImagingTab::setupUi() +{ + auto* layout = new QVBoxLayout(this); + + // Clone Disk section + auto* cloneGroup = new QGroupBox(tr("Clone Disk")); + auto* cloneLayout = new QGridLayout(cloneGroup); + cloneLayout->addWidget(new QLabel(tr("Source:")), 0, 0); + cloneLayout->addWidget(new QComboBox(), 0, 1); + cloneLayout->addWidget(new QLabel(tr("Target:")), 1, 0); + cloneLayout->addWidget(new QComboBox(), 1, 1); + auto* cloneBtn = new QPushButton(tr("Clone")); + cloneBtn->setObjectName("applyButton"); + cloneLayout->addWidget(cloneBtn, 2, 1, Qt::AlignRight); + layout->addWidget(cloneGroup); + + // Create Image section + auto* imageGroup = new QGroupBox(tr("Create Disk/Media Image")); + auto* imageLayout = new QGridLayout(imageGroup); + imageLayout->addWidget(new QLabel(tr("Source:")), 0, 0); + auto* sourceCombo = new QComboBox(); + sourceCombo->setToolTip(tr("Select disk, USB drive, SD card, or other media")); + imageLayout->addWidget(sourceCombo, 0, 1); + imageLayout->addWidget(new QLabel(tr("Output File:")), 1, 0); + auto* outputLine = new QLineEdit(); + imageLayout->addWidget(outputLine, 1, 1); + auto* browseBtn = new QPushButton(tr("Browse...")); + imageLayout->addWidget(browseBtn, 1, 2); + imageLayout->addWidget(new QLabel(tr("Compression:")), 2, 0); + auto* compCombo = new QComboBox(); + compCombo->addItems({tr("None"), tr("Fast (zstd-1)"), tr("Default (zstd-3)"), tr("Best (zstd-9)")}); + imageLayout->addWidget(compCombo, 2, 1); + auto* createImgBtn = new QPushButton(tr("Create Image")); + createImgBtn->setObjectName("applyButton"); + imageLayout->addWidget(createImgBtn, 3, 1, Qt::AlignRight); + layout->addWidget(imageGroup); + + // Restore Image section + auto* restoreGroup = new QGroupBox(tr("Restore Image")); + auto* restoreLayout = new QGridLayout(restoreGroup); + restoreLayout->addWidget(new QLabel(tr("Image File:")), 0, 0); + restoreLayout->addWidget(new QLineEdit(), 0, 1); + restoreLayout->addWidget(new QPushButton(tr("Browse...")), 0, 2); + restoreLayout->addWidget(new QLabel(tr("Target:")), 1, 0); + restoreLayout->addWidget(new QComboBox(), 1, 1); + auto* restoreBtn = new QPushButton(tr("Restore")); + restoreBtn->setObjectName("applyButton"); + restoreLayout->addWidget(restoreBtn, 2, 1, Qt::AlignRight); + layout->addWidget(restoreGroup); + + // Flash ISO/IMG section + auto* flashGroup = new QGroupBox(tr("Flash ISO/IMG to USB")); + auto* flashLayout = new QGridLayout(flashGroup); + flashLayout->addWidget(new QLabel(tr("Image:")), 0, 0); + flashLayout->addWidget(new QLineEdit(), 0, 1); + flashLayout->addWidget(new QPushButton(tr("Browse...")), 0, 2); + flashLayout->addWidget(new QLabel(tr("Target USB:")), 1, 0); + flashLayout->addWidget(new QComboBox(), 1, 1); + auto* flashBtn = new QPushButton(tr("Flash")); + flashBtn->setObjectName("applyButton"); + flashLayout->addWidget(flashBtn, 2, 1, Qt::AlignRight); + layout->addWidget(flashGroup); + + // Progress + auto* progressBar = new QProgressBar(); + progressBar->setVisible(false); + layout->addWidget(progressBar); + + layout->addStretch(); +} + +} // namespace spw diff --git a/src/ui/tabs/ImagingTab.h b/src/ui/tabs/ImagingTab.h new file mode 100644 index 0000000..91bf1a8 --- /dev/null +++ b/src/ui/tabs/ImagingTab.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace spw +{ + +class ImagingTab : public QWidget +{ + Q_OBJECT + +public: + explicit ImagingTab(QWidget* parent = nullptr); + ~ImagingTab() override; + +private: + void setupUi(); +}; + +} // namespace spw diff --git a/src/ui/tabs/MaintenanceTab.cpp b/src/ui/tabs/MaintenanceTab.cpp new file mode 100644 index 0000000..060695c --- /dev/null +++ b/src/ui/tabs/MaintenanceTab.cpp @@ -0,0 +1,106 @@ +#include "MaintenanceTab.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spw +{ + +MaintenanceTab::MaintenanceTab(QWidget* parent) + : QWidget(parent) +{ + setupUi(); +} + +MaintenanceTab::~MaintenanceTab() = default; + +void MaintenanceTab::setupUi() +{ + auto* layout = new QVBoxLayout(this); + + // Secure Erase section + auto* eraseGroup = new QGroupBox(tr("Secure Erase")); + auto* eraseLayout = new QGridLayout(eraseGroup); + + eraseLayout->addWidget(new QLabel(tr("Target Disk:")), 0, 0); + eraseLayout->addWidget(new QComboBox(), 0, 1); + + eraseLayout->addWidget(new QLabel(tr("Erase Method:")), 1, 0); + auto* methodWidget = new QWidget(); + auto* methodLayout = new QVBoxLayout(methodWidget); + methodLayout->setContentsMargins(0, 0, 0, 0); + auto* zeroPass = new QRadioButton(tr("Zero fill (1 pass) — Fast")); + zeroPass->setChecked(true); + auto* dod3Pass = new QRadioButton(tr("DoD 5220.22-M (3 passes) — Standard")); + auto* dod7Pass = new QRadioButton(tr("DoD 5220.22-M ECE (7 passes) — Enhanced")); + auto* gutmann = new QRadioButton(tr("Gutmann method (35 passes) — Maximum")); + auto* customPass = new QRadioButton(tr("Custom:")); + auto* customSpin = new QSpinBox(); + customSpin->setRange(1, 99); + customSpin->setValue(3); + customSpin->setEnabled(false); + auto* customRow = new QHBoxLayout(); + customRow->addWidget(customPass); + customRow->addWidget(customSpin); + customRow->addWidget(new QLabel(tr("passes"))); + customRow->addStretch(); + + methodLayout->addWidget(zeroPass); + methodLayout->addWidget(dod3Pass); + methodLayout->addWidget(dod7Pass); + methodLayout->addWidget(gutmann); + methodLayout->addLayout(customRow); + eraseLayout->addWidget(methodWidget, 1, 1); + + auto* verifyCheck = new QCheckBox(tr("Verify after erase")); + verifyCheck->setChecked(true); + eraseLayout->addWidget(verifyCheck, 2, 1); + + auto* eraseBtn = new QPushButton(tr("Secure Erase")); + eraseBtn->setObjectName("cancelButton"); + eraseBtn->setToolTip(tr("WARNING: This permanently destroys all data on the selected disk!")); + eraseLayout->addWidget(eraseBtn, 3, 1, Qt::AlignRight); + + layout->addWidget(eraseGroup); + + // Boot Repair section + auto* bootGroup = new QGroupBox(tr("Boot Repair")); + auto* bootLayout = new QVBoxLayout(bootGroup); + + auto* bootInfo = new QLabel( + tr("Repair boot configuration for Windows and other operating systems.")); + bootLayout->addWidget(bootInfo); + + auto* mbrRepairBtn = new QPushButton(tr("Repair MBR")); + mbrRepairBtn->setToolTip(tr("Rewrite the Master Boot Record with a standard boot loader")); + auto* gptRepairBtn = new QPushButton(tr("Repair GPT")); + gptRepairBtn->setToolTip(tr("Rebuild GPT headers and verify partition entries")); + auto* bcdRepairBtn = new QPushButton(tr("Repair Windows BCD")); + bcdRepairBtn->setToolTip(tr("Rebuild the Windows Boot Configuration Data store")); + auto* bootloaderBtn = new QPushButton(tr("Reinstall Bootloader")); + bootloaderBtn->setToolTip(tr("Reinstall the bootloader to the selected disk's boot sector")); + + bootLayout->addWidget(mbrRepairBtn); + bootLayout->addWidget(gptRepairBtn); + bootLayout->addWidget(bcdRepairBtn); + bootLayout->addWidget(bootloaderBtn); + + layout->addWidget(bootGroup); + + // Progress + auto* progressBar = new QProgressBar(); + progressBar->setVisible(false); + layout->addWidget(progressBar); + + layout->addStretch(); +} + +} // namespace spw diff --git a/src/ui/tabs/MaintenanceTab.h b/src/ui/tabs/MaintenanceTab.h new file mode 100644 index 0000000..21c2995 --- /dev/null +++ b/src/ui/tabs/MaintenanceTab.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace spw +{ + +class MaintenanceTab : public QWidget +{ + Q_OBJECT + +public: + explicit MaintenanceTab(QWidget* parent = nullptr); + ~MaintenanceTab() override; + +private: + void setupUi(); +}; + +} // namespace spw diff --git a/src/ui/tabs/RecoveryTab.cpp b/src/ui/tabs/RecoveryTab.cpp new file mode 100644 index 0000000..a7e7f6b --- /dev/null +++ b/src/ui/tabs/RecoveryTab.cpp @@ -0,0 +1,91 @@ +#include "RecoveryTab.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spw +{ + +RecoveryTab::RecoveryTab(QWidget* parent) + : QWidget(parent) +{ + setupUi(); +} + +RecoveryTab::~RecoveryTab() = default; + +void RecoveryTab::setupUi() +{ + auto* layout = new QHBoxLayout(this); + auto* splitter = new QSplitter(Qt::Horizontal); + + // Left: recovery options + auto* optionsPanel = new QWidget(); + auto* optLayout = new QVBoxLayout(optionsPanel); + + auto* typeGroup = new QGroupBox(tr("Recovery Type")); + auto* typeLayout = new QVBoxLayout(typeGroup); + auto* partRecoveryBtn = new QPushButton(tr("Partition Recovery")); + partRecoveryBtn->setToolTip(tr("Scan for lost or deleted partitions")); + auto* fileRecoveryBtn = new QPushButton(tr("File Recovery")); + fileRecoveryBtn->setToolTip(tr("Recover files from damaged or formatted drives")); + auto* mbrRepairBtn = new QPushButton(tr("MBR/GPT Repair")); + mbrRepairBtn->setToolTip(tr("Rebuild partition table from filesystem superblocks")); + typeLayout->addWidget(partRecoveryBtn); + typeLayout->addWidget(fileRecoveryBtn); + typeLayout->addWidget(mbrRepairBtn); + optLayout->addWidget(typeGroup); + + auto* targetGroup = new QGroupBox(tr("Target Disk")); + auto* targetLayout = new QVBoxLayout(targetGroup); + auto* diskCombo = new QComboBox(); + diskCombo->addItem(tr("Select a disk...")); + targetLayout->addWidget(diskCombo); + optLayout->addWidget(targetGroup); + + auto* scanBtn = new QPushButton(tr("Start Scan")); + scanBtn->setObjectName("applyButton"); + optLayout->addWidget(scanBtn); + + auto* progressBar = new QProgressBar(); + progressBar->setVisible(false); + optLayout->addWidget(progressBar); + + optLayout->addStretch(); + splitter->addWidget(optionsPanel); + + // Right: results + auto* resultsPanel = new QWidget(); + auto* resLayout = new QVBoxLayout(resultsPanel); + + auto* resLabel = new QLabel(tr("Recovery Results")); + resLabel->setStyleSheet("font-weight: bold; padding: 4px;"); + resLayout->addWidget(resLabel); + + auto* resultsTable = new QTableWidget(0, 5); + resultsTable->setHorizontalHeaderLabels( + {tr("Type"), tr("Name/Label"), tr("Size"), tr("Filesystem"), tr("Confidence")}); + resultsTable->setAlternatingRowColors(true); + resultsTable->setSelectionBehavior(QAbstractItemView::SelectRows); + resLayout->addWidget(resultsTable); + + auto* recoverBtn = new QPushButton(tr("Recover Selected")); + recoverBtn->setObjectName("applyButton"); + resLayout->addWidget(recoverBtn); + + splitter->addWidget(resultsPanel); + splitter->setStretchFactor(0, 1); + splitter->setStretchFactor(1, 2); + + layout->addWidget(splitter); +} + +} // namespace spw diff --git a/src/ui/tabs/RecoveryTab.h b/src/ui/tabs/RecoveryTab.h new file mode 100644 index 0000000..48ba65a --- /dev/null +++ b/src/ui/tabs/RecoveryTab.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace spw +{ + +class RecoveryTab : public QWidget +{ + Q_OBJECT + +public: + explicit RecoveryTab(QWidget* parent = nullptr); + ~RecoveryTab() override; + +private: + void setupUi(); +}; + +} // namespace spw diff --git a/src/ui/tabs/SecurityTab.cpp b/src/ui/tabs/SecurityTab.cpp new file mode 100644 index 0000000..cdb2b23 --- /dev/null +++ b/src/ui/tabs/SecurityTab.cpp @@ -0,0 +1,154 @@ +#include "SecurityTab.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spw +{ + +SecurityTab::SecurityTab(QWidget* parent) + : QWidget(parent) +{ + setupUi(); +} + +SecurityTab::~SecurityTab() = default; + +void SecurityTab::setupUi() +{ + auto* layout = new QVBoxLayout(this); + + // Sub-tabs for the three security key types + auto* subTabs = new QTabWidget(); + + // --- FIDO2/WebAuthn Tab --- + auto* fido2Widget = new QWidget(); + auto* fido2Layout = new QVBoxLayout(fido2Widget); + + auto* fido2DevGroup = new QGroupBox(tr("FIDO2 Device")); + auto* fido2DevLayout = new QGridLayout(fido2DevGroup); + fido2DevLayout->addWidget(new QLabel(tr("Device:")), 0, 0); + auto* fido2DeviceCombo = new QComboBox(); + fido2DeviceCombo->addItem(tr("No FIDO2 devices detected")); + fido2DevLayout->addWidget(fido2DeviceCombo, 0, 1); + auto* fido2RefreshBtn = new QPushButton(tr("Refresh")); + fido2DevLayout->addWidget(fido2RefreshBtn, 0, 2); + fido2DevLayout->addWidget(new QLabel(tr("Device Info:")), 1, 0); + fido2DevLayout->addWidget(new QLabel(tr("—")), 1, 1); + fido2Layout->addWidget(fido2DevGroup); + + auto* fido2OpsGroup = new QGroupBox(tr("Operations")); + auto* fido2OpsLayout = new QVBoxLayout(fido2OpsGroup); + auto* setPinBtn = new QPushButton(tr("Set/Change PIN")); + auto* genCredBtn = new QPushButton(tr("Generate Credential")); + auto* listCredsBtn = new QPushButton(tr("List Resident Keys")); + auto* resetBtn = new QPushButton(tr("Factory Reset Device")); + resetBtn->setObjectName("cancelButton"); + fido2OpsLayout->addWidget(setPinBtn); + fido2OpsLayout->addWidget(genCredBtn); + fido2OpsLayout->addWidget(listCredsBtn); + fido2OpsLayout->addWidget(resetBtn); + fido2Layout->addWidget(fido2OpsGroup); + + auto* fido2KeyList = new QListWidget(); + fido2Layout->addWidget(new QLabel(tr("Resident Keys:"))); + fido2Layout->addWidget(fido2KeyList); + + subTabs->addTab(fido2Widget, tr("FIDO2 / WebAuthn")); + + // --- Encrypted Vault Tab --- + auto* vaultWidget = new QWidget(); + auto* vaultLayout = new QVBoxLayout(vaultWidget); + + auto* vaultCreateGroup = new QGroupBox(tr("Create Encrypted Vault")); + auto* vaultCreateLayout = new QGridLayout(vaultCreateGroup); + vaultCreateLayout->addWidget(new QLabel(tr("USB Drive:")), 0, 0); + vaultCreateLayout->addWidget(new QComboBox(), 0, 1); + vaultCreateLayout->addWidget(new QLabel(tr("Vault Size:")), 1, 0); + auto* vaultSize = new QSpinBox(); + vaultSize->setRange(1, 999999); + vaultSize->setSuffix(" MB"); + vaultSize->setValue(256); + vaultCreateLayout->addWidget(vaultSize, 1, 1); + vaultCreateLayout->addWidget(new QLabel(tr("Encryption:")), 2, 0); + auto* encCombo = new QComboBox(); + encCombo->addItems({tr("AES-256-XTS"), tr("AES-256-CBC"), tr("ChaCha20-Poly1305")}); + vaultCreateLayout->addWidget(encCombo, 2, 1); + vaultCreateLayout->addWidget(new QLabel(tr("Password:")), 3, 0); + auto* passEdit = new QLineEdit(); + passEdit->setEchoMode(QLineEdit::Password); + vaultCreateLayout->addWidget(passEdit, 3, 1); + vaultCreateLayout->addWidget(new QLabel(tr("Confirm:")), 4, 0); + auto* confirmEdit = new QLineEdit(); + confirmEdit->setEchoMode(QLineEdit::Password); + vaultCreateLayout->addWidget(confirmEdit, 4, 1); + auto* keyFileCheck = new QCheckBox(tr("Also require key file")); + vaultCreateLayout->addWidget(keyFileCheck, 5, 1); + vaultLayout->addWidget(vaultCreateGroup); + + auto* createVaultBtn = new QPushButton(tr("Create Vault")); + createVaultBtn->setObjectName("applyButton"); + vaultLayout->addWidget(createVaultBtn); + + auto* vaultManageGroup = new QGroupBox(tr("Manage Existing Vaults")); + auto* vaultManageLayout = new QVBoxLayout(vaultManageGroup); + auto* unlockBtn = new QPushButton(tr("Unlock Vault")); + auto* lockBtn = new QPushButton(tr("Lock Vault")); + auto* changePassBtn = new QPushButton(tr("Change Password")); + vaultManageLayout->addWidget(unlockBtn); + vaultManageLayout->addWidget(lockBtn); + vaultManageLayout->addWidget(changePassBtn); + vaultLayout->addWidget(vaultManageGroup); + + vaultLayout->addStretch(); + subTabs->addTab(vaultWidget, tr("Encrypted Vaults")); + + // --- Boot Auth Key Tab --- + auto* bootAuthWidget = new QWidget(); + auto* bootAuthLayout = new QVBoxLayout(bootAuthWidget); + + auto* bootAuthGroup = new QGroupBox(tr("Boot Authentication Key")); + auto* bootAuthGridLayout = new QGridLayout(bootAuthGroup); + bootAuthGridLayout->addWidget(new QLabel(tr("USB Drive:")), 0, 0); + bootAuthGridLayout->addWidget(new QComboBox(), 0, 1); + bootAuthGridLayout->addWidget(new QLabel(tr("Target PC:")), 1, 0); + auto* pcIdLabel = new QLabel(tr("Current machine")); + bootAuthGridLayout->addWidget(pcIdLabel, 1, 1); + bootAuthGridLayout->addWidget(new QLabel(tr("Auth Method:")), 2, 0); + auto* authMethodCombo = new QComboBox(); + authMethodCombo->addItems({tr("USB presence only"), tr("USB + PIN"), tr("USB + Password")}); + bootAuthGridLayout->addWidget(authMethodCombo, 2, 1); + bootAuthLayout->addWidget(bootAuthGroup); + + auto* createBootKeyBtn = new QPushButton(tr("Create Boot Auth Key")); + createBootKeyBtn->setObjectName("applyButton"); + bootAuthLayout->addWidget(createBootKeyBtn); + + auto* bootKeyInfoGroup = new QGroupBox(tr("Information")); + auto* bootKeyInfoLayout = new QVBoxLayout(bootKeyInfoGroup); + bootKeyInfoLayout->addWidget(new QLabel( + tr("A boot authentication key prevents your PC from booting\n" + "unless the USB key is inserted. The key material is paired\n" + "with your machine's hardware identity.\n\n" + "Warning: Keep a backup key! Losing the USB key may lock\n" + "you out of your system."))); + bootAuthLayout->addWidget(bootKeyInfoGroup); + + bootAuthLayout->addStretch(); + subTabs->addTab(bootAuthWidget, tr("Boot Authentication")); + + layout->addWidget(subTabs); +} + +} // namespace spw diff --git a/src/ui/tabs/SecurityTab.h b/src/ui/tabs/SecurityTab.h new file mode 100644 index 0000000..f594ba6 --- /dev/null +++ b/src/ui/tabs/SecurityTab.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace spw +{ + +class SecurityTab : public QWidget +{ + Q_OBJECT + +public: + explicit SecurityTab(QWidget* parent = nullptr); + ~SecurityTab() override; + +private: + void setupUi(); +}; + +} // namespace spw diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8f6858a --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,16 @@ +include(GoogleTest) + +add_executable(spw_tests + test_result.cpp +) + +target_include_directories(spw_tests PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(spw_tests PRIVATE + spw_core + GTest::gtest_main +) + +gtest_discover_tests(spw_tests) diff --git a/tests/test_result.cpp b/tests/test_result.cpp new file mode 100644 index 0000000..feacaa2 --- /dev/null +++ b/tests/test_result.cpp @@ -0,0 +1,72 @@ +#include "core/common/Result.h" + +#include +#include + +using namespace spw; + +TEST(ResultTest, SuccessValue) +{ + Result r(42); + EXPECT_TRUE(r.isOk()); + EXPECT_FALSE(r.isError()); + EXPECT_EQ(r.value(), 42); +} + +TEST(ResultTest, ErrorValue) +{ + Result r(ErrorInfo::fromCode(ErrorCode::DiskNotFound, "disk missing")); + EXPECT_FALSE(r.isOk()); + EXPECT_TRUE(r.isError()); + EXPECT_EQ(r.error().code, ErrorCode::DiskNotFound); + EXPECT_EQ(r.error().message, "disk missing"); +} + +TEST(ResultTest, ValueOr) +{ + Result ok(10); + EXPECT_EQ(ok.valueOr(99), 10); + + Result err(ErrorInfo::fromCode(ErrorCode::Unknown)); + EXPECT_EQ(err.valueOr(99), 99); +} + +TEST(ResultTest, BoolConversion) +{ + Result ok(std::string("hello")); + EXPECT_TRUE(static_cast(ok)); + + Result err(ErrorInfo::fromCode(ErrorCode::Unknown)); + EXPECT_FALSE(static_cast(err)); +} + +TEST(ResultTest, Map) +{ + Result r(5); + auto mapped = r.map([](int v) { return v * 2; }); + EXPECT_TRUE(mapped.isOk()); + EXPECT_EQ(mapped.value(), 10); +} + +TEST(ResultTest, MapOnError) +{ + Result r(ErrorInfo::fromCode(ErrorCode::DiskReadError)); + auto mapped = r.map([](int v) { return v * 2; }); + EXPECT_TRUE(mapped.isError()); + EXPECT_EQ(mapped.error().code, ErrorCode::DiskReadError); +} + +TEST(ResultVoidTest, Success) +{ + Result r = Result::ok(); + EXPECT_TRUE(r.isOk()); + EXPECT_FALSE(r.isError()); +} + +TEST(ResultVoidTest, Error) +{ + Result r(ErrorInfo::fromCode(ErrorCode::DiskWriteError, "write failed")); + EXPECT_FALSE(r.isOk()); + EXPECT_TRUE(r.isError()); + EXPECT_EQ(r.error().code, ErrorCode::DiskWriteError); +} diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt new file mode 100644 index 0000000..dfdd494 --- /dev/null +++ b/third_party/CMakeLists.txt @@ -0,0 +1,23 @@ +# Third-party dependencies managed via FetchContent +include(FetchContent) + +# Google Test (for testing only) +if(SPW_BUILD_TESTS) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.14.0 + ) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) +endif() + +# zstd (compression for disk images) +# For now, we'll use system zstd or fetch it later +# FetchContent_Declare( +# zstd +# GIT_REPOSITORY https://github.com/facebook/zstd.git +# GIT_TAG v1.5.5 +# SOURCE_SUBDIR build/cmake +# ) +# FetchContent_MakeAvailable(zstd)