06 COCKPIT // The QML interface SRC connector-app/qml/ — 23 files, 6,514 lines
06.A

Three pages, one deck

Main.qml

Dense on purpose

The whole application is a StackLayout with three children — page-computers, page-this-computer, page-settings — behind a single row of navigation buttons. There is no dashboard, no onboarding wizard, no empty state that fills a screen with an illustration. The first page you land on is the working surface, and it is full of readouts.

The design brief for this is written down. DESIGN.md asks for a console that is "technical, not sterile; cinematic, not flashy; ambient, not animated for its own sake; dense in information, but easy to scan," and specifies the palette down to individual hex values. Every colour in the drawing below is one of those values.

The three pages and their objectName / automation ids
PageobjectNameNav idComponent
Computerspage-computersnav-computersCommandDeck.qml — 805 lines
This Computerpage-this-computernav-this-computerCommandThisComputer.qml
Settingspage-settingsnav-settingsCommandSettings.qml — 557 lines

Two breakpoints are handled inside the Computers page itself: compactSidebar below 1320 px and stackedLayout below 920 px. The window will not shrink past 820 × 780, which is the point at which the three-column deck stops being legible.

06.B

Palette

DESIGN.md §2 · Main.qml
bg0#050608 abyss background
bg1#081015 deep ambient
panel#0d1214 base panel
border#2b312d default hairline
accentGreen#42e59a primary signal
accentCyan#6cd9ff telemetry
warmSignal#ffd58c restrained highlight
danger#ff7c7c destructive / error
Stated non-goals

From DESIGN.md §8: don't "turn the app into a neon gimmick", don't "use heavy blur, aggressive glow, or large continuous animations", don't "mix too many accent colors". The monospace font is scoped: "Monospace is for telemetry, network addresses, and secondary technical labels only."

06.C

The Computers deck

CommandDeck.qml — reconstructed from source

Three columns: a host rail, a selected-host schematic, and a live protocol panel. Every label, button caption and placeholder string below is taken verbatim from the QML; automation ids are annotated in the margin because they are the same strings the CLI and MCP layers use.

Connector Computers page interface A reconstruction of the Connector desktop window showing the top bar with page navigation and Tailscale status, a left device rail with search and filter buttons and five host rows, a central selected-host panel with specification rows and protocol status rows and a connect button, a right host-status panel with diagnostic tabs and status cards and shell access controls, and a bottom telemetry strip. CONNECTOR COMPUTERS // REMOTE SYSTEMS COMPUTERS THIS COMPUTER SETTINGS TAILSCALE ONLINE DEVICES 4 TAILNET HOST RAIL LIVE search host / ip ALL LAST READY STAR workstation 100.64.0.12 TAILSCALE · SSH READY READY linux-host 100.64.0.11 TAILSCALE · SSH READY READY zflow 100.64.0.13 TAILSCALE · SSH READY NEEDS HOST ubuntu-wdc 100.64.0.14 TAILSCALE OFFLINE ians-macbook 100.64.0.15 THIS DEVICE SELF + ADD DEVICE SELECTED HOST workstation ONLINE EDIT HOST SCHEMATIC SYSTEM linux online record ADDRESS 100.64.0.12 linux-host ROUTE Tailscale same-tailnet path SHELL Tailscale SSH [email protected] PROTOCOL STATUS TRUST TRUSTED Saved access record present. OK SSH SSH READY tailscale ssh [email protected] TS STREAM STREAM READY Desktop READY ROUTE TAILSCALE Same-tailnet path. LIVE TAGS TAILSCALE TAILSCALE SSH DESKTOP >>> CONNECT <<< SSH UNSTAR HOST STATUS workstation READY PROTOCOLS STREAM SESSION TAILSCALE / ROUTE TAILSCALE Same-tailnet path. LIVE HOST RUNTIME STREAM READY Ready to connect READY TRUST RECORD TRUSTED Saved access record present. OK SHELL CONTROL SSH READY Non-interactive shell verified. TS ADVANCED CONTROLS AVAILABLE Display + overlay control enabled. OK FIX HOST SHELL ACCESS SETUP Non-interactive shell automation verified. MODE TAILSCALE SSH USER ian TARGET tailscale ssh [email protected] TAILSCALE IP 100.64.0.12 DERP AUTO NAT DIRECT AUTOMATION IDS top: nav-computers · nav-this-computer · nav-settings rail: available-only-toggle · computer-search · rail-filter-{all|last|ready|stars} · add-computer centre: selected-device-rename-toggle · connect-selected-device · test-selected-device · selected-device-favorite right: diagnostic-tab-{protocols|stream|session} · pair-selected-device toggle-shell-setup · selected-device-ssh-mode · selected-device-ssh-user selected-device-shell-target
Figure 06.1 — the Computers page, reconstructed from CommandDeck.qml Drawn to the app's real aspect ratio (1500 × 940). The FIX HOST button is rendered disabled because its enabled binding requires !device.clientTrusted — on a trusted host it greys out. The CONNECT caption is conditional too: it reads >>> PREPARE & CONNECT <<< when the device is neither trusted nor on the Tailscale route.
06.D

In-session controls

Main.qml — session overlay window + menuBar

Once a stream is live the cockpit window is no longer the thing you are looking at. Connector puts a frameless, always-on-top overlay window next to the stream — and, on capable Linux hosts, a second copy of the same controls inside the remote desktop. The local overlay is only shown when the host overlay is not in use: visible: (sessionActive || sessionConnecting) && !sessionUsesHostOverlay.

Session overlay window and Session menu The frameless session overlay window with its host label, five action buttons, a display slot row and a hint line, shown beside the Session menu with its five keyboard-shortcut items and a Displays submenu, and beneath them the host-side GTK overlay variant. LOCAL OVERLAY — frameless, WindowStaysOnTop, transientParent = main window Connected to workstation Desktop RELEASE MOUSE STATS FULLSCREEN DISCONNECT DISPLAYS 1 · HDMI-0 2 · DP-2 3 · DP-4 Current display: HDMI-0 // Use RELEASE before clicking outside the stream // Display buttons switch monitors directly ids: session-release-mouse · session-toggle-mouse · session-toggle-stats · session-toggle-fullscreen · session-disconnect · session-overlay-display-slot-N SESSION MENU — enabled only while a session exists Release Mouse F8 Toggle Mouse Mode F9 Toggle Stats Overlay F10 Toggle Fullscreen F11 Disconnect F12 Displays HOST-SIDE OVERLAY — GTK 3 window drawn inside the streamed desktop, connector-host-overlay.py Connector controls IN-STREAM 1 · HDMI-0 2 · DP-2 LOCATE HIDE DISCONNECT drag handle · pointer guard · pid file · polls state.json, writes command.json CONNECTOR MENU Command Palette Connect Last Computer WHY BOTH SURFACES EXIST Once the stream captures the pointer, a local overlay is unreachable without releasing first — hence RELEASE, and hence the hint text. The host-side overlay solves the same problem from the other end: it is inside the captured image, so it needs no release at all. Connector picks one. When the host overlay is running, sessionUsesHostOverlay is true and the local overlay window stays hidden.
Figure 06.2 — the two in-session control surfaces The overlay is a real Window with Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint, registered for automation on completion so its buttons are addressable by the same ui.activate path as everything else. Activating it calls releaseSessionMouse() first — clicking the overlay while the stream holds the pointer would otherwise do nothing.
06.E

This Computer

CommandThisComputer.qml

The hosting side of the app. Four status tiles across the top — TAILNET, HOST, SETUP, AUTO — then a setup ladder, then the trust controls.

Host readiness ladder

Steps are generated in localHostSetupSteps() with one of four states: complete, current, warning, blocked. Each carries a title, an explanation, and optionally an action id and a button caption.

Tailscale rung — the four mutually exclusive outcomes
ConditionTitleStateAction
CLI not foundInstall TailscaleblockedDOWNLOAD
not signed inSign Into TailscalecurrentOPEN TAILSCALE
signed in, offlineBring Tailscale OnlinewarningOPEN TAILSCALE
onlineTailscale Readycomplete

The hosting rung follows the same shape: Host Runtime (blocked, platform unsupported), Install Host Runtime, Turn On Hosting with a TURN ON action, Wait For Host Runtime. On Linux the sign-in copy even branches on whether an AuthURL is present, suggesting either the login page or sudo tailscale up.

Trust controls

  • FALLBACK TRUSTpair-client-name, pair-pin, approve-pairing
  • LOCAL HOST LOGINsunshine-username, sunshine-password, save-sunshine-credentials, clear-sunshine-credentials
  • TRUSTED CLIENTS — one row per record, each with revoke-client-<uuid>
06.F

Settings

CommandSettings.qml

Six stream controls and an automation-socket readout. Everything here maps directly onto the settings.get / settings.set RPC pair, which is why the MCP tool connector_set_settings takes exactly these six optional arguments.

STREAM CONTROL SURFACE — automation id to settings key
ControlautomationIdSettings key
QUALITYsettings-quality-presetquality_preset
WINDOW MODEsettings-display-modedisplay_mode
LINUX DESKTOP MODEsettings-desktop-session-modedesktop_session_mode
ABSOLUTE MOUSEsettings-absolute-mouseabsolute_mouse
MIC PASSTHROUGHsettings-microphone-passthroughmicrophone_passthrough_enabled
PERFORMANCE OVERLAYsettings-performance-overlayperformance_overlay

Three of these have real consequences elsewhere on this site. desktop_session_mode chooses between physical and the dedicated-workspace modes described on the host runtime page. absolute_mouse is the preference that the mouse routing contract can override. And microphone_passthrough_enabled is what puts --microphone-passthrough 1 on the connectorstream command line and provisions the PipeWire graph on the host.

AUTOMATION SOCKET

The Settings page shows the live control socket path and its running state, sourced from ConnectorControlServer::statusChanged(running, socketName, message). It is the one place in the UI that admits the socket exists.

06.G

Every control is addressable

automationId → ui.activate / ui.set_value

QML components in this project accept an automationId property and mirror it into objectName. That single convention is what makes the GUI scriptable, and it is applied consistently enough that the UI tree doubles as an API reference.

connectorctl — walking the live QML tree
$ build/connector-app/connectorctl ui tree | head -40
{
    "nodes": [
        {
            "id": "connector-window",
            "type": "ApplicationWindow",
            "children": [
                { "id": "nav-computers",      "type": "SidebarButton", "text": "COMPUTERS" },
                { "id": "nav-this-computer",  "type": "SidebarButton", "text": "THIS COMPUTER" },
                { "id": "nav-settings",       "type": "SidebarButton", "text": "SETTINGS" },
                {
                    "id": "page-computers",
                    "children": [
                        { "id": "computer-search",           "type": "TextField" },
                        { "id": "available-only-toggle",     "type": "CheckBox"  },
                        { "id": "rail-filter-all",          "type": "DeckButton" },
                        { "id": "rail-filter-ready",        "type": "DeckButton" },
                        { "id": "connect-selected-device",   "type": "DeckButton" },
                        { "id": "pair-selected-device",     "type": "DeckButton" },
                        { "id": "selected-device-ssh-mode",  "type": "HudComboBox" }
                    ]
                }
            ]
        }
    ]
}

# the same tree is what an agent reads through connector://ui/tree
$ build/connector-app/connectorctl ui activate rail-filter-ready
{
    "activated": true,
    "id": "rail-filter-ready"
}

An interface that a script can read is an interface that a test can assert on. The integration suite drives this application entirely through the same ids a human clicks.

scripts/test_connector_automation.py, registered as the connector_automation ctest suite
Scope of the drawings above

Figures 06.1 and 06.2 are hand-drawn reconstructions from the QML source, not screenshots. Panel titles, button captions, placeholder text, status strings, colours and layout proportions are taken from the code; the device names and addresses shown are the resolver hints committed in scripts/connector-mcp.py.

06.H

Continue