Skip to content

Architecture

Mikhail Deynekin edited this page Oct 16, 2026 · 2 revisions

Architecture

One executable, three personalities (GUI, headless guard, CLI), two threads of real work (UI and guard worker) plus transient update threads, and a UI-free engine core that could be lifted into another shell tomorrow. This page maps modules, threads, IPC and data flow as implemented at commit 124e849; line references point into that tree.


Contents


Module map

src/
├── main.cpp            wWinMain: parse → headless or GUI
├── app/app.cpp         bootstrap: mutex, IPC window, init order, wizard, loop
├── core/               engine — no UI includes
│   ├── registry.*      thin Reg*W wrappers (get/set dword+string, delete, enum, clear)
│   ├── trayicons.*     store: rescan, counts, SetPromoted, PromoteAll, RestoreDefaults,
│   │                   exclusions, friendly names, HICON cache
│   ├── guard.*         watcher thread, modes, stats, pause/resume, PromoteAllNow
│   ├── legacy.*        EnableAutoTray belt, HideSCA cleanup, TrayNotify cache reset
│   ├── explorer.*      safe shell restart
│   ├── backup.*        schema-8 snapshots, prune, traversal-guarded delete
│   ├── autostart.*     HKCU Run install/remove
│   ├── settings.*      settings.json schema 8, clamps, atomic save, first-run
│   ├── log.*           daily rotating file + 500-entry ring
│   ├── json.*          dependency-free parser/writer (ordered objects)
│   ├── cli.*           argv parsing, headless verbs, exit codes, IPC client
│   ├── update.*        feed fetch, version compare, download, SHA-256, apply
│   ├── installer.*     per-user install/uninstall (copy, lnk, uninstall key)
│   ├── os.*            RtlGetVersion, theme/accent/language probes, DPI helpers
│   └── win_compat.h    missing-constant shims for older mingw headers
├── ui/
│   ├── theme.*         palette resolution (mode × accent), subscribers
│   ├── gfx.*           GDI+ bootstrap, fonts, rounded rects, app-icon bitmap
│   ├── kit/            widget base + Button/IconButton/Toggle/Segmented/SearchBox/ScrollBar
│   ├── mainwindow.*    shell: nav rail, top bar, status bar, input, DWM, anim timer
│   ├── trayicon.*      own icon, menu, balloons, TaskbarCreated
│   ├── setupwizard.*   first-run modal wizard
│   ├── pages/          dashboard, icons, guard, appearance, backups, activity, about
│   └── lang/           string ids, EN base, 29 sparse packs, resolver
└── util/               str (UTF-8↔UTF-16, wildcards, version compare), fs (paths, atomic IO)

Design rule from the project charter: the engine never includes UI headers; the UI thread owns every HWND and GDI+ object; workers communicate by PostMessage only.

Thread model and event marshalling

Thread Lifetime Work
UI (main) process message loop, all painting, settings writes, clipboard, dialogs
Guard worker Start→Stop arm registry watch → wait → debounce → reconcile pass → emit events
Update worker one-shot feed GET (auto-check or manual check), posts result messages
Download/apply worker one-shot binary GET + hash + apply, posts success/error

Worker → UI channels: guard events via registered message TrayIconGuard.GuardEvent.v8 (fallback WM_APP+1) carrying a heap-copied wide string the UI deletes; update results via WM_APP+60/63/64; “update applied, relaunch” via WM_APP+62; silent auto-check balloon via WM_APP+61. Locks: SRWLOCK around guard sink+stats, store caches, and the log; settings are touched from the UI thread only (the guard reads them).

External IPC: message-only window TrayIconGuard.Ipc.v8 accepting WM_COPYDATA with fixed constant 0x54494738 and verbs show|toggle-guard|exit; single-instance mutex TrayIconGuard.SingleInstance.v8.

Startup sequence

flowchart TD
    A["wWinMain: SetErrorMode, argv"] --> B["cli::Parse"]
    B -->|invalid| Z["IPC show + exit 1 (silent in published builds)"]
    B -->|headless| H["cli::ExecuteHeadless"]
    B -->|GUI/ui-test| C["mutex: already running? → IPC show, exit 0"]
    C --> D["DPI PerMonitorV2, OleInitialize, GDI+ init"]
    D --> E["settings::Load → l10n → log::Init → theme::Init"]
    E --> F{"install mode unknown?"}
    F -->|yes| G["setup wizard → install+relaunch | portable | cancel"]
    F -->|no| I["store::Rescan, MainWindow::Create"]
    G --> I
    I --> J["IPC window, tray icon"]
    J --> K{"first run?"}
    K -->|yes| L["guardEnabled=true, promote-all once, welcome balloon"]
    K -->|no| M["guard::Start if enabled"]
    L --> M
    M --> N["opt-in auto-check thread; autostart reconcile; message loop"]
Loading

Reconcile pass data flow

RegNotifyChangeKeyValue (subtree, NAME|LAST_SET, async, auto-reset event) → wait (stop event; timeout = pollMs, INFINITE when 0) → debounce wait → paused? re-arm : RunPass: store::Rescan → desired state per mode (full / compact own-icon / custom minus exclusions and user-hidden) → store::SetPromoted diffs → stats, log EVENT, UI event N|names → optional cooldown-bounded Explorer restart → re-arm. See The Guard Watcher for the wakeup defect in the published wait set and the shipped patch.

UI framework

Retained-mode custom widgets: each owns Layout/Paint/OnMouse/OnKey/Tick; pages own widget vectors and a content-height for the overlay scrollbar. Page-local coordinates are offset into window space once per layout, so hit-testing and painting share one space. Paint order: window background → page PaintBackground (cards, labels) → widgets → scrollbar; one compatible DC double-buffers the whole window per WM_PAINT. Animations use exponential approach with a 16 ms timer armed only while something moves. Native controls appear in exactly two places: a borderless EDIT for search input (themed via WM_CTLCOLOREDIT) and TaskDialogs for confirmations/save dialogs.

Persistence and atomicity

Settings and snapshots: write <path>.tmp then MoveFileExW(REPLACE_EXISTING) (snapshots add WRITE_THROUGH). Logs: append-only daily files, pruned to 14 days at init. Registry: values only, never tree deletions except the app's own uninstall key. Backup delete validates names against path separators and ...

Build system and CI gates

CMake ≥ 3.16, C++17, defines UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX WINVER=0x0A00 _WIN32_WINNT=0x0A00 NTDDI_VERSION=0x0A000006. GNU path: -Wall -Wextra -Wno-unused-parameter -O2 -ffunction-sections -fdata-sections, link -static -static-libgcc -static-libstdc++ -Wl,--gc-sections -s -municode; MSVC path: /W4 /permissive- /O2 /Gy plus /MANIFEST:NO because resources.rc embeds the manifest. Libraries: gdiplus, comctl32, comdlg32, dwmapi, uxtheme, shlwapi, version, winhttp, ole32, oleaut32, shell32, advapi32, gdi32, user32, kernel32, bcrypt. Threads are deliberately Win32 (CreateThread, SRWLOCK) because mingw's win32 thread model lacks <thread>.

CI (.github/workflows/ci.yml): ubuntu-24.04 cross-build with a zero-warning gate and a DLL allow-list audit (rejects any import outside the 16 system DLLs), packaging with SHA256SUMS; windows-2022 MSVC build; release job on v* tags publishing exe, zip and sums.

Known rough edges

Audited and shipped as patches in this wiki's fix bundle: guard wakeup defect; Stop() handle-lifetime edge; store snapshot race; log retention timezone skew; silent invalid-arg exit; stale version strings; MIT copyright in the PE resource. Provenance: the v8.4.0 release binary cannot be reproduced from its tag. All detailed in [[Security and Privacy]], The Guard Watcher and Changelog and Versioning.


See also

Clone this wiki locally