CommTool is a desktop, event-driven communications workbench. main_window.py is
still the composition root for the existing UI, but new behavior should live in the
domain package that owns it and remain Qt-free whenever practical.
| Area | Package | Boundary |
|---|---|---|
| transports | transport/ |
serial, SEGGER J-Link RTT, TCP/UDP, BLE, Virtual; emits bytes/events |
| protocol | protocol/ |
framing, parsing, checksums, reusable frame templates |
| automation | automation/ |
replies, triggers, sequences, scripts, action safety |
| record | record/ |
.ctrec, replay, PCAP export, local session catalog |
| project | project/ |
.ctproj, presets, operator-panel and device resources |
| sessions | sessions/ |
per-tab connection, buffers, engines, ownership |
| UI | ui/ |
Qt widgets/dialogs and presentation adapters only |
- Put normalization, validation, indexing, serialization, and policy in a Qt-free module with direct unit tests.
- Keep payload ownership in its session. Window-level state is only for resources deliberately shared by a workspace/project.
- External actions are deny-by-default and must have bounded concurrency, timeout, import gates, and explicit unsafe overrides.
- Persist project resources explicitly; retain their old QSettings mapping until a format migration has shipped and been exercised.
- Do not add another large feature directly to
main_window.py; add a thin adapter or mixin after the domain API is stable.
- Supported and tested source runtime: Python 3.11–3.13.
- Compatibility CI: Python 3.11 and 3.13; primary Windows tests: Python 3.12.
- Runtime dependencies are bounded in
requirements.txtand reproduced in CI/release builds throughconstraints-runtime.txt. - RTT uses
pylink-square; the Python package is bundled, while the vendor J-Link driver remains an explicit host prerequisite. DLL access is process-serialized. - Ruff, compileall, pure-logic tests, platform smoke suites, full Windows tests, and nightly soak/disconnect churn form the delivery gate.
The current release line uses PyQt5/Qt5. New domain modules intentionally avoid Qt,
which limits a future Qt6 migration to UI, signals, packaging, and compatibility
tests. The migration should be a separate release track: introduce one qt_compat
surface, move imports package-by-package, validate high-DPI/tray/network behavior on
all three platforms, then switch packaging. Mixing that migration into protocol or
transport feature work would make regression attribution unreliable.
ui/sidebar_dock.py owns pin/unpin, hover delay, flyout animation and dismissal.
The flyout belongs to the terminal workspace row, and is explicitly hidden when
that workspace hides. Its logical visibility is checked relative to the row so
ancestor hiding cannot bypass cleanup. main_window.py persists the auto-hide
preference and the two-widget splitter state captured before undocking.
User-facing popups go through the themed dialog set only: InfoDialog
(ui/dialogs.py) for info/error, CommTool._confirm_dlg for two-way
confirmation, and CommTool._build_themed_text_input_dialog for single-line
prompts. Native QMessageBox and QInputDialog are no longer called anywhere
in src/. The pre-window "max sessions" notice in main.py builds an
InfoDialog directly and reads language/theme from the profile ini because no
main window exists yet. The send box sets setAcceptRichText(False) so paste
and drag-drop insert plain text; all other editable text widgets are
QPlainTextEdit / QLineEdit, which never accept rich text.
CommTool.__init__ applies the global stylesheet once, using the theme saved in
the profile ini (cb_theme is only restored later by _load_settings).
apply_style remembers the last stylesheet it set and skips setStyleSheet plus
the full polish_widget_tree pass when the stylesheet is unchanged, so the
deferred _on_theme_changed after the event loop starts only refreshes the
inline-styled pieces. A real runtime theme switch still re-polishes the tree.
The QApplication-wide eventFilter returns early unless the event type is in
_ef_types (the types it actually dispatches on; mouse move/release only when
Linux manual resizing is active). New event types handled there must be added
to that set.
The J-Link RTT catalog (RttCatalog, thousands of DLL calls that hold the GIL)
is held back while _rtt_catalog_hold is set: from construction until the main
window's first paintEvent (plus one event-loop turn), with a 2 s fallback for a
window that is never shown. Requests during the hold only set
_rtt_catalog_wanted; _release_rtt_catalog_hold enumerates only if the
connection type is still RTT. The device picker and driver-folder reload clear
the hold and enumerate immediately.