With both at 0 the monitor is disabled and handleIdleChanged returns early, so a cycle already running when shell.json changed never cancels: omarchy-system-wake never runs, and if the screensaver never opened a window nothing else ends it. Cancel it the way turning on stay-awake does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex Medium <noreply@openai.com>
With 0 meaning off, setting the screensaver to 0 while the screensaver is up moves the first idle deadline to the lock's, which turns the pending lock's bound interval to 0 and locks at once, minutes early. Set each timer's interval when the cycle starts, so a change to shell.json takes effect from the next cycle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex Medium <noreply@openai.com>
The timers' intervals are bound to the configured delays, so setting the lock or screensaver to 0 while the screensaver is up turns a pending timer's interval to 0 and it fires at once. Check the action is still enabled when its timer fires.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No suite failed with the strip restored, so nothing kept the 4.0.3 regression from coming back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bar widget instances are not handed sourceDir (ModuleSlot.injectProps sets only bar, moduleName and settings), and a plugin could reach its directory through Qt.resolvedUrl, so the comment's rationale was wrong on both counts. Two lines carry what the code cannot say.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex 0.158's app-server can leave account/read unanswered, and asking it
first lost the limits whenever it did, leaving the agents panel showing
"Codex limits unavailable". The limits name the plan themselves, so they're
asked first and account/read is only a short fallback when they don't.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Under the Lua config system, 'hyprctl keyword monitor ...' is rejected
("keyword can't work with non-legacy parsers") yet still exits 0, so the
panel's enable/disable row silently did nothing. Use the hl.monitor eval
API instead, as omarchy-hyprland-monitor-scaling already does.
The re-enable must pass disabled = false explicitly: hl.monitor rules
merge, so a disabled = true already in config (e.g. the
internal-monitor-disable toggle file) otherwise survives and the
re-enable no-ops.
fzf's completion.bash rewraps the completions of ~76 commands on every
interactive shell start, costing ~25ms once bash-completion is loaded. Keep
only the key bindings (Ctrl-R, Ctrl-T, Alt-C); **<Tab> fuzzy completion goes.
`starship init bash` only prints a line that runs starship again with
--print-full-init, so call that directly and start the binary once.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The fake nodes carried only id and name, so a projection that handed back the node passed every assertion. Giving one an object-valued audio property, as a live PwNode has, makes the deep comparison catch it.
Co-Authored-By: Codex Medium <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A machine without a GPU, like most VMs, renders through llvmpipe on the
CPU, where every animated frame and every translucent window costs. In a
VM, opening and closing a terminal took ~5s of CPU; a panel ~3.4s.
omarchy toggle animations (also under Toggle > Animations) places a Hyprland
flag that turns off animations, blur and shadows and makes windows opaque.
The shell follows Hyprland's animations:enabled, rereading it on every
config reload: its one-shot animations run for Style.duration(ms), which
is then 0, and its spinners, pulses and title marquee hold still. A new
install in a VM starts with the flag in place.
Measured in the ISO test VM (llvmpipe), CPU per interaction:
terminal open+close 5000ms -> 481ms, workspace switch 1670ms -> 409ms,
audio panel 3446ms -> 796ms, volume OSD 2324ms -> 584ms, menu 2028ms ->
1241ms.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Hand browser and web app launches to the running browser directly
With a Chromium-based browser already running, each launch started a second
browser process only to pass its command line over the profile's singleton
socket and exit, ~165ms before the running browser even heard of it.
omarchy-cmd-browser-handoff sends that message itself with socat and waits
for the ACK, so a web app opens in ~210ms instead of ~430ms.
It hands off only what it can do exactly as the browser would: the packaged
browser binary rather than a wrapper, a flags file holding nothing beyond the
process flags Omarchy ships, no other data directory, and a socket we own.
Anything else, or no answer, launches the browser as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hand Brave Origin launches to the running browser too
/usr/bin/brave-origin is the package's own launcher, reading
brave-origin-flags.conf, with the profile in BraveSoftware/Brave-Origin.
A web app now opens in ~158ms instead of ~345ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Collapse identical notifications from the same sender into one toast
A web app open in several tabs fires one notification per tab for a single
reminder, stacking identical toasts. A new notification matching an on-screen
toast's app, summary, and body now replaces it like a replaces_id update.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Only collapse live duplicates that share image and click target
Screen recording toasts share text but preview and open different files, and
replayed history rows share images with entries still in history.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Compare a notification's current content when collapsing duplicates
A replaces_id update can land while the insert is deferred, so the initial
snapshot may no longer describe what the notification says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
To open a new terminal in the focused terminal's directory, the launcher
asked Hyprland for the active window with hyprctl, ~10ms on every
SUPER+RETURN. The binding now reads the pid from hl.get_active_window() and
passes it as --pid. Without one, from the menu or a shell, the launcher asks
Hyprland as before.
The bind is now a Lua function, which Hyprland reports only as __lua, so
o.bind_commands records the command it stands for and the keybindings menu
still launches a terminal when the entry is picked.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Read the default browser from mimeapps.list when launching
omarchy-launch-browser and omarchy-launch-webapp asked xdg-settings for the
default browser on every launch, ~72ms through several POSIX shell scripts.
The new omarchy-cmd-default-browser reads the http handler from the
config-dir mimeapps.list files the way xdg-mime does, desktop-specific list
first, taking the first listed entry whose command is installed, in ~10ms.
With nothing set there it falls back to xdg-settings as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Let the first copy of a browser entry decide whether it is installed
A user-local entry whose browser is gone could be rescued by a system copy
of the same ID, which the launchers never read. xdg-mime judges the first
copy found, as the launchers use it, so do the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add SSH Agent as an optional service install
Enables gcr-ssh-agent (already shipped via the gnome-keyring dependency)
so passphrase-protected SSH keys work from any context: the agent prompts
graphically on first use, with opt-in passphrase storage in the keyring.
Adds install/remove commands and Install > Service / Remove > Service
menu entries.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BReZWvvLsDRUaqgjiRzvZ5
* Clear the agent's SSH_AUTH_SOCK when removing the SSH agent service
The socket's ExecStartPost exports SSH_AUTH_SOCK into the user manager with set-environment, which disabling the socket does not undo, so apps launched after removal still pointed at a stopped agent. The environment.d file also has to be deleted before disable reloads the manager, or the generator re-exports it. Only an SSH_AUTH_SOCK pointing at gcr is cleared, so another agent's stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move SSH Agent setup to Setup > Security
Turning on an SSH agent is security configuration of the machine rather than installing software, so it sits with SSHD and the other Setup > Security entries. It hides once enabled, as Sudoless Docker does, and Remove > Service > SSH Agent turns it back off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
The wizard writes its config while prompts remain, so the watcher starts probing a gateway that is not up yet; each probe hangs until its 10 second timeout, and OpenClaw's CLI, killed by it, takes the terminal it inherited on stdin out of raw mode on the way out. The wizard does not set raw mode again, so from then on its prompts echo keys instead of reading them: the first letter typed at the channel search landed and the next one was printed below the prompt. The probes now read from /dev/null. This is the change reverted earlier on a test that only ever saw probes that exited on their own, never one the timeout killed.
Installing a service enables and starts it, so moving an old-package gateway or node host the user had stopped or disabled turned it back on. What was running and enabled is read before the move and put back after it.
Co-Authored-By: Codex XHigh <noreply@openai.com>
A service counted as the runtime's, or as a half-moved old one on /usr/bin/node, whenever it ran anything under ~/.openclaw, so a script of the user's in that directory could be stopped and rewritten, and seeding could go ahead beside it. The runtime is now OpenClaw's own script as upstream's installer lays it out, under ~/.openclaw/tools/*/lib/node_modules/openclaw, and both checks use that one definition.
Co-Authored-By: Codex XHigh <noreply@openai.com>
Judging a moved service by whether its program sat under ~/.openclaw missed a retry after a half-finished move, whose unit already ran the runtime's script on /usr/bin/node and so was never selected again; misread a symlinked home against the resolved Node it pins; and would have moved a gateway the user runs on Bun. A service now needs moving while it runs the old package's script, or the runtime's script on /usr/bin/node, the Node the old package pulled in; any other runtime is left alone.
Co-Authored-By: Codex XHigh <noreply@openai.com>
Since 2026.9.6 upstream's installer keeps the Node a gateway service already ran when it rewrites the unit, so moving a gateway the old package installed left it running the runtime's code on /usr/bin/node, from the nodejs package the old openclaw package pulled in and the seed no longer depends on. A moved service now counts as moved only once its program is under ~/.openclaw, and the reinstall pins the runtime's Node with --runtime-path, which gives the same unit a fresh install gets.
Hype joins Omacut, Monologue, Omacalc and Omawrite as an Omacom app in
the base install and the preinstall set, so Remove and Install
Preinstalls cover it too. A migration installs it on existing machines,
unless their owner has removed the preinstalls, and the GUIs chapter of
the manual introduces it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Flip Elsewhen between Fahrenheit and Celsius with Shift+T
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Flip Elsewhen's temperature units with Alt+T instead of Shift+T
PanelKeyCatcher's textKey now carries the held modifiers, which is how the
panel tells Alt+T from T. Existing handlers take only the text and are
unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A row lit itself by lifting its own time-of-day fill, so a picked night row
could read darker than the daytime rows around it. The picked city now takes
midday's lifted fill, the brightest, whatever its time of day; hover still
lifts each row from its own.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Monologue is Omarchy's own webcam recorder: pick a camera and microphone
once, press Space to record, and stop straight into a built-in editor
to split, trim and remove clips before saving an MP4.
Ship it in the base packages alongside Omacut, include it in the
preinstall restore/remove lists, and describe it in the manual. A
migration installs it on existing machines unless the user removed the
preinstalls.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Flip Elsewhen between 24-hour and AM/PM time with t
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Line up Elsewhen's weather in one column
The temperature and weather icon sat against each row's time, which is
narrower for "4:06" than "10:06", and shared the name's slack. The time
column now takes the widest time's width in every row, and a spacer holds
the weather against it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Navigate Elsewhen from the keyboard and summon it with Super+Ctrl+Alt+E
Up and down walk home and the cities, lighting the picked row and turning
the globe to it. Left and right move the clocks an hour, held until Escape
or close. Escape now clears a shifted time before leaving the globe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Reach Add a city from the keyboard and shift time past a day
Down past the last city selects Add a city, where Return or Space opens the
search. The arrow keys no longer stop at twelve hours, and shifts past a day
read in days.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Delete the picked Elsewhen city from the keyboard
Delete (or x) removes the picked city and moves the cursor to the one that
takes its place. The picked row shows its × so the target is plain. Delete
now reaches every panel's deleteRequested, as it already does in the
clipboard and the menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Center bar glyphs on the open-panel underline
The 16px icon canvas in the 27px slot was snapped from 5.5 to 6, and the
glyph's fractional centering correction was snapped too, so glyphs sat up to
0.9px right of the underline, which centers on the slot. Both are now placed
exactly, and the geometry test measures against the slot to within 0.05px.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Answer the Elsewhen panel over the shell's IPC socket
The Elsewhen panel arrived with a plain IpcHandler, so omarchy-shell
reached it only through the slower qs ipc fallback, and the test that
every first-party handler registers as ShellIpc failed on quattro.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Remove the IPC socket of each shell a test starts
The runtime smoke and screenshot sanity tests start the real shell from a
temporary copy and kill it, which leaves its IPC socket behind in
XDG_RUNTIME_DIR. A session that runs the suite a few times collected
dozens of them.
base-test.sh gains shell_ipc_socket, which derives a shell's socket as
omarchy-shell does, and both tests remove theirs in cleanup. The socket
test derives its fixture's socket through it too, so the helper cannot
drift from omarchy-shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Every plugin change handed the panel Instantiator a fresh array, which
rebuilds every panel. The panels were rebuilt four times in the first
700ms of startup, each rebuild starting a new asynchronous load of every
keepLoaded panel while the last was still in flight. Under load two OSDs
could run at once, each registering the osd IPC handler: the runtime
smoke test's intermittent "another handler is registered for target
osd".
The entries now live in a ListModel synced in place. A panel whose
plugin still loads the same way keeps its loader, so startup adds each
panel once, and a full plugin reload still clears the model to rebuild
from fresh code.
Under a six-core busy load the smoke test hit the duplicate in 1 of 5
runs on quattro and in none of 10 with this change.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A city on this machine's zone becomes homeCity, the zone's own city clears
it, and a city on another zone leaves it alone. Ported from omacom/elsewhen#5.
Co-authored-by: Shawn Yeager <shawn@shawnyeager.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Move Elsewhen into Omarchy as omarchy.elsewhen
The world clock ships in the shell tree instead of its own package. A
migration renames existing bar entries with their settings and removes
the retired package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Remove Elsewhen's shelved Earth row, overlap band and sky tint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tidy Elsewhen's models, data helper and docs, and drop currency
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Restructure Elsewhen's panel and globe on the shared shell components
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Show Elsewhen's bar globe upright
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use the shared city search for Elsewhen's globe jump bar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep legacy Elsewhen entries single and retry offline geocodes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fetch Elsewhen's weather with curl instead of a Python helper
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The OSD, the reminder flow and the Wi-Fi QR code now keep a surface that
parks as a 1x1 in the corner and grows when shown. They were missing from
the layer rule that turns Hyprland's layer animation off for the shell's
overlays, so Hyprland animated that growth: the OSD flew in from the left.
Add them to the rule, and test that every OverlayWindow's namespace is in
it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* Add a theme-set sample hook that mirrors themes to herdr machines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Mirror theme changes to herdr machines by default
Replaces the sample hook with omarchy-theme-sync, which omarchy-theme-set
starts detached after every theme change, and a theme-sync-off toggle that
stops a machine from both sending and receiving.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rename omarchy-theme-sync to omarchy-theme-set-herdr-machines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Label the toggle Herdr Theme Sync and extract its menu guard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Answer the Herdr Theme Sync menu guard from omarchy-theme-set-herdr-machines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sync herdr machines one at a time and drop the dry run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check the theme sync toggle under the lock and stop special-casing this machine
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Make herdr theme sync opt-in
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
Each brightness key ran omarchy-brightness-display: resolving the focused
monitor and backlight device, checking for an Apple display, reading,
writing and reading back through brightnessctl, then omarchy-osd over a
qs IPC client. That is 64-133ms from keypress to OSD depending on load,
on keys that repeat while held.
The brightness up and down keys now dispatch global shortcuts. On an
internal panel the shell reads the level from sysfs, steps it by the
script's rules, writes it with one brightnessctl, and shows the OSD from
the level read back. A press overlapping one still being applied is
dropped, as the script's flock drops it. External and Apple displays
fall back to the script, which drives them over DDC or their own helper.
The absolute and precise brightness keys still run the script.
Across 24 steps from eight starting levels the shell and the script land
on identical levels, and keypress to OSD drops to ~31ms.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
omarchy-launch-webapp asked xdg-settings for the default browser with
BROWSER still set to omarchy-launch-browser, which sends it down a
slower path to the same answer: ~105ms instead of ~30ms on every web app
launch. Unset it, as omarchy-launch-browser already does.
omarchy-cmd-terminal-cwd found the focused terminal's shell with pgrep,
which scans all of /proc on every new terminal. Read the terminal's
child lists from /proc instead, keeping the newest child as before.
Web app wrapper overhead drops from ~111ms to ~34ms, and a new terminal
appears in ~77ms instead of ~90ms.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Step the volume in the shell instead of a script per keypress
Each volume key ran omarchy-audio-output-volume: several pactl calls in
bash, then omarchy-osd delivering the OSD over a qs IPC client, ~165ms
from keypress to OSD, on keys that repeat while held.
The volume up, down and mute keys now dispatch global shortcuts that the
media service handles over PipeWire, stepping, clamping, unmuting and
debouncing by the script's rules and showing the same OSD. It acts only
when the default sink is an ALSA sink, which is its own physical sink.
Any other default, a DSP chain or EasyEffects above all, falls back to
the script, which resolves the physical sink from the live routing on
every press. The precise +1/-1 keys still run the script.
Keypress to OSD drops from ~168ms to ~20ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Run media and notification keys in the shell without an IPC client
The media keys and the notification dismiss, invoke and history keys ran
omarchy-shell, starting a qs client for one argument-free IPC call.
A new ipc shortcut kind names such a call as target.method. The shell
hands it to the service that owns the target, which runs its own
IpcHandler function, so the key behaves exactly as the omarchy-shell
call did. A call missing from the list binds through omarchy-shell.
Dismissing a notification with SUPER+comma clears the popup in ~11ms
instead of ~40ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(screensaver): open in own special workspace instead of active
- The screensaver window rule sets fullscreen, and a workspace holds
only one fullscreen window
- Launching it on the active workspace drops the fullscreen window
already there, and leaves it windowed after the screensaver quits
* Keep a showing special workspace open through the screensaver
A monitor shows one special workspace at a time, so opening the screensaver on its own replaced a visible scratchpad, and it stayed hidden after the screensaver closed. Open it on the special workspace that is already showing instead; it covers that just as well, and the workspace is still there when it goes.
The test's stubbed event stream follows the one in #10870.
Co-Authored-By: Willem van Ede <37050539+WillemCR@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hand focus back after the screensaver closes
Hyprland focuses a monitor when its special workspace empties, so on more than one monitor keyboard focus ended up wherever the last screensaver happened to close, rather than where it was. A detached reader on the launcher's event stream waits for the last one to go and focuses the original monitor again; the launcher itself still exits straight away, as the idle service expects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep a remapped screensaver off the regular workspaces
The terminal occasionally maps its window again as it closes, after its special workspace is gone. The launcher's workspace only applies to the first map, so the window landed on the focused monitor's regular workspace, where its fullscreen rule took fullscreen from the window there. On Hyprland 0.56.2 with two monitors this happened in 4 of 45 cycles, each time losing the user's fullscreen. A class rule now sends any later map to a hidden special workspace; the launcher's exec rule still wins on the first map.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check for screensavers before waiting on their close
The focus watcher only looked for remaining screensavers after reading a closewindow, so one that never mapped, or closed while the launcher was still waiting on another monitor, left it blocked until some unrelated window closed, and then it pulled focus back to the old monitor. It now checks before each wait, and matches the exact class, passed through the environment so the screensaver's pgrep and pkill never see it in argv.
Co-Authored-By: Codex Medium <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Willem van Ede <37050539+WillemCR@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex Medium <noreply@openai.com>
The menu, emoji picker, clipboard, OSD, reminder flow and Wi-Fi QR code
mapped a fresh surface on every open. Qt drew its first frames before
Hyprland sent the surface's fractional scale: the pixel ratio stepped
2, 1, then 1.6, so on a 1.6 display each overlay showed blurry for
~350ms before going sharp.
A new OverlayWindow keeps the surface. Hidden, it parks as a 1x1,
input-less layer below windows, off the overlay layer so it never
blocks direct scanout. Showing only resizes and raises it, so the scale
is already settled. Content stays hidden until the surface has grown,
so no 1x1 frame is stretched across the screen, and the window follows
the focused monitor each time it is shown. The image picker's own
fullscreen parking moves onto it too.
Each parked overlay keeps a Qt window alive: at rest the shell holds
~40 MiB more RSS and ~20 MiB more GPU memory.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Every binding that opened a menu or panel ran omarchy-menu or
omarchy-shell, which starts a qs client just to deliver one IPC call:
~60ms before the shell heard about the keypress.
The shell now registers a Hyprland global shortcut for each menu route
and panel in default/omarchy/shortcuts, and o.bind turns { menu = ... }
and { panel = ... } into hl.dsp.global for those, so a keypress spawns
nothing. A route or panel missing from the list binds through the
command as before. The default menu and panel bindings use the new form.
SUPER+SPACE opens the menu in ~31ms instead of ~94ms, measured from a
simulated keypress until the menu layer maps.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Speed up theme switch by backgrounding preload and parallelizing browser refresh
`omarchy theme set` was blocking on `omarchy-theme-switcher --preload` (~1.1s)
and serially refreshing every Chromium-family browser (~1.0-1.5s), making a
picked theme feel slow even though the shell recolored instantly.
- Background `omarchy-theme-switcher --preload` like the background cache
- In `omarchy-theme-set-browser`, skip the privileged policy write and browser
refreshes when every existing `color.json` already has the requested color
- Refresh only running browsers, and run those refreshes in parallel
Fixes#12627.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* Harden the unchanged-color shortcut in omarchy-theme-set-browser
Review feedback identified that the original shortcut could vacuously skip
when no managed policy directories existed, and that it accepted a color.json
whose ownership or contents the privileged writer would have rewritten.
- Only skip when at least one managed directory exists and every existing
color.json is a regular, root-owned 0644 file containing the canonical JSON
- Add OMARCHY_BROWSER_POLICY_DIRS as a test-only override so the setter's
unchanged-color check does not depend on the host's real /etc policy files
- Update browser-policy-sudoers-test.sh to use that override, and make
theme-set-browser-test.sh behavioral rather than grep-only
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* Find running browsers with one process scan during the policy write
Each browser check ran its own pgrep, and every pgrep rescans /proc,
~40ms apiece. Read the process table once with ps while the policy
writer runs, keeping its stdin so a terminal sudo prompt stays in the
terminal, then refresh the running browsers in parallel.
Chrome's fallback to plain google-chrome never ran: the refresh helper
returned success for a missing browser, so the || branch was dead. Pick
whichever Chrome binary exists up front instead.
The unchanged-color shortcut keeps its root:root 0644 check without the
fallback for hosts lacking stat -c, which Omarchy never runs on. Its
tests stub stat, ps and command discovery, so they neither depend on
nor touch the host's browsers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Decode the next theme background while the theme stages
The wipe waited 125-290ms after the transition arrived, decoding the new
wallpaper. Most stock wallpapers are WebP, which Qt decodes at full size
and scales afterwards, so a screen-sized sourceSize does not shorten it.
Start the decode earlier instead. omarchy-theme-set chooses the next
background and snapshots it before rendering templates, then sends a new
background prepare call in the background. The shell loads it into the
hidden incoming frame, so the transition finds it decoded. A prepare that
arrives after its transition is ignored, and one no transition claims is
dropped after five seconds.
The wipe now starts ~255ms after omarchy-theme-set begins instead of
~345-490ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Decode the wallpaper at screen size instead of shipped size
Wallpapers decoded at the resolution they were shipped at: a 5120x2880
stock wallpaper took 5120x2880 of RGBA on a 1920x1200 panel, and a
transition held up to three such frames. Bind sourceSize on the
displayed wallpaper and both transition frames to the screen's physical
size. PreserveAspectCrop treats it as the area to cover, so the image
still fills the screen.
Qt scales a decode up as well as down to cover sourceSize, so the native
size is read from the file header first with magick identify, and a
wallpaper smaller than the screen decodes at its own size. The images
wait for both sizes, so nothing decodes at native size first.
Ported from #8324 onto BackgroundMedia and the prepared incoming frame.
Measured with a 5120x2880 wallpaper, the shell's GPU memory at rest
drops from 264 MiB to ~148 MiB. The size probe delays the reveal by
~30ms, which the earlier prepare still more than covers.
Co-authored-by: Ryan Yogan <ryanyogan@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Ryan Yogan <ryanyogan@gmail.com>