Daemon & Client Model
PolyKybdHost is built around a daemon + client split. Understanding it helps explain why the GUI can be closed and reopened without ever disturbing the keyboard.
The two parts
Section titled “The two parts”- The core (daemon). A Qt-free operational core owns the HID device, the reconnect logic, the active-window tracking, overlay/icon/keymap sends, adaptive brightness, and the recently-used overlay cache. It runs as a background process that keeps the keyboard connection alive on its own.
- The GUI (client). The system-tray application — menus, dialogs, the layout editor, the log viewer — attaches to the core as a client over a local control socket. It renders state the core publishes and forwards user actions back to it. It holds no device state of its own.
A plain python -m polyhost launch starts the GUI and, if no daemon is running, spawns one (detached) and connects to it. If the daemon cannot start, it falls back to running the core in-process.
Why it exists
Section titled “Why it exists”Running the core as a daemon means:
- The keyboard connection and its reconnect/debounce state survive GUI restarts.
- Multiple front-ends — the tray GUI and the
polyctlCLI — talk to the same live core. - A headless machine (a server, a CI rig) can run the core with no display at all.
The control socket
Section titled “The control socket”The GUI and polyctl reach the daemon over a local control socket:
- A Unix domain socket on Linux/macOS, or a named pipe on Windows.
- Authenticated with a key file stored with
0600permissions in the platform’s user config directory. - It also acts as the single-instance lock — only one core can own it at a time.
The wire protocol is plain JSON-RPC framed over that socket; the CLI uses the exact same transport the GUI does.
Startup modes
Section titled “Startup modes”| Mode | Flag | What runs |
|---|---|---|
| Daemon + GUI | (default) | Spawns/attaches the daemon, runs the tray GUI as its client |
| Client only | --connect[=ENDPOINT] |
Tray GUI as a pure client of an already-running daemon |
| Headless | --headless |
The core + control socket only, no Qt imported |
| In-process | --no-daemon |
Legacy single-process startup (the core runs inside the GUI) |
In headless mode the core auto-applies its own reconnect snapshots and is driven entirely through polyctl. The GUI can attach to a headless daemon at any time with --connect.
Logging
Section titled “Logging”The daemon writes its own log to daemon_log.txt, which the Log Viewer shows under a Daemon Log tab. Start the daemon with --dev 1 for debug lines or --dev 2 for verbose ones, such as window-report receipts. It is the same --dev flag the GUI takes.