Architecture

Client-daemon model

deskctl uses a client-daemon architecture over Unix sockets. The daemon starts automatically on the first command and keeps the X11 connection alive so repeated calls skip the connection setup overhead.

Each command opens a new connection to the daemon, sends a single NDJSON request, reads one NDJSON response, and exits.

Wire protocol

Requests and responses are newline-delimited JSON (NDJSON) over a Unix socket.

Request:

{ "id": "r123456", "action": "snapshot", "annotate": true }

Response:

{"success": true, "data": {"screenshot": "/tmp/deskctl-1234567890.png", "windows": [...]}}

Error responses include an error field:

{ "success": false, "error": "window not found: @w99" }

Socket location

The daemon socket is resolved in this order:

  1. --socket flag (highest priority)
  2. $DESKCTL_SOCKET_DIR/{session}.sock
  3. $XDG_RUNTIME_DIR/deskctl/{session}.sock
  4. ~/.deskctl/{session}.sock

PID files are stored alongside the socket.

Sessions

Multiple isolated daemon instances can run simultaneously using the --session flag:

deskctl --session workspace1 snapshot
deskctl --session workspace2 snapshot

Each session has its own socket, PID file, and window ref map.

Backend design

The core is built around a DesktopBackend trait. The current implementation uses x11rb for X11 protocol operations and enigo for input simulation.

The trait-based design means adding Wayland support is a single trait implementation with no changes to the core, CLI, or daemon code.

X11 integration

Window detection uses EWMH properties:

PropertyPurpose
_NET_CLIENT_LIST_STACKINGWindow stacking order
_NET_ACTIVE_WINDOWCurrently focused window
_NET_WM_NAMEWindow title (UTF-8)
_NET_WM_STATE_HIDDENMinimized state
_NET_CLOSE_WINDOWGraceful close
WM_CLASSApplication class/name

Falls back to XQueryTree if _NET_CLIENT_LIST_STACKING is unavailable.