Architecture and execution model

The persistent session and the CLI client, the control socket, the Whisper backend adapter and the text-injection cascade.

A persistent session, a client that talks to it

whispskrid started with no control argument launches a persistent session in the foreground: it loads the configuration, loads the Whisper model once, opens the capture device, starts the key listener and the control socket, then waits.

Key pressAudio captureWhisper transcriptionInjection cascadeControl socket
The path from voice to text: capture starts on press, stops on release, and the whole buffer goes to the backend adapter in a single pass. The control socket is not a step on this path — it is the side channel a desktop shortcut uses to drive the running session.

A CTranslate2 model takes on the order of a second to several seconds to load, depending on its size and the machine. Reloading it for every dictation would make push-to-talk unusable: that is the reason for the persistent process.

whispskrid started with a control argument (--dictate, --status, --stop…) acts as a client: it connects to the socket of the running session, sends a one-line command, prints the reply and exits with a return code reflecting OK or ERR. If there is no session, it says so and exits with an error.

Two paths to trigger a dictation

The same internal state of the persistent session is driven by a local key listener and by the control socket.

  • Local shortcut listener (pynput) — it watches the X server. pynput distinguishes press from release, so the “I speak as long as I hold” semantics are native on this path. Under Wayland it only sees windows going through XWayland, never a native Wayland window; it is a best-effort convenience. Default key: right Shift (shift_r), overridable through hotkeys.push_to_talk.
  • CLI sub-commands through the socket — a universal path, independent of the session type. A desktop shortcut does not carry “key held / key released”, so this path works as a toggle. --dictate starts capture, --dictate-stop stops it and injects, --toggle does one or the other depending on state, --cancel discards the current capture without injecting.

A capture started by the key can be cancelled with --cancel, and vice versa.

Duration safeguard

The only automatic safeguard is capture.max_seconds (default 300).

If the key stays held beyond that, capture stops on its own, what was recorded is transcribed and injected normally, and a warning is logged. This is not a convenience timer — it is protection against a stuck key or an audio buffer growing without end. Automatic silence cutoff (VAD) remains possible later, as an option; the vad.enabled key is reserved in the schema but is not implemented in v1.0.1.

The backend adapter

A backend/ module exposes a narrow interface — load, transcribe(audio, language), info — so a second engine can be added later without touching the rest.

faster-whisper (CTranslate2) is the only implementation shipped in v1.0.1; the backend.name key is reserved but accepts only that value. whisper.cpp is considered as a second backend, later.

The adapter instantiates WhisperModel with device (auto: CPU or CUDA), compute_type (auto: int8 on CPU, float16 on CUDA) and download_root pointed at the managed model directory. transcribe() is called with beam_size (default 5), the forced language or None, and no VAD filter; it concatenates the returned segments and trims the text.

Model management

Models live in a managed directory — /usr/share/whispskrid/whisper-models/ and ~/.local/share/whispskrid/whisper-models/ for a package install, whisper-models/ at the repository root for a source install.

Resolving a short name (base) walks these locations in order. The default Hugging Face cache of faster-whisper is only a last resort: the tool must start offline once the model is present. whispskrid --download-model [NAME] downloads the requested model into the user directory, checks that it loads, and exits; the package postinst offers this download on first run if no model is present.

Text injection: a clipboard paste, with an engine cascade

Once the text is transcribed and post-processed, the injection layer places it on the clipboard and simulates a paste. The engine is chosen once at startup.

PathToolsCondition
Firstydotool + wl-clipboardDriver-level keystrokes (/dev/uinput) through the ydotoold daemon; keycodes depend on the keyboard layout; requires membership of the input group.
Secondxdotool + xclipStandard path. Window-class detection (terminal paste combo) is only available here — ydotool has no notion of a targeted window.
Otherwisedegraded modeNo keystroke sent; the transcribed text stays in the terminal for the rest of the session.

The typing engine needs a clipboard tool alongside it: wl-clipboard under Wayland — required even on the ydotool fallback — and xclip under X11. The Debian package pulls wl-clipboard and recommends xclip; a source install adds them itself. Without it, the transcribed text never reaches the clipboard and the paste re-inserts whatever was last copied by hand.

The previous clipboard content is read back then restored after the paste (tunable through clipboard.restore, on by default). Non-text content — an image, files — is left intact. Under Wayland with a GTK application, the restore can run ahead of the window reading the clipboard; clipboard.defer_restore defers the restore to the end of the session so the paste never picks up the old content.

If no engine suited to the current session works, the tool does not crash and does not silently give up: it goes into degraded mode. The transcribed text keeps showing in the terminal and automatic typing is disabled for the rest of the session, rather than retried — unsuccessfully — on every dictation. Some windows also refuse the paste — certain Java applications, protected fields; on the first such case, the tool goes into degraded mode the same way.

The control socket

A running session opens a private Unix socket at $XDG_RUNTIME_DIR/whispskrid.sock (mode 0600). The same command called with --dictate, --dictate-stop, --toggle, --cancel, --status or --stop connects to that socket instead of starting a second session.

This is what makes desktop shortcuts work everywhere under Wayland, including for native Wayland windows that the pynput listener never sees. The protocol is lines of text, one command per line, reply prefixed OK or ERR; an unknown command replies ERR unknown-command. There is no sleep / wake: WhispSkrid has no sleep state.

Only one session runs per user at a time; a socket left behind by a crash is detected and replaced on the next startup.

Interface language and dictation language

These are two separate settings.

Interface language

The tool’s own strings go through the GNU gettext chain — .po catalogues compiled to .mo, extracted from the Python source. Four languages: English, French, German, Spanish.

Dictation language

-l fr forces the language passed to Whisper for that session; with no argument, default_language then Whisper’s autodetection. No per-language configuration beyond that choice.

Whisper punctuates and applies sentence capitals itself. WhispSkrid’s post-processing is limited to trimming edge whitespace and forcing a capital on the first letter of the injected text. No substitution table, no per-language rules.

See the project repository for the code, and Guides to get started.