Skip to main content

Architecture

Threading model

The core design goal is that the UI never blocks, even while psutil enumerates thousands of processes or a scan walks the disk.

Main thread UI, Qt event loop, model updates
└─ Worker QThread blocks on the collector future
└─ Subprocess psutil runs here (separate GIL)
└─ QThreadPool on-demand scanners (disk / appdata / registry / links)
  • Process and connection collection runs in a separate subprocess (ProcessPoolExecutor), so psutil's work never touches the main-thread GIL and the UI stays responsive under load.
  • A worker QThread drives that subprocess on a timer and hands results back to the GUI via queued signals (never by calling into the worker directly).
  • Each on-demand scanner is a cancellable job on the global QThreadPool, reporting progress and streaming results back to its tab.

Why it stays smooth

  • Diff-based model updates. The process model diffs incoming data against the current rows and emits granular insert/remove/change signals instead of a full reset — no scroll jump, minimal repaint.
  • Repaint coalescing. Model update and re-sort happen inside setUpdatesEnabled(False), so the view repaints once per tick, not once per signal.
  • Hidden-tab buffering. The connections model is only fed while its tab is visible; otherwise the latest snapshot is buffered and flushed on tab switch.
  • Numeric-correct sorting. Sort values are compared in Python so byte sizes above 2³¹ order correctly (Qt's default QVariant comparison mis-orders them).

Module layout

main.py entry point — QApplication + MainWindow
build.ps1 standalone-exe build script (pyside6-deploy)
pysidedeploy.spec Nuitka / deploy configuration
procman/
window.py MainWindow — tabs, menus, worker wiring, kill handling
collector.py background QThread driving the collection subprocess
data.py pure-psutil collection (no Qt — runs in the subprocess)
proc_tab.py Processes tab + per-process connection detail
proc_model.py process table model (diff updates, numeric sort)
port_tab.py All Connections tab
port_model.py connection table model
scan_tab.py shared base for the read-only scan tabs
scan_model.py declarative table model for scans (ColumnSpec)
disk_tab.py / appdata_tab.py / registry_tab.py / links_tab.py / threat_tab.py
*_data.py pure data collectors for each scanner (no Qt)
registry_explorer.py read-only application-registry browser
theme.py + styles/app.qss themeable QSS (Dark / High Contrast / Lilac)
links.py canonical repository & documentation URLs
settings.py + settings_dialog.py persisted preferences
version.py version / commit / build-date provenance
mcp_tab.py MCP tab — server controls, client setup, approvals queue
mcp_server.py FastMCP app, bearer-token middleware, uvicorn manager
mcp_data.py pure-psutil read-only views for the MCP tools (no Qt)
mcp_approvals.py thread-safe human-in-the-loop approval broker (mocked exec)
mcp_instructions.py per-client (Claude Code / Copilot / Codex) setup snippets
tests/ stdlib unittest suite

A recurring pattern: UI (*_tab.py) is separate from data (*_data.py / data.py). The data collectors are pure Python with no Qt imports, which is what lets them run in a subprocess or thread pool — and makes them straightforward to unit-test.