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<T> monadic error handling, admin elevation, and dark Catppuccin theme.
This commit is contained in:
11
.clang-format
Normal file
11
.clang-format
Normal file
@@ -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
|
||||||
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
build/
|
||||||
|
.vs/
|
||||||
|
.vscode/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.db
|
||||||
|
*.opendb
|
||||||
|
CMakeUserPresets.json
|
||||||
|
compile_commands.json
|
||||||
28
CMakeLists.txt
Normal file
28
CMakeLists.txt
Normal file
@@ -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()
|
||||||
54
CMakePresets.json
Normal file
54
CMakePresets.json
Normal file
@@ -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 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
cmake/CompilerWarnings.cmake
Normal file
13
cmake/CompilerWarnings.cmake
Normal file
@@ -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()
|
||||||
9
cmake/FindWMI.cmake
Normal file
9
cmake/FindWMI.cmake
Normal file
@@ -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()
|
||||||
16
cmake/QtDeployHelper.cmake
Normal file
16
cmake/QtDeployHelper.cmake
Normal file
@@ -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
|
||||||
|
"$<TARGET_FILE:${TARGET}>"
|
||||||
|
COMMENT "Running windeployqt on ${TARGET}..."
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
12
cmake/Version.cmake
Normal file
12
cmake/Version.cmake
Normal file
@@ -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}"
|
||||||
|
)
|
||||||
29
resources/setec.manifest
Normal file
29
resources/setec.manifest
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<assemblyIdentity
|
||||||
|
version="1.0.0.0"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
name="Setec.PartitionWizard"
|
||||||
|
type="win32"
|
||||||
|
/>
|
||||||
|
<description>Setec Partition Wizard</description>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- Windows 10 and 11 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
37
resources/setec.rc
Normal file
37
resources/setec.rc
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include <winver.h>
|
||||||
|
|
||||||
|
// 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
|
||||||
165
resources/styles/default.qss
Normal file
165
resources/styles/default.qss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
3
src/CMakeLists.txt
Normal file
3
src/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
add_subdirectory(core)
|
||||||
|
add_subdirectory(ui)
|
||||||
|
add_subdirectory(app)
|
||||||
26
src/app/CMakeLists.txt
Normal file
26
src/app/CMakeLists.txt
Normal file
@@ -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)
|
||||||
36
src/app/SingleInstance.cpp
Normal file
36
src/app/SingleInstance.cpp
Normal file
@@ -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
|
||||||
32
src/app/SingleInstance.h
Normal file
32
src/app/SingleInstance.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#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
|
||||||
124
src/app/main.cpp
Normal file
124
src/app/main.cpp
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#include "SingleInstance.h"
|
||||||
|
#include "core/common/Logging.h"
|
||||||
|
#include "core/common/Version.h"
|
||||||
|
#include "ui/MainWindow.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
51
src/core/CMakeLists.txt
Normal file
51
src/core/CMakeLists.txt
Normal file
@@ -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()
|
||||||
71
src/core/common/Constants.h
Normal file
71
src/core/common/Constants.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
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
|
||||||
114
src/core/common/Error.h
Normal file
114
src/core/common/Error.h
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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
|
||||||
97
src/core/common/Logging.cpp
Normal file
97
src/core/common/Logging.cpp
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#include "Logging.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
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
|
||||||
47
src/core/common/Logging.h
Normal file
47
src/core/common/Logging.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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
|
||||||
121
src/core/common/Result.h
Normal file
121
src/core/common/Result.h
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Error.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace spw
|
||||||
|
{
|
||||||
|
|
||||||
|
// Monadic result type for disk operations.
|
||||||
|
// Forces callers to handle errors — no exceptions can skip past a partial write.
|
||||||
|
template <typename T>
|
||||||
|
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<ErrorInfo>(m_data).isError());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOk() const { return std::holds_alternative<T>(m_data); }
|
||||||
|
bool isError() const { return std::holds_alternative<ErrorInfo>(m_data); }
|
||||||
|
|
||||||
|
const T& value() const&
|
||||||
|
{
|
||||||
|
assert(isOk());
|
||||||
|
return std::get<T>(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
T& value() &
|
||||||
|
{
|
||||||
|
assert(isOk());
|
||||||
|
return std::get<T>(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
T&& value() &&
|
||||||
|
{
|
||||||
|
assert(isOk());
|
||||||
|
return std::get<T>(std::move(m_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
const ErrorInfo& error() const
|
||||||
|
{
|
||||||
|
assert(isError());
|
||||||
|
return std::get<ErrorInfo>(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value or default
|
||||||
|
T valueOr(const T& defaultVal) const
|
||||||
|
{
|
||||||
|
if (isOk())
|
||||||
|
return value();
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monadic map
|
||||||
|
template <typename Func>
|
||||||
|
auto map(Func&& func) const -> Result<decltype(func(std::declval<T>()))>
|
||||||
|
{
|
||||||
|
if (isOk())
|
||||||
|
return func(value());
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monadic flatMap / andThen
|
||||||
|
template <typename Func>
|
||||||
|
auto andThen(Func&& func) const -> decltype(func(std::declval<T>()))
|
||||||
|
{
|
||||||
|
if (isOk())
|
||||||
|
return func(value());
|
||||||
|
return error();
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const { return isOk(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::variant<T, ErrorInfo> m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Specialization for void results
|
||||||
|
template <>
|
||||||
|
class Result<void>
|
||||||
|
{
|
||||||
|
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<ErrorInfo> m_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace spw
|
||||||
140
src/core/common/Types.h
Normal file
140
src/core/common/Types.h
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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
|
||||||
28
src/core/common/Version.h
Normal file
28
src/core/common/Version.h
Normal file
@@ -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
|
||||||
30
src/ui/CMakeLists.txt
Normal file
30
src/ui/CMakeLists.txt
Normal file
@@ -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
|
||||||
|
)
|
||||||
149
src/ui/MainWindow.cpp
Normal file
149
src/ui/MainWindow.cpp
Normal file
@@ -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 <QAction>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QStatusBar>
|
||||||
|
#include <QTabWidget>
|
||||||
|
#include <QToolBar>
|
||||||
|
|
||||||
|
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("<h2>%1</h2>"
|
||||||
|
"<p>Version %2</p>"
|
||||||
|
"<p>A comprehensive disk recovery, repair, flashing, and formatting tool "
|
||||||
|
"for Windows.</p>"
|
||||||
|
"<p>Supports all major and legacy filesystems, partition table formats "
|
||||||
|
"(MBR, GPT, APM), USB security key creation, and media imaging.</p>"
|
||||||
|
"<p>Copyright © 2026 Setec</p>")
|
||||||
|
.arg(AppName, VersionString));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onRefreshDisks()
|
||||||
|
{
|
||||||
|
statusBar()->showMessage(tr("Refreshing disk list..."), 2000);
|
||||||
|
// TODO: Call DiskController::refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace spw
|
||||||
51
src/ui/MainWindow.h
Normal file
51
src/ui/MainWindow.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
|
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
|
||||||
90
src/ui/tabs/DiagnosticsTab.cpp
Normal file
90
src/ui/tabs/DiagnosticsTab.cpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#include "DiagnosticsTab.h"
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
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
|
||||||
20
src/ui/tabs/DiagnosticsTab.h
Normal file
20
src/ui/tabs/DiagnosticsTab.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace spw
|
||||||
|
{
|
||||||
|
|
||||||
|
class DiagnosticsTab : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DiagnosticsTab(QWidget* parent = nullptr);
|
||||||
|
~DiagnosticsTab() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUi();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace spw
|
||||||
107
src/ui/tabs/DiskPartitionTab.cpp
Normal file
107
src/ui/tabs/DiskPartitionTab.cpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#include "DiskPartitionTab.h"
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <QTableView>
|
||||||
|
#include <QTreeView>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
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
|
||||||
39
src/ui/tabs/DiskPartitionTab.h
Normal file
39
src/ui/tabs/DiskPartitionTab.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
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
|
||||||
96
src/ui/tabs/ImagingTab.cpp
Normal file
96
src/ui/tabs/ImagingTab.cpp
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#include "ImagingTab.h"
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
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
|
||||||
20
src/ui/tabs/ImagingTab.h
Normal file
20
src/ui/tabs/ImagingTab.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace spw
|
||||||
|
{
|
||||||
|
|
||||||
|
class ImagingTab : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ImagingTab(QWidget* parent = nullptr);
|
||||||
|
~ImagingTab() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUi();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace spw
|
||||||
106
src/ui/tabs/MaintenanceTab.cpp
Normal file
106
src/ui/tabs/MaintenanceTab.cpp
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#include "MaintenanceTab.h"
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QRadioButton>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
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
|
||||||
20
src/ui/tabs/MaintenanceTab.h
Normal file
20
src/ui/tabs/MaintenanceTab.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace spw
|
||||||
|
{
|
||||||
|
|
||||||
|
class MaintenanceTab : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MaintenanceTab(QWidget* parent = nullptr);
|
||||||
|
~MaintenanceTab() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUi();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace spw
|
||||||
91
src/ui/tabs/RecoveryTab.cpp
Normal file
91
src/ui/tabs/RecoveryTab.cpp
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include "RecoveryTab.h"
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
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
|
||||||
20
src/ui/tabs/RecoveryTab.h
Normal file
20
src/ui/tabs/RecoveryTab.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace spw
|
||||||
|
{
|
||||||
|
|
||||||
|
class RecoveryTab : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RecoveryTab(QWidget* parent = nullptr);
|
||||||
|
~RecoveryTab() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUi();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace spw
|
||||||
154
src/ui/tabs/SecurityTab.cpp
Normal file
154
src/ui/tabs/SecurityTab.cpp
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
#include "SecurityTab.h"
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QTabWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
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
|
||||||
20
src/ui/tabs/SecurityTab.h
Normal file
20
src/ui/tabs/SecurityTab.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace spw
|
||||||
|
{
|
||||||
|
|
||||||
|
class SecurityTab : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SecurityTab(QWidget* parent = nullptr);
|
||||||
|
~SecurityTab() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUi();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace spw
|
||||||
16
tests/CMakeLists.txt
Normal file
16
tests/CMakeLists.txt
Normal file
@@ -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)
|
||||||
72
tests/test_result.cpp
Normal file
72
tests/test_result.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include "core/common/Result.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace spw;
|
||||||
|
|
||||||
|
TEST(ResultTest, SuccessValue)
|
||||||
|
{
|
||||||
|
Result<int> r(42);
|
||||||
|
EXPECT_TRUE(r.isOk());
|
||||||
|
EXPECT_FALSE(r.isError());
|
||||||
|
EXPECT_EQ(r.value(), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ResultTest, ErrorValue)
|
||||||
|
{
|
||||||
|
Result<int> 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<int> ok(10);
|
||||||
|
EXPECT_EQ(ok.valueOr(99), 10);
|
||||||
|
|
||||||
|
Result<int> err(ErrorInfo::fromCode(ErrorCode::Unknown));
|
||||||
|
EXPECT_EQ(err.valueOr(99), 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ResultTest, BoolConversion)
|
||||||
|
{
|
||||||
|
Result<std::string> ok(std::string("hello"));
|
||||||
|
EXPECT_TRUE(static_cast<bool>(ok));
|
||||||
|
|
||||||
|
Result<std::string> err(ErrorInfo::fromCode(ErrorCode::Unknown));
|
||||||
|
EXPECT_FALSE(static_cast<bool>(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ResultTest, Map)
|
||||||
|
{
|
||||||
|
Result<int> r(5);
|
||||||
|
auto mapped = r.map([](int v) { return v * 2; });
|
||||||
|
EXPECT_TRUE(mapped.isOk());
|
||||||
|
EXPECT_EQ(mapped.value(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ResultTest, MapOnError)
|
||||||
|
{
|
||||||
|
Result<int> 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<void> r = Result<void>::ok();
|
||||||
|
EXPECT_TRUE(r.isOk());
|
||||||
|
EXPECT_FALSE(r.isError());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ResultVoidTest, Error)
|
||||||
|
{
|
||||||
|
Result<void> r(ErrorInfo::fromCode(ErrorCode::DiskWriteError, "write failed"));
|
||||||
|
EXPECT_FALSE(r.isOk());
|
||||||
|
EXPECT_TRUE(r.isError());
|
||||||
|
EXPECT_EQ(r.error().code, ErrorCode::DiskWriteError);
|
||||||
|
}
|
||||||
23
third_party/CMakeLists.txt
vendored
Normal file
23
third_party/CMakeLists.txt
vendored
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user