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:
DigiJ
2026-03-11 19:11:25 -07:00
commit 179bb85c4f
42 changed files with 2399 additions and 0 deletions

11
.clang-format Normal file
View 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
View File

@@ -0,0 +1,11 @@
build/
.vs/
.vscode/
*.user
*.suo
*.sdf
*.opensdf
*.db
*.opendb
CMakeUserPresets.json
compile_commands.json

28
CMakeLists.txt Normal file
View 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
View 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 }
}
]
}

View 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
View 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()

View 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
View 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
View 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
View 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

View 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
View File

@@ -0,0 +1,3 @@
add_subdirectory(core)
add_subdirectory(ui)
add_subdirectory(app)

26
src/app/CMakeLists.txt Normal file
View 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)

View 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
View 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
View 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
View 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()

View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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 &copy; 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
View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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

View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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)