* Restart fprintd after resume to clear a claim wedged by suspend A fingerprint verify still open when the machine suspends leaves fprintd unable to hand the reader back: the verify dies with "Cannot run while suspended" and the follow-up ReleaseDevice fails on the still-busy device. The wedged claim then rejects every lock-screen attempt after resume until fprintd exits on its own 30-second idle timer -- and the retry loop keeps it from ever reaching that timer, so the reader stays dead until the user gives up and types a password. Install a system-sleep hook that restarts fprintd on resume, dropping the claim so the reader answers on the first touch. It is installed by omarchy-setup-security-fingerprint and removed by its teardown, so it is present exactly when a fingerprint reader is configured. try-restart is a no-op when fprintd is not running, so a healthy resume pays nothing. Approach suggested in #7229 and measured by @paracycle: 45 stray PAM sessions after resume down to 2. * Pace fingerprint retries and show when the reader is unavailable The lock screen retried fingerprint auth on a flat 250ms timer with no sign to the user, so a reader it could not reach -- a claim wedged across suspend, one held by another client, or a sensor gone from the bus -- spun PAM sessions at four per second behind an icon still inviting touches that could never unlock. Pace and report on one signal: whether an attempt reached the reader at all. pam_fprintd relays a finger prompt only once the claim lands, so an attempt that ends without prompting never reached the device. Those advance a streak that backs the retry off exponentially (to a ceiling above fprintd's 30s idle exit) and, past a few in a row, crosses out the icon and shows a "Fingerprint reader unavailable" notice. An attempt that did prompt proves the reader works -- a finger that merely did not match still reaches it -- so it clears the streak and the loop stays responsive. User presence (a keypress or touch) collapses a backed-off wait to a prompt retry, rate-limited so a moving cursor cannot respin the storm. An attempt that never reaches the reader within a few seconds is aborted and settled as unreached, so a claim orphaned by the resume restart surfaces the notice and retries a fresh daemon rather than hanging silently. The pacing, streak, nudge, and reach-timeout logic live in FingerprintModel.js with Node coverage; the new Text elements declare textFormat; lock status reports fingerprintUnavailable. The attempt state machine tracks the open attempt with fingerprintAuthenticating alone; the first settle closes it, and one PAM attempt raising both onError and onCompleted still folds into the streak exactly once. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Install the fprintd resume hook root-owned and keep it with the PAM file cp -p carried the checkout's owner and mode into /usr/lib/systemd/system-sleep/, so under dev-link the root-executed hook was user-owned, and a tree whose exec bit had been stripped installed a hook that systemd-sleep silently never ran. Use install -Dm755 -o root -g root, as the migration that installs the same file already does. The hook also belongs exactly where the fingerprint PAM file does: omarchy-apply-lock creates and removes /etc/pam.d/omarchy-lock-fingerprint on its own, and any apply-lock run after enrollment left PAM without the hook while its removal branch left a hook behind without PAM. Have apply-lock install and remove the hook together with the PAM file, and teach apply-lock-test.sh to redirect the hook into its scratch tree and assert the hardened run lands it beside the PAM fixtures. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Only treat fingerprint as configured when a print is enrolled The lock screen and omarchy-apply-lock decided fingerprint was set up with fprintd-list | grep -qi finger, which also matches "has no fingers enrolled" and "ListEnrolledFingers failed". A second account on a machine where one user enrolled, or anyone who ran fprintd-delete, was therefore handed the fingerprint loop: every attempt bailed before the claim, and with the new pacing that showed up as a crossed icon and "Fingerprint reader unavailable" for a reader the account simply has no print on. Match the per-print " - #N:" lines instead. apply-lock-test.sh follows: its fprintd-list stubs answer with a real enrolled-print row and its helper patcher matches the new probe line. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Exercise the migration's default hook source in its test Every case overrode OMARCHY_FPRINTD_RESUME_SRC, so the path the migration really reads from was never checked, while its -f guard turns a missing source into a clean exit and a permanent per-user marker. Add a case that runs against the shipped hook under the repo, and adopt set -euo pipefail like the sibling tests. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Take the fprintd restart off the thaw and bound its stop timeout The resume hook ran systemctl try-restart synchronously while user sessions were still frozen, so its cost landed on the wake path: half a second when fprintd answers SIGTERM, but a wedged fprintd on a stale device handle (the reader re-enumerated across the sleep) does not, and then the desktop stayed frozen for the whole stop timeout -- precisely in the case the hook exists for. Enqueue the restart with --no-block instead, as the unmount-fuse hook already does for the same reason, and ship a drop-in capping fprintd's TimeoutStopSec at 3s so the restart lands within seconds either way. The drop-in is numbered 10-stop-timeout.conf, as the other Omarchy system drop-ins are, so an administrator's override.conf sorts after it and wins. It is installed and removed wherever the hook is (setup, teardown, apply-lock, migration), and apply-lock-test.sh redirects it into its scratch tree alongside the hook. The hook's comments now say what actually happens on a locked resume -- Omarchy locks before every suspend and the lock screen opens a verify at once, so the restart is real, not a no-op -- and name the upstream defects this works around, fprintd#173 and fprintd#216, so the hook and the drop-in can be retired when upstream fixes them. Measured by MaxMad75 on an X390 Yoga (S3): 2 of 10 fprintd stops rode out the timeout to SIGKILL; the 3s cap verified with systemctl show. Co-authored-by: Omabot <omabot@omarchy.org> Co-authored-by: MaxMad75 <44462964+MaxMad75@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex XHigh <noreply@openai.com> Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Close the status-check and start-failure exits through settle Two paths left the fingerprint loop stuck or misreporting. A mid-lock status check that found fingerprint unconfigured aborted the PAM context directly; abort() delivers no signal, so fingerprintAuthenticating stayed true and every later attempt and nudge returned on it until the password unlock. And a fingerprintPam.start() that fails synchronously means the PAM file is gone -- a configuration problem, not a reader miss -- yet it fed the reader streak and reported "Fingerprint reader unavailable". Route the abort through settleFingerprintAttempt like the reach timeout does, drop the pending retry with it, and on a start failure re-check the configuration so the icon disappears instead; a pending retry owns the next attempt when a status check comes back configured. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Pace fingerprint nudges by the pending tier and the cap's idle stretch The nudge cooldown was a flat 2s, shorter than every backoff step, so a user moving the mouse at a wedged reader collapsed each wait to 2s -- thirty claims a minute against the cap's 1.5 -- and each claim re-armed fprintd's 30s idle timer, so the hook-less recovery the cap exists for never happened while anyone was present. Grow the cooldown with the pending wait, so presence collapses each backed-off wait once and repeat nudges are paced by the tier. At the cap the wait itself is the cure -- it is what lets fprintd idle out and drop a wedged claim -- so there the idle stretch is measured from the last settle, not the last nudge: a nudged attempt that hung until the reach timeout would otherwise eat most of the window, and under continuous input fprintd would never be left alone long enough to exit. Wall-clock steps are handled in both directions: a clock stepped back past the last nudge does not hold a fresh nudge back, and one stepped back past the last settle counts as no idle time at the cap rather than as enough. The retry test drives continuous input against attempts that hang to the reach bound and checks the gap fprintd is left. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Give a slow fingerprint claim time to land before aborting it The reach bound aborted any attempt that had not prompted within 5s by SIGKILLing the PAM child mid-Claim. A reader whose device open takes longer than that (out-of-tree drivers, and any reader right after the resume hook forces a re-open) could then never prompt: each kill left fprintd tearing the claim down until the open finished, the 1s retry hit "already claimed", and three misses later the reader was reported unavailable for good. Raise the bound to 20s, under GDBus's 25s Claim timeout and pam_fprintd's 30s verify timeout (whose "Verification timed out" is a non-error message that would read as reached), and name the hazard the bound actually covers: a daemon restarted under the verify fails the attempt promptly, a stuck device open does not. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Detect resume and hold the streak through the restart window Monotonic timers pause across suspend, so a backed-off wait armed before the sleep picked up mid-count afterwards: with the streak at the cap the "Fingerprint reader unavailable" notice stayed up for the remaining wait after the resume hook had already freed the reader, and misses collected around the suspend edge carried across it, so a healthy reader could cross the notice threshold in the first seconds after waking. With the restart enqueued off the thaw, the loop's first attempts after a wake can also land on the old daemon while it is being stopped -- up to ~3s when it ignores SIGTERM -- and three of those would show the notice for a reader that was merely being restarted underneath. Notice a resume from any of three signals -- a sleep watch ticking the wall clock for the whole lock, a retry that fired late, or an unreached attempt whose settle finds the watch's last tick far in the past (so a suspend shorter than the reach bound is caught before the tick itself gets a chance to) -- and open a grace window: the stale streak is dropped, a pending wait retries the fresh daemon at once, and misses inside the window hold the streak at the first tier without ever counting toward the notice. Detection is idempotent within the window, since more than one timer can notice the same resume. Pinned by MaxMad75's reading: the window is armed by the resume, not by the first miss. Verified on his X390 (S3, frozen sessions): six lid-close cycles, fingerprint-resume at +15ms, streak held, notice never fired. Co-authored-by: MaxMad75 <44462964+MaxMad75@users.noreply.github.com> Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Only let a definitive probe change whether fingerprint is configured The status probe collapsed every fprintd-list result into yes or no, so an unreachable fprintd -- restarting under the resume hook, or failing a D-Bus activation mid-resume -- read as "not configured": the icon vanished, the retry loop and the sleep watch stopped, and nothing asked again for the rest of the lock. One transient miss killed fingerprint until the next lock, with the password as the only clue. MaxMad75 hit it on hardware in run 6 of the X390 series; osborng filed the stock repro as #9453 (mask fprintd, lock, unmask -- fingerprint never returns). Classify the probe's output instead: an enrolled-print row is yes, fprintd's explicit no-prints answer (or a missing PAM file or binary) is no, and anything else is unknown -- the probe could not tell, so nothing changes and it is retried on the attempt-retry pacing. The unavailable notice, backoff, and resume detection all sit downstream of this flag; now only an answer that actually means something can clear it. Fixes #9453. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Log the fingerprint loop's misses, notice, and recovery as lock events The reach timeout, an unreached settle, the streak crossing into the notice, a resume restart, and the recovery all changed lock state without touching logEvent, so a report of "Fingerprint reader unavailable" left no omarchy lock line to line up with suspend and resume timestamps in omarchy-debug-idle output. Log those transitions; reached attempts are the steady state and stay quiet. A match that unlocks after a run of misses is the recovery too -- the unlock resets the streak without settling, so it logs fingerprint-recovered there as well. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Abort an attempt stranded in flight when a resume is detected A verify that survived into the suspend still prompted comes back to a daemon the resume hook has already replaced, and the loop's resume handling deliberately left it alone: the reach timer stopped at the prompt, so nothing bounded it but pam_fprintd's own ~25s timeout, and until that ran out the icon invited touches that could not work. Most visible where user sessions are not frozen across sleep and the lock races the hook. Abort the stranded session when the resume is detected and route it through settle: it lands inside the grace window, so the kill never counts toward the notice, and the settle arms the fast retry against the fresh daemon itself. Suggested by sliekens in review. Claude-Session: https://claude.ai/code/session_0168egYTXrVBVg16ugszGzQt * Simplify fingerprint recovery and consolidate enrollment checks Use the enrolled-entry matcher from #9551 while retaining the lock's tri-state probe recovery and the privileged /usr/bin/fprintd-list call. Unknown enrollment probes must preserve existing PAM and resume recovery rather than deleting the machinery needed to recover. Preserve administrator-owned unnumbered timeout files during migration. Remove presence-driven retry overrides and their cooldown, clock, and idle-window state: resume has its own fast recovery path, while other errors can follow the bounded automatic backoff. Let the existing sleep watcher detect resume instead of also tracking the age of each retry. Setup now uses apply-lock so PAM and recovery installation have one implementation. Keep the restart, stop bound, unreachable-attempt pacing, unavailable feedback, reach watchdog, and probe rechecks because each handles a distinct failure. Co-Authored-By: Karl Ahlin <kalle.ahlin@gmail.com> Co-Authored-By: Codex Medium <noreply@openai.com> * Preserve failed-enrollment coverage in the setup fixture The successful-enrollment fixture accepts PAM commands, so failure checks must explicitly reject those commands instead of relying on an unexpected-command error. Log both sed and tee and stub apply-lock for every case so premature authentication setup is detected without reaching live PAM files. Co-Authored-By: Codex Medium <noreply@openai.com> * Complete fingerprint recovery and setup reporting Back off immediate device errors after the verification prompt as well as failed claims, while retaining fast retries for mismatches and normal scan timeouts. Measure from the prompt so a slow claim cannot hide a fast failure. Paced user activity retries preserve the daemon idle window required to clear a wedged claim. Initial probe outages remain visible without inventing enrollment, and setup cannot claim lock-screen success when the PAM configuration was not installed. Exercise the real QML service rather than a copy of its state machine. Co-Authored-By: GPT-6 <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 Medium <noreply@anthropic.com> * Avoid competing fingerprint probes and partial setup Known enrollment is recovered by the PAM retry loop, so failed status probes must not raise a false unavailable notice or interrupt its daemon idle window. Initial unknown enrollment still gets paced probes. Install recovery files before enabling fingerprint PAM so a missing source cannot leave a new partial configuration. Co-Authored-By: GPT-6 <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 Medium <noreply@anthropic.com> * Keep the fingerprint error clock at the first prompt pam_fprintd also sends Verification timed out as an informational message. Updating the prompt timestamp on that message made a normal full scan window look like an immediate device error and caused unnecessary backoff. Record the first prompt of each PAM attempt so later status messages cannot move the error window. Co-Authored-By: GPT-6 <noreply@openai.com> --------- Co-authored-by: Omabot <omabot@omarchy.org> Co-authored-by: MaxMad75 <44462964+MaxMad75@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex XHigh <noreply@openai.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Karl Ahlin <kalle.ahlin@gmail.com> Co-authored-by: Omarchy Bot <omarchybot@users.noreply.github.com>
First-party plugins
These plugins ship with Omarchy and are discovered by the shell at startup.
They use the same manifest.json contract as third-party plugins; the
only difference is that the shell flags them with __isFirstParty: true.
First-party non-bar plugins are enabled unless listed in disabledPlugins[];
omarchy.bar is the default bar option and becomes inactive only while another
kind: "bar" plugin is selected. Services and keep-loaded panels are mounted
at startup; other panels, overlays, and menus are loaded on demand.
User-installed plugins live alongside these conceptually but on disk under
~/.config/omarchy/plugins/<plugin-id>/ rather than in this directory.
| Plugin | id | kinds | entry point |
|---|---|---|---|
| Bar | omarchy.bar |
bar |
bar/Bar.qml |
| Image picker | omarchy.image-picker |
overlay |
image-picker/ImagePicker.qml |
| Emojis | omarchy.emojis |
overlay |
emojis/Emojis.qml |
| Clipboard mgr | omarchy.clipboard |
overlay |
clipboard/Clipboard.qml |
| Reminders | omarchy.reminders |
overlay |
reminders/ReminderFlow.qml |
| Omarchy menu | omarchy.menu |
menu, bar-widget |
menu/Menu.qml, menu/BarWidget.qml |
| Notifications | omarchy.notifications |
service |
notifications/Service.qml |
| Audio | omarchy.audio |
bar-widget |
panels/audio/Panel.qml |
| Bluetooth | omarchy.bluetooth |
bar-widget |
panels/bluetooth/Panel.qml |
| Clock | omarchy.clock |
bar-widget |
panels/clock/BarWidget.qml |
| Elsewhen | omarchy.elsewhen |
bar-widget |
panels/elsewhen/Panel.qml |
| Monitor | omarchy.monitor |
bar-widget |
panels/monitor/Panel.qml |
| Network | omarchy.network |
bar-widget |
panels/network/Panel.qml |
| Power | omarchy.power |
bar-widget |
panels/power/Panel.qml |
| Tailscale | omarchy.tailscale |
bar-widget |
panels/tailscale/Panel.qml |
| Agents | omarchy.agents |
bar-widget |
agents/Panel.qml |
| Weather | omarchy.weather |
bar-widget |
panels/weather/BarWidget.qml |
| Media | omarchy.media |
service, bar-widget |
services/media/Service.qml, services/media/BarWidget.qml |
| Battery | omarchy.battery |
service |
services/battery/Service.qml |
| Idle | omarchy.idle |
service |
services/idle/Service.qml |
| Night light | omarchy.nightlight |
service |
services/nightlight/Service.qml |
| Lock screen | omarchy.lock |
service |
lock/Service.qml |
| OSD | omarchy.osd |
panel |
osd/Osd.qml |
| Polkit agent | omarchy.polkit |
service |
polkit/PolkitAgent.qml |
First-party bar-only widgets also carry manifests next to their QML files,
e.g. bar/widgets/Workspaces.manifest.json. Rich popup widgets live in their
own plugin directories, each with its own manifest.json.
Bar
The built-in status bar and default full-bar option. Layout lives in the
top-level bar: subtree of ~/.config/omarchy/shell.json (with the shell
providing config/omarchy/shell.json when
the user has no file). See bar/README.md for the widget catalogue
and customization schema.
Image picker
Fullscreen image-grid selector overlay. Used by omarchy-menu-images
(wallpaper picker) and omarchy-theme-switcher (theme picker) and any
other caller that wants to present a directory of images with previews.
Two ways to drive it:
- Shell-level summon:
omarchy-shell shell summon omarchy.image-picker '<jsonPayload>'. The payload can carryimageDirs,imageRows,selectedImage,selectionFile,doneFile,showLabels,filterable. Best for in-shell callers that already speak JSON. - Direct IPC target:
omarchy-shell image-selector open <imageDirs> <imageRowsB64> <selectedImage> <selectionFile> <doneFile> <showLabels> <filterable>. Positional args;imageRowsB64is base64-encoded so embedded newlines / tabs survive the bash argv handoff. This is whatomarchy-menu-imagesuses. Colors come from the central shell theme singleton; there is no per-call override surface.
The selection round-trip remains file-based: callers create a
selection_file and done_file (both mktemp), pass the paths, and
poll done_file for existence. The plugin writes the chosen path into
selection_file and touches done_file when it's done. cancel IPC
clears it without writing a selection.
The plugin has keepLoaded: true so the layer-shell window survives
between summons within a single shell session.
The carousel renders only the slices that fit on the screen plus one prefetch slice per side, capped at 33 cards. It preserves overlapping delegates during navigation, decodes images asynchronously at card size, and loads the selected preview before its neighbors. Lazy thumbnail preparation runs below normal CPU and I/O priority in one shared worker pool per image list: one worker on single/dual-core machines and at most two on larger machines. Opening and navigating the picker does not wait for that queue to finish.
Lock screen
Session-lock surface using Quickshell's native WlSessionLock and two
separate PAM services: omarchy-lock-password for password auth and,
only when fingerprints are enrolled, omarchy-lock-fingerprint for
fingerprint auth. It mirrors the previous lock screen field dimensions,
colors, blurred wallpaper, placeholder, and Hyprland-driven corners.
The plugin sets keepLoaded: true so a plugin hot-reload (for example
an installed bar widget changing on disk) does not destroy the lock
client while Hyprland still holds the session lock.
Polkit agent
Theme-aware authentication dialog for privileged actions. It uses
Quickshell's native Quickshell.Services.Polkit.PolkitAgent backend and
runs inside the long-lived omarchy-shell process, replacing the old
polkit-gnome-authentication-agent-1 autostart.
Omarchy menu
Quickshell-powered Omarchy command menu.
The menu UI lives in menu/Menu.qml as a first-party menu plugin and is
summoned through the shell (omarchy-shell shell summon omarchy.menu ...),
so it shares the long-running omarchy-shell process instead of starting a
second Quickshell instance.
The menu definition lives outside the shell host code:
- defaults:
default/omarchy/omarchy-menu.jsonc - user extensions:
~/.config/omarchy/extensions/omarchy-menu.jsonc
The shell parses both JSONC files at startup (with watchChanges: true
so edits take effect without a restart), evaluates when: / checked:
bash expressions in a single batched subprocess, and executes the
selected action: string directly via Quickshell.execDetached. The
long-running shell process keeps the parsed menu in memory, so the
keybind → IPC → visible path costs ~30ms cold.
Coming soon
omarchy.theme-switcher— folds theme switching into the shell.