5a0e7348af0e82c3de472e732dbac7045cd77498
22
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
879d6583da |
Fix fingerprint enrollment and lock-screen recovery (#7158)
* 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> |
||
|
|
c231097df7 |
Answer omarchy-shell calls over the shell's own socket (#13435)
* Answer omarchy-shell calls over the shell's own socket Every omarchy-shell call started a qs ipc client, ~45ms of startup for one IPC call: a theme switch makes two, and every script-driven OSD, toggle refresh and lock query paid it too. The shell now serves a socket in XDG_RUNTIME_DIR, named from its config path and Wayland display as qs ipc selects its instance, and omarchy-shell tries it first through socat, which starts in ~5ms. First-party handlers register as ShellIpc, an IpcHandler that qs ipc still reaches, and the socket calls only the functions a handler declares with their exact argument count, allowed by name so QObject methods such as destroy() stay out of reach. When the shell ran nothing it answers SKIP, and omarchy-shell asks qs ipc for its exact answer, so errors, third-party plugins and an unreachable socket behave as before. A call that may have run is never retried: a timeout or a connection closed without an answer reports the shell as not responding. omarchy-shell shell ping takes ~13-18ms instead of ~61ms, and omarchy-osd reaches the screen in ~36ms instead of ~77ms. Output and exit status match the qs ipc path across 26 calls, errors and quiet mode included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Only accept whole socket replies and retry only unmade connections A reply cut off after its OK prefix passed for the whole answer, and an empty reply with socat failing was retried through qs ipc although the request might already have been delivered. An answer now counts only once its record separator arrived. socat's own errors join the reply, so only its connect error, a socket nothing listens on, falls back to qs ipc beside an explicit SKIP; anything else is reported as not responding rather than retried. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
387fcf599a |
Keep the lock wallpaper decoded so waking shows it with the password field (#13431)
The lock started decoding its wallpaper only once locked, with the cache off, and first at the view's unsized native resolution. A machine suspending right after locking froze that decode partway, so waking showed the password field on a bare background and the wallpaper popped in after it. On this machine the wallpaper took ~208ms to become ready, and the suspend followed the lock by 66ms. The lock service now keeps each screen's lock wallpaper decoded in the image cache, as the lock view requests it: same URL, the screen's logical size, PreserveAspectCrop. The view waits for its size and reads from the cache, so the wallpaper is ready within ~3ms of the lock starting. The version in the cached URL follows the file's mtime and size, so a wallpaper overwritten in place still reloads. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b423f4993d | Complete OWE service setup and lock feed fallback | ||
|
|
41b6cc6965 |
Add native video wallpaper support (#6792)
* Add native video wallpaper support * Pause video wallpapers while a fullscreen app is focused * Sample one frame when a video background sets the bar text colour A video wallpaper made the transparent bar's colour sampling decode the entire file. ImageMagick's video delegate runs ffmpeg with no frame limit, so a twenty-second 1080p background took 11.3s of CPU where one frame takes 0.14s, and it did that on every theme change. The result was unusable anyway: a multi-frame input emits one value per frame, which the single-value match then rejected, so transparent bars silently fell back to the plain text colour on every video wallpaper. Selecting frame zero fixes the cost and the colour together, and fixes animated GIFs, which had the same bug. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Load wallpaper video lazily, and without an audio output Three costs the still-image path should never have paid. BackgroundMedia imported QtMultimedia at file scope and was instantiated on every output, so the module and its audio dependency closure mapped into every shell process whether or not a video was ever shown — measured at +2.72 MiB RSS. Moving the element into its own file behind a Loader that takes a URL defers the whole import: an inactive loader maps none of it, an active one maps all 25 libraries. An inline Component cannot defer that, because the type has to resolve when the file compiles. Qt's Video convenience type always builds an AudioOutput, and `muted` only aliases that sink's volume, so every monitor decoded an audio stream it would never play and opened an audio client for it. A bare MediaPlayer with no audio output spawns no QFFmpeg::AudioR, QAudioContext or PWDevMon thread, and plays files with no audio track just the same. The shared image also turned mipmapping on, which the desktop background never had. A full mip chain is about a third more texture memory — 10.6 MiB extra at 4K, per output — for a wallpaper drawn at its own size. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Stop wallpaper playback while the session is locked or screensaved Playback stopped only for a focused fullscreen window. Locking the session did not stop it, and the lock screen starts a player of its own, so an N-monitor desktop reached 2N decode pipelines the moment it locked — and stayed there, because a display blanked for idle stops being presented but does not stop Qt's FFmpeg engine, which drives its own clock. A laptop locked with the lid shut decoded video until the battery ran out. The lock and idle services already know both states, so the background service takes the shell reference the loader offers it and reads them. Looking a service up by id needs the registry to be reactive, or a background that loads before the lock service would bind to null and stay there. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Fan out video thumbnails narrower than single-threaded image jobs The generator fans out one job per core, which was bounded because VIPS_CONCURRENCY=1 made each of them single-threaded. ffmpegthumbnailer leaves FFmpeg's automatic decoder threading on, so a folder of uncached videos put a codec thread pool on every core at once. Queueing video work separately keeps the still-image path at full width and gives the video path a quarter of it. * Recognize a named video file as a theme preview The backgrounds fallback beside it already picks videos, so a theme shipping preview.mp4 was the one case that still went unseen. * Document video backgrounds in the manual The manual described backgrounds as images only. Worth saying plainly that a video wallpaper costs far more power than a still one and that each monitor decodes its own copy, since neither is visible from the picker. * Stop the lock screen's own playback once the displays go dark Pausing the desktop wallpaper on lock only moved the cost. The lock screen builds a player per monitor of its own, so locking an N-monitor session went from N decoders to N rather than to none — and the lock service blanks the displays five seconds later without touching them, which is where a lock spends nearly all of its time. A laptop locked and shut still decoded video into a dark panel. The service already owns both transitions, so it records whether the displays are dark and the lock view stops playback while they are. The manual said playback stops while the screen is locked, which was the same overstatement; it now says once a locked screen has gone dark. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Keep videos out of the lazy thumbnail path A lazy row stands in with the media file itself until its thumbnail exists, and the picker draws that with an Image — which shows a picture and shows nothing for a video, with no reload once the real thumbnail lands. So the first open after discovering an uncached video showed a blank tile. The same branch also spawns one generator per file immediately, before either queue is reached, and the theme switcher always asks for lazy thumbnails. That put the narrower video fan out on the one path that never used it: forty uncached previews meant forty ffmpegthumbnailer processes. Sending videos to the queue instead fixes the blank tile and puts them back under the cap. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Rebuild the theme preview cache after teaching it about video Preview discovery changed what it recognizes, but its cache keys on theme directory mtimes alone. A theme that already shipped a video preview would keep whatever the old rules cached until something happened to touch the directory. Bumping the version rebuilds it once. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Drop an activeAudioTrack setting that never took effect Qt's FFmpeg backend ignores setActiveTrack while no source is open, and the literal binding is not reapplied once the media loads and the tracks become known, so the line did nothing. What actually keeps the audio decoder and its client from ever being built is the absent audio output, which a file carrying an audio track confirms on its own: no QFFmpeg::AudioR, QAudioContext or PWDevMon thread appears without it. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Give up the blank state when a display comes back The lock screen stops its wallpaper while the displays are dark, but it was tracking the blanking it asked for rather than the panels themselves. Opening a docked lid turns the internal panel back on without going through runWake, and so does a resume, which left a visible lock wallpaper frozen on one frame until the next keypress. A frozen wallpaper someone is looking at is worse than the decoding it saves, so a screen change gives the state up. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Time bound the video thumbnail generator Routing videos through the queue means they are generated before the picker opens rather than behind it, which turned an unreadable or stalled file into a picker that never opens. ffmpegthumbnailer had no bound of its own and the drain waits for every job. A generator that gives up is already handled: the run reports failure, the partial file is removed, and the row drops out of the list. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Pause only the output a fullscreen window covers The fullscreen test was global, so a game on one monitor stopped the wallpaper on every other one — including the ones still in plain view. That is the failure the lock work was careful to avoid, and it made the manual's claim that playback stops when nothing can see it untrue for the commonest multi-monitor case. A lock or a screensaver does cover every output, so those stay a single decision; fullscreen is now matched against the focused monitor, the way the bar already routes by output. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Kill a video thumbnail generator that ignores the timeout Plain timeout sends TERM and then waits for a process that may never take it, which leaves the bound it was added for unenforced on exactly the stuck files it was meant to catch. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * Pause video wallpapers in battery power-saver * Fix paused video wallpaper source priming * Skip snapshots for video background transitions (cherry picked from commit 6f759538bfa76c2da03634e98ebfc2ebf63ec68e) * Generate thumbnails for direct-scan videos (cherry picked from commit 10fcca018a865dca311fb6863e8c8b0057291223) * Remember a video the thumbnail converter rejected A permanently unreadable video cost ten seconds of generator time on every picker open before its row dropped, because nothing recorded the failure. Both the menu image generator and the direct picker scan now leave a marker beside the missing thumbnail, keyed like the thumbnail on the file's size and mtime, so a repaired file starts clean. A timeout is left to retry, as it may only have been a busy machine. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Follow the panels' real DPMS state under a locked video wallpaper The lock screen stopped video playback when it asked for the displays to blank, and resumed on input, but never checked what the panels did. A blank that failed left a lit panel on one frozen frame, and a resume that turned the same outputs back on played nothing until the next keypress. Quickshell exposes no DPMS signal, so while a video is the locked wallpaper the lock polls hyprctl and decides per surface from the answer. A wake or blank request drops the last answer so its optimistic state applies until the next poll confirms it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Pause a video wallpaper for the fullscreen window that covers it The fullscreen check read the globally active window and the focused monitor, so it only knew about the window that had focus. A fullscreen window left on one monitor while focus moved to another resumed the wallpaper decoding behind it, and with fullscreen windows on two outputs only the focused one paused. Each output's visible workspace reports whether a fullscreen window covers it, and Quickshell flips that on the compositor's fullscreen event, so each panel now decides from its own monitor's active workspace instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Reopen a video wallpaper a theme switch replaced behind its path Two themes that both ship backgrounds/wallpaper.mp4 leave the current background at the same path after a switch, so the displayed path never changed and the running player kept decoding the old file from its open descriptor. Stills go through the snapshot transition and survive this; a video switch is instant and did not. A forced switch onto the path already on show now bumps a reload counter, and BackgroundMedia rebuilds the video player for it. A cache-busting query is not an option there, since FFmpeg reads it as part of the filename. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Keep picker rows uncached while a rejected video is left out Skipping a video with a failure marker let the picker cache its rows without it, and cached rows are trusted on the directory's mtime alone. A file repaired in place never touches that, so the marker's fresh key was never consulted and the video stayed missing. The generator now hands the marker back to the row loop, which drops the row and leaves the rows uncached, so each open re-stats the file and a repaired one is converted again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Hand each background loader only its own kind of file BackgroundMedia fed one URL to both the still loader and the video player. On a switch from image to video the Image was handed the video's URL in the moment before its loader unloaded, so Qt tried to decode the mp4 as a picture and logged an unsupported format on every such switch; the reverse handed the player a still to demux. The still URL is now empty whenever the path is a video and the video URL empty whenever it is a still, so a switch changes only the loader that stays. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Stop a video wallpaper before tearing its player down Switching from a video to a still destroys the BackgroundVideo item while its player is mid-read, which FFmpeg reports as a failed open in the shell journal on every such switch. Stopping the player on destruction lets the demuxer wind down first, and the switch is quiet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Play a video wallpaper's sound track from the first monitor Video wallpapers were always silent: the player was built without an audio output, since a muted output still decodes the track and opens an audio client on every monitor. A video with music should be able to play it. The player now builds its AudioOutput only once the media reports a sound track, so a silent file still opens no audio client, and only the first screen's panel opts in, so a multi-monitor desktop does not layer copies of the track. The output is muted while a paused player primes its first frame, and the lock screen stays silent. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Keep a departing video player off the still's file The switch away from a video still logged a cancelled open, and stopping the player on destruction only hid it: stopping reports the media as loaded, which the loaded handler answered by playing again. The real cause was one evaluation pass. Both URLs derived from the `video` flag, which is itself bound to the path, and QML updates the two in no fixed order, so the video URL could evaluate against the stale flag and hand the player the still for a moment. Its destructor then cancelled that open. Each URL now tests the path directly, the source binding only applies while the path is a video and restores nothing when it stops, and the destruction stop goes away with the hazard it introduced. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Pin the audio wiring in the test and name the output in the manual The audio assertion passed with the BackgroundMedia forwarding binding removed, which would have left every wallpaper silent, and did not pin the silent default or the first-screen selection. It covers all three now. The manual said the sound track plays "from your first monitor", which reads as routing to that monitor's audio device. It is the first monitor's wallpaper that plays, through the default output. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Omabot <omabot@omarchy.org> Co-authored-by: Codex XHigh <noreply@anthropic.com> Co-authored-by: z8 <yam@kernelius.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
f73740ff07 |
Blank the lock screen while the fingerprint reader waits (#6817)
The blank timer was gated on `authenticating`, which is `authenticatingPassword || fingerprintAuthenticating`. The fingerprint PAM sits armed for the entire lock waiting for a finger, so on any machine with a reader enrolled the gate is true from lock until unlock: the timer is stopped when the lock begins and never re-armed, and the display stays lit indefinitely. Gate on `authenticatingPassword` instead. A password check in flight still holds the display up, and the passive fingerprint wait no longer does. Claude-Session: https://claude.ai/code/session_01EDpyC9793TKZBS2jXUNECG Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1e7bb66556 |
Recover a session lock stranded by a dead shell (#6692)
* Detect a compositor session lock through one helper omarchy-restart-shell decided whether the session was locked by looking for "LOCK" anywhere in the hyprctl monitors payload. That works, but not for the reason the code reads like: Hyprland reports no lock state of its own, and the string comes from solitaryBlockedBy, the list of reasons a monitor cannot hand a client the whole screen. An active ext-session-lock is one of those reasons. A substring match over the whole payload also answers yes to a workspace or a monitor description that merely spells LOCK, and locking a desktop nobody asked to lock is the worst way to be wrong. Match the reason list itself, and put it behind a helper now that a second caller needs the same answer. That second caller needs a third answer too, because the reason list is not always readable. Hyprland stops at the first reason on a monitor with no workspace yet — one just coming back — and returns before it ever looks at the lock, so a missing LOCK there means nothing was asked rather than nothing was found. Neither that nor an unreachable compositor is an unlocked session, and locks strand precisely while outputs are coming and going, so both exit 2. Callers that only branch on success are unaffected. The test fixture claimed the string came from a workspace name, so it was encoding the wrong model of the compositor. It now returns what Hyprland actually returns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Retake a session lock stranded by a dead shell ext-session-lock keeps the session locked when its client goes away — that is the point of the protocol, so a crashing lock screen cannot expose the desktop. The cost is that a shell which dies while locked leaves the compositor locked with nothing left to authenticate against: Hyprland's failsafe, which takes a TTY or another machine to clear. Nothing carried the lock across a restart. Quickshell relaunches itself after a crash and omarchy-restart-shell can be run by hand, but both bring back a shell holding no lock, so the failsafe stayed up. A fresh shell never holds a lock, so a session already locked as the lock service starts can only be that orphan: take it back and let the user type their way out. Asking once is not enough. These deaths happen while outputs are going away, and the replacement shell comes up inside that same window, where there is nothing to read a lock off. So the question is asked until the answer means something: on a short timer while the session settles, and again when a screen comes back, since a display asleep for hours outlasts any timer worth running and returns through a state the compositor cannot answer for either. Once an answer does arrive the search ends, so the timer stops and later screen changes cost nothing. Three ways this could lock a desktop nobody asked to lock, all closed. A lock this shell took itself is not an orphan, including one taken while the question was in flight — omarchy-restart-shell re-locks a fresh shell, and the answer cannot tell whose lock it found. Recovery runs once and clears the flag, so nothing lingers to fire after an unlock. And PAM landing late reopens the question rather than answering it: clearing the failsafe from a TTY is the documented way out, so a yes from before there was anything to do about it may be stale by the time it can be acted on. The check has to live here rather than in the launcher. Quickshell's crash handler re-execs in place, keeping the same pid, so a supervising process never sees the restarts that recovery matters most for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Relaunch the shell when it dies without a signal Quickshell restarts itself after a crash, but only from its signal handlers: SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTRAP. Qt does not always leave that way. When the Wayland connection fails, QWaylandDisplay::checkWaylandError calls _exit() directly, which raises no signal at all — so the crash handler never runs, no report lands in ~/.cache/quickshell/crashes, and the desktop is left with no bar and no explanation. That is how #6684 ends: the lock path meets a screen with no valid Wayland output, declines to create a lock surface for it, and the connection dies with EINVAL. Supervise the launcher so those deaths come back. A clean exit is deliberate — omarchy-restart-shell stops the shell over IPC and starts its own replacement — and a signal to the supervisor means the session is going away, so neither relaunches. Neither does a shell that outlived its compositor, though that takes more than one unanswered query to conclude: the shell dies while outputs are being reconfigured, which is also when a busy compositor can miss one without being gone. A shell that cannot stay up gives up after five tries in a minute rather than spinning. Signals need care now that a launcher stands between the session and the shell. Bash defers a trap until a foreground command returns, so the shell runs as a job and the supervisor waits on it. Stopping the launcher used to stop the shell with it, back when this script exec'd Quickshell, so the signal is passed on rather than leaving a desktop nobody is watching. One arriving during the backoff sleep only reaches the trap afterwards, so the flag is read again at the top of the loop: a shutdown racing a crash would otherwise get one more Quickshell on its way out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b65dde39fc |
Keep fingerprint unlock offered on the lock screen with the lid shut
The reader is still reachable on an external keyboard or a docked laptop, so hiding the icon and skipping the scan just forced the password. sudo and polkit keep their pam_exec lid gates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
540e411edf |
Show fingerprint on lock screen and polkit, gated by lid state
Bring the fingerprint affordance to the Quickshell lock screen and polkit dialog, matching what hyprlock did on master. Lock screen: render the md-fingerprint glyph inside the password field's right edge when a sensor is enrolled, reserving space so long passwords never run under it. Polkit dialog: show one method at a time. When a sensor is enrolled and the reader is reachable, the dialog is just the centered fingerprint icon (square card); the moment PAM asks for a password it switches to the password field. Detects pam_fprintd anywhere in the auth stack now that a gate can precede it. Lid awareness: a closed lid means the reader is unreachable, so both surfaces fall back to the password. polkit gets a pam_exec clamshell gate (auth [success=1 default=ignore] before pam_fprintd) so a shut lid drops straight to the password prompt instead of blocking on the reader for the pam_fprintd timeout; the lock screen hides the icon and skips scanning. The gate points at the fixed /usr/bin path the package always provides so it survives switching between package installs and dev-link. A migration adds the gate for existing fingerprint setups. New helper omarchy-hw-laptop-closed (pure lid state); omarchy-hw-clamshell now composes it with the external-monitor check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a64d895a02 |
Spawn shell subprocesses with bash -c instead of bash -lc
Each `bash -lc` starts a login shell that re-sources the profile (mise activation, /etc/profile.d) on every invocation — ~16 forks per call versus ~2 for `bash -c` — which taxes every menu/panel/launcher action the shell shells out for. The session already exports PATH and env to the shell, so omarchy commands resolve fine under `bash -c`. Switch the internal/omarchy-owned spawns (theme+background switches, brightness, monitor scaling, DNS, lock/fingerprint, keyboard-layout probe, voxtype status, and the `:`/printf state-file writes) to `bash -c`. Leave `bash -lc` on the sites that run user-configurable commands (custom bar-widget exec, menu provider/guard scripts, launcher scan commands, configurable idle/screensaver command), where a user's command may rely on their login environment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
54d79d9d16 |
Fix screen flash when waking from sleep into the unlock screen
The lock service arms a 5s blank timer whenever the screen locks, and input at the lock screen re-arms it. Closing the lid sprays pointer noise over the lock surface, so the timer was routinely armed right before suspend, froze mid-countdown, and fired moments after resume -- blanking the freshly woken unlock screen under the user. Guard the timer with a wall-clock check: if far more time elapsed than the interval, the countdown slept through a suspend, so take a fresh run-up instead of blanking. This also blanks the lock screen 5s after an untouched resume. Two accomplices made the flash worse and hid the real bug: - The clamshell watcher's 2s poll fired an unconditional global DPMS enable whenever no external monitor was active, relighting any blank within 2 seconds (lock-screen blanking never stuck on undocked laptops) and racing the resume modeset. Recovery now only wakes displays when it actually re-enables one. - Every keystroke at the lock screen dispatched a redundant DPMS enable via omarchy-system-wake, forcing extra modesets in the fragile just-resumed DRM state. Brightness "on" now skips the dispatch when every active display is already lit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
af828481b9 |
Give it a little more time
Felt too abrupt at 3s |
||
|
|
eb3cb5225c | Delay session lock during display hotplug | ||
|
|
8f5336e0d5 | Protect against hyprlock crash when there are no screens at all | ||
|
|
35a6940992 | Just 3 seconds of screen on after lock | ||
|
|
0f5e81143e | Move current theme state to local state | ||
|
|
0f715dff88 | Lock screen improvements | ||
|
|
30bafe7bb0 | Make lock wallpaper lazy | ||
|
|
67d2511984 | Improve shell lock acquisition before sleep | ||
|
|
aa5896766b | Mirror lock input across screens | ||
|
|
72c9331c72 | Make lock screen behave like a lock screen | ||
|
|
7ea4e1ab04 | Switch hyprlock to QS |