quickshell
Recipe card from the charly-check plugin (Commands — runtime CLI verbs).
QUICKSHELL — IPC against a Quickshell desktop shell
Section titled “QUICKSHELL — IPC against a Quickshell desktop shell”Overview
Section titled “Overview”The quickshell: check verb drives the control channel of a
Quickshell desktop shell — the IPC surface a
desktop’s panels, menus, notifications and overlays are summoned and dismissed
through. It is NOT a host charly subcommand: it is a declarative check verb
served out-of-process by its plugin, parallel to wl:, cdp: and dbus:.
It is desktop-agnostic. The verb speaks Quickshell, not omarchy: config: names
the shell’s config directory and target:/function: name whatever that shell’s QML
exposes through an IpcHandler.
Why this is a verb and not a command: step
Section titled “Why this is a verb and not a command: step”This is the whole justification, and it is worth understanding before authoring:
qs ipc reports failures on STDOUT and exits ZERO.
$ qs ipc -p /usr/share/omarchy/shell call -- shell nosuchfunction; echo "exit=$?"Function not found.exit=0So a command: step asserting “the menu opened” PASSES against a shell with no menu
plugin loaded, against a typo in the target, and against an IPC surface that changed
underneath it. The step is green and asserts nothing.
quickshell: reads those responses and fails on them. The four it keys on —
Target not found., Function not found., and the two argument-count errors — are
strings in the quickshell binary itself, not omarchy’s wrapper, which is what
keeps the guard desktop-agnostic.
Methods
Section titled “Methods”| Method | Purpose |
|---|---|
quickshell: ping |
liveness — calls the shell’s conventional shell.ping |
quickshell: call |
the generic primitive — invoke function on target |
ping is a convenience over call with overridable defaults; everything a desktop
exposes is reachable through call.
Fields
Section titled “Fields”| Field | Meaning |
|---|---|
config: |
required — the Quickshell config DIRECTORY (the folder holding shell.qml) |
target: |
the IpcHandler target name; defaults to shell for ping |
function: |
the function to invoke; required for call |
args: |
positional arguments; a JSON payload is just a string argument |
wayland_display: |
override display discovery (see below) |
config: is required because qs matches instances BY CONFIG PATH, and a step
arriving from outside the session has no ambient one.
Authoring
Section titled “Authoring”# liveness — the scalar shorthand needs the fields, so use the map form- check: the shell answers a ping context: [runtime] eventually: 60s quickshell: method: ping config: /usr/share/omarchy/shell
# every core plugin is loaded, asserted in ONE step- check: the core shell plugins are loaded context: [runtime] quickshell: method: call config: /usr/share/omarchy/shell target: shell function: listPlugins stdout: - contains: omarchy.bar - contains: omarchy.menu
# summon a surface. EVERY argument the QML function declares is REQUIRED —# omarchy's is summon(pluginId, payloadJson), so a panel that needs no payload# still passes one. Pass '{}', not nothing.- check: the root menu opens context: [runtime] stdout: - matches: '^ok\\s*$' quickshell: method: call config: /usr/share/omarchy/shell target: shell function: summon args: ['omarchy.menu', '{"menu":"root"}']
# a panel with no payload of its own — still two arguments- check: the weather panel opens context: [runtime] stdout: - matches: '^ok\\s*$' quickshell: method: call config: /usr/share/omarchy/shell target: shell function: summon args: ['omarchy.weather', '{}']Two exit-zero traps, one inside the other
Section titled “Two exit-zero traps, one inside the other”The verb closes the FIRST: qs ipc reports IPC-level failures on stdout while
exiting zero, so this plugin classifies “Target not found.”, “Function not
found.” and the argument-count errors as failures. Passing one argument to a
two-argument function is caught here:
quickshell: shell.summon: Too few arguments provided (2 required but 1 were provided.)The verb CANNOT close the second, because it is the shell’s own contract rather
than Quickshell’s. omarchy’s summon returns the STRING “ok” or “unknown” and
exits zero either way — measured on a live guest:
$ qs ipc -n -p /usr/share/omarchy/shell call -- shell summon totally.bogus.plugin '{}'unknownexit=0$ qs ipc -n -p /usr/share/omarchy/shell call -- shell summon omarchy.weather '{}'okexit=0So exit status proves only that the round-trip happened — NOT that anything
opened. A summon step without an stdout: assertion passes against a shell that
summoned nothing, which is the failure-open shape this verb exists to remove.
Assert the ANSWER on every summon step. Read the target’s QML for the function’s
real return before trusting a bare exit code: hide(id) returns void and needs
no assertion, summon and call return strings that do.
The display, and why steps over SSH need it
Section titled “The display, and why steps over SSH need it”qs matches instances by WAYLAND_DISPLAY, and a check step arriving over SSH has
none. The verb recovers it from XDG_RUNTIME_DIR automatically — without that it
would fail on exactly the guests it is most useful for. Set wayland_display: only
to override that discovery.
Pairing it with wl:
Section titled “Pairing it with wl:”quickshell: drives the shell; wl: observes the compositor. They answer different
questions and the strong assertions use both:
- check: the menu opens # quickshell: DRIVE it quickshell: {method: call, config: …, target: shell, function: summon, args: ['omarchy.menu']}- check: its surface is mapped # wl: OBSERVE the result wl: hypr-layers stdout: [{contains: omarchy-menu}]- check: and it actually rendered # wl: + OCR: prove it DREW wl: {method: screenshot, artifact: /tmp/m.png, artifact_contains_text: Apps}A shell can accept an IPC call and render nothing; a surface can map and draw
nothing. See /charly-check:wl for the layer and OCR halves.