Four programs, no installer
The agent that isn't installed
Connector controls remote Linux hosts without deploying anything to them. The four Python helpers are
compiled into the client binary as Qt resources, base64-encoded at call time, and piped through an SSH heredoc into a
six-line bootstrap that writes them to ~/.cache/connector-host-runtime/ and executes them. There is no
package, no daemon to install, no version to keep in sync — the helper on the host is always exactly the helper in the
client you are running.
| Helper | Lines | Interface | Responsibility |
|---|---|---|---|
connector-linux-hostctl.py | 1,146 | state | start | restart | stop | mic-start | mic-stop | approve-pair |
systemd supervision of Sunshine and the mic service, environment repair, pair approval |
connector-x11-workspace.py | 901 | apply | update | restore, --runtime-dir |
dedicated Xvfb workspace with its own desktop and its own Sunshine instance |
connector-host-overlay.py | 544 | --runtime-dir |
GTK control bar drawn inside the streamed desktop |
connector-linux-michost.py | 313 | serve |
UDP receiver that injects client microphone audio into PipeWire |
1CONNECTOR_HOSTCTL_ACTION='state' CONNECTOR_HOSTCTL_ARGS_B64='W10=' \ 2CONNECTOR_HOSTCTL_SCRIPT_B64='…' CONNECTOR_MICHOST_SCRIPT_B64='…' \ 3CONNECTOR_MIC_PORT='48200' python3 - <<'PY' 4import base64, json, os, pathlib, subprocess, sys 5runtime_dir = pathlib.Path.home() / ".cache" / "connector-host-runtime" 6runtime_dir.mkdir(parents=True, exist_ok=True) 7script_path = runtime_dir / "linux-hostctl.py" 8script_path.write_bytes(base64.b64decode(os.environ["CONNECTOR_HOSTCTL_SCRIPT_B64"])) 9args = json.loads(base64.b64decode(os.environ.get("CONNECTOR_HOSTCTL_ARGS_B64", "W10=")).decode("utf-8")) 10completed = subprocess.run([sys.executable, str(script_path), os.environ["CONNECTOR_HOSTCTL_ACTION"], *args], check=False) 11raise SystemExit(completed.returncode) 12PY
W10= is the encoding of []. That removes an entire class of quoting bug from a code path
that carries user-supplied device names and PINs.Finding the session
An SSH login has no DISPLAY. Every helper therefore begins by walking /proc to steal the
environment of a real desktop process owned by the same uid.
Keys harvested
DISPLAY · WAYLAND_DISPLAY · XDG_RUNTIME_DIR · DBUS_SESSION_BUS_ADDRESS · SWAYSOCK · HYPRLAND_INSTANCE_SIGNATURE · XAUTHORITY · XDG_SESSION_TYPE · XDG_CURRENT_DESKTOP
Processes considered
gnome-shell · gnome-session · plasmashell · startplasma · kwin · xfce4-session · cinnamon · mate-session · sway · hyprland · xorg · xwayland
Candidates are ranked by display_rank(), which prefers a low, real display
number over a scratch one — so a helper never accidentally adopts the environment of a previous Connector workspace
running on :98.
Sunshine started from an SSH session without these variables will launch, bind its ports, and capture nothing.
The connector-env.conf systemd drop-in exists precisely to write the harvested values back into the
unit so the next start is correct even without Connector present.
Runtime supervision and its fallbacks
hostctl start is not a wrapper around systemctl start. It repairs the unit first, then
starts it, then verifies, then falls back to a direct spawn if systemd could not produce a responding runtime.
collect_state() snapshot, so the client always receives a
complete picture regardless of which path succeeded. ok is not "the command ran" — it is
sunshine_start_ready(state), which requires the HTTP port to answer and exactly one runtime
process to exist.
ian@linux-host:~$ python3 ~/.cache/connector-host-runtime/linux-hostctl.py state { "ok": true, "command": "state", "session_env": { "DISPLAY": ":1", "XDG_SESSION_TYPE": "x11", "XDG_CURRENT_DESKTOP": "ubuntu:GNOME", "XDG_RUNTIME_DIR": "/run/user/1000", "DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus", "XAUTHORITY": "/run/user/1000/.mutter-Xwaylandauth.XXXXXX" }, "environment_ready": true, "unit_file_state": "enabled", "active_state": "active", "sub_state": "running", "main_pid": 4471, "process_count": 1, "duplicate_processes": false, "runtime_responding": true, "admin_responding": true, "http_port": 47989, "admin_port": 47990, "mic_port": 48200, "mic_active_state": "active", "mic_virtual_source_ready": true, "mic_ready": true, "mic_default_source": "connector_virtual_mic", "mic_default_source_is_connector": true, "pipewire_pulse_active_state": "active", "pactl_available": true, "pactl_usable": true, "wpctl_available": true, "pw_cli_available": true }
admin_port is not a separate constant — it is
read_config_port() + 1, where read_config_port() parses port out of the
Sunshine configuration file and defaults to 47989.
Display switching
Pressing a display button in the session overlay does not change what the client requests — it changes what the host captures. Connector treats the host as authoritative and refuses to fake a switch it could not confirm.
m_sessionManager->switchDisplay() path still exists, but it is only used when no
authoritative attempt was made at all — that is, when the host has no shell automation. The moment Connector can
reach the host's shell, the host decides.
Dedicated X11 workspaces
Streaming a host's physical desktop means taking it over: whoever is sitting at that machine sees your mouse. The
workspace helper builds a second, headless desktop sized to your client, runs its own Sunshine against it, and leaves
the physical session untouched. connector-x11-workspace.py apply --runtime-dir <dir> is the entry
point; update re-applies a changed display selection and restore tears everything back down.
:98–:120 is scanned rather than assumed, and sibling workspaces left
behind by a previous crashed session are cleaned up before a new one is built. Everything needed to undo the
operation is written to meta.json before the operation happens.
"Client-matched virtual display active. Use DISCONNECT to end the session."
The in-stream overlay
The controls you need most — disconnect, switch monitor — are hardest to reach while a stream has your pointer captured. Connector's answer is to draw the control bar on the host, inside the picture you are already looking at. It is a GTK 3 window that talks to the client through two files in the runtime directory.
slot numbers, and which display is current. The overlay polls it, hashes it, and rebuilds its buttons only when the hash changes.{"action":"switch_display","display_value":1} or {"action":"disconnect"}. The client picks these up and executes them through the normal session path.Fixed controls
- LOCATE — warps the pointer onto the overlay and re-runs the sequence at 120 ms, 360 ms and 820 ms, because compositors move windows after they are mapped.
- HIDE — collapses the bar without killing the process.
- DISCONNECT — writes the disconnect command.
- One button per display, labelled by slot, with the active one styled
overlay-display.active.
The overlay also carries a drag handle, a pointer guard that keeps the cursor on the current display, and a pid file it cleans up on exit. Its title defaults to "Connector controls" with the subtitle "IN-STREAM".
If the overlay cannot be launched, Connector says so rather than silently losing a control surface: "Connector could not launch the in-stream host controls, so the local Session menu stays as fallback."
Microphone bridge — CMIC v1
Moonlight streams audio from host to client. It does not stream a microphone the other way. Connector adds a small, fully specified one-way UDP protocol to close that gap on Linux hosts.
connectormicrophonepacket.h and the
decoder in connector-linux-michost.py. The header layout is asserted from both sides, and the packet
builder has its own ctest suite (connector_microphone_packet).