Tensaku shows a "Pick a default annotation size factor" dialog until
its persisted state names one. Neither config.toml nor the CLI flag
suppresses it, so ship the state file itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Solitude had no unlock assets at all, so it fell back to the default
Plymouth logo. Last Horizon shipped a preview-unlock.png derived from
Nord's that still carried Nord's navy #2e3440 entry-box fill; only the
background and logo had been recolored.
Both are now generated the way the rest of the set was: the logo mask
recolored to the theme's accent, and the preview rendered by the same
pipeline as `omarchy plymouth preview` using each theme's background
and foreground. Last Horizon's unlock.png was already correct and is
untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CC9kSQv8ZEogaxDoeKBzL
It was the user override file for the first Quickshell bar, back when the
bar layered it over bar-defaults.json. Unifying everything into
shell.json left an empty file that installs and the quattro upgrade kept
faithfully copying around.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Moving them ahead of the clock left the old entry in place, so a fresh
config rendered the widget on both sides of the time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tmux only raises an activity flag for windows that are not currently
selected, so a long-running command in the window you left selected --
the common case, since you switch away by moving your Hyprland focus
elsewhere rather than by selecting another tmux window -- finished
silently and the indicator never lit.
Windows now also count as waiting when they are selected in an attached
client, every client showing them is unfocused, and their window_activity
is newer than an @omarchy_unfocused_activity watermark. The watermark is
stamped by a new `track` subcommand wired to client-focus-in/out and the
existing select-window hooks, so it records where attention last was.
The hooks pass #{window_id} and #{window_activity} as arguments, which
tmux expands when the hook fires; run-shell -b would otherwise let output
arriving during the handoff be swallowed by the new watermark. The focus
hooks take index 100 to leave a user's own bindings alone.
That state has no hook of its own, so it needs polling to be noticed.
The probe therefore moves out of the indicator and into a service plugin,
alongside nightlight and battery. A bar surface exists per monitor and
each one instantiates every indicator twice, once per block, so a timer
on the indicator meant a shell-out per instance per tick -- four probe
processes every three seconds on a two-monitor machine, forever. One
service polls for the whole shell instead.
Sharing the state also fixes what per-instance polling would have papered
over: each indicator used to own its own count, so a timer-driven update
only refreshed the bar it ran on and left the other monitors stale.
Refreshes still arrive over the existing indicator broadcast, which now
coalesces into a single run no matter how many bars relay it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CC9kSQv8ZEogaxDoeKBzL
The lineup now reads toward the clock instead of away from it, so Stay
Awake sits nearest the time, then do-not-disturb, night light, reminder,
screen recording, dictation, and tmux alerts trailing off to the left.
Active indicators render after the hover-revealed ones so they stay
against the clock, and a newly active indicator joins on the far side
rather than shoving the ones already showing sideways.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tmux alert hooks fire omarchy-shell through run-shell, and run-shell
builds its own environment rather than passing the server's along, so the
hook ran without WAYLAND_DISPLAY. qs matches instances by display, so every
refresh from a bell died at "omarchy-shell is not running", which -q then
swallowed by design.
That left the indicator with no way to turn itself on. Its five second poll
only runs while the indicator is already active, so an alert raised after
startup was invisible until the shell happened to restart with the window
still flagged. The icon appeared roughly never.
Recovering the display from the socket in XDG_RUNTIME_DIR fixes it in the
one place that needs it, and covers any other caller reaching the shell from
a stripped environment rather than just the tmux hooks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Omarchy installed zram-generator but never shipped a config for it, so the
only zram tuning any machine had was whatever the ISO wrote once, and the VM
sysctls were untouched kernel defaults aimed at spinning disks.
The generator's own default caps the device at 4G, which on a large machine
sends reclaim to the hibernation swapfile far earlier than it needs to go.
Ship min(ram / 2, 8192) instead, matching what Fedora settles on, and state
the priority explicitly since it has to sit above the pri=0 that
omarchy-hibernation-setup gives the disk swapfile.
The four sysctls follow from swap being RAM rather than a disk. swappiness
above 100 says evicting an anonymous page beats dropping a page-cache page
that would have to be read back, which is true once swap is compressed
memory. page-cluster drops to one page per fault because the default of 8
buys readahead for a seek zram doesn't have and pays a decompression for
each page. Zeroing watermark_boost_factor stops fragmentation from producing
reclaim bursts while memory is still free, and raising watermark_scale_factor
gives kswapd room to reclaim in the background instead of letting allocations
stall in direct reclaim. The last two are what actually addresses the stutter
people notice and misread as swap being used too eagerly: proactive swapping
to zram is the cure, and synchronous direct reclaim is the disease.
Every ISO-installed machine already has an unowned zram-generator.conf, which
pacman would refuse to overwrite, so both upgrade paths need it listed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The apps list rebuilt its rows by writing into the maps held by the menu's
items and itemOrder var properties. Writing into an object owned by a QML var
property is not reliable: the same row object written into a plain JS object
always lands, but written through the property it occasionally arrives with the
key created and the value undefined. One write per rescan was lost, on a
different app each time.
A lost write left an id in itemOrder with no item behind it. The old purge only
deleted app rows it could find in items, so the orphan survived the next merge,
the add loop appended a second row for the same app, and the list grew by one --
permanently, and again on every later rescan. Touching a single desktop file
fires around a dozen merges, because the entry model emits valuesChanged per
insert and removal while it reconciles, so duplicates piled up quickly: nine
YouTube rows on the reporting machine, and Alacritty doubled before that.
The bookkeeping moves into MenuModel as two pure functions that build fresh maps
for the caller to assign in one shot, so the fragile write disappears. They also
make the merge self-healing rather than merely correct-when-nothing-is-lost: an
id with no item is dropped instead of carried forward, and an id is listed once
even when two desktop entries claim it -- so no single dropped write can compound
into a duplicate row again. The bash-backed providers behind the font and power
profile lists had the same latent bug and get the same treatment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJQJfHXXApUk6En8EZisHg
Clicking the Voxtype or fingerprint invitation did nothing. The launcher
execs setsid, which forks because the transient unit's main process is
already a process group leader, so the unit exits within milliseconds and
systemd's default control-group kill took the still-starting terminal down
with it. Run those units with KillMode=process instead.
The invitation tests had also drifted from the two-unit design and were
failing; teach the systemd-run mock to run the command it is given.
Both notifications are sent from background subshells, so whichever
notify-send registered first won the bottom slot. Give the update toast a
tick to register so Wi-Fi lands newest and stacks on top.
Reported to suit the XPS 16 as well, so the tuning now matches both machines and
the directory is named for the pair rather than the one model.
Gating moves from the DMI product name to the product SKU, which is what Dell keys
the Cirrus speaker firmware on -- 10280db9 for the XPS 14, 10280dba for the XPS 16
-- so it identifies the speaker hardware itself instead of a marketing name. SKUs
are compared as whole values, not substrings, so this cannot widen to the rest of
the XPS line the way a short product-name match would: product_family on these
machines is "Dell Laptops".
match_sku and match_dmi are now lists, letting one tuning name every model it
covers. A plain string still works as a single entry.
The measurements in tuning.conf were taken on the XPS 14 (0DB9). The XPS 16 is
covered on report rather than measurement, and tuning.conf and the graph both say
so, because the profile's bass lift and high-pass were fitted against one
machine's excursion and distortion limits.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Laptop speakers ship voiced by the vendor's Windows DSP layer, which Linux does
not get. A tuning restores that as a PipeWire filter-chain in front of the
internal speaker sink, matched to the machine by DMI string and expected sink.
Adding a laptop is a directory under default/audio/tunings with two files and no
new code: matching is data. The XPS 14 DA14260 tuning included here was derived by
measuring the xps-audio-linux EasyEffects profile (MIT) and fitting a biquad chain
to it, so no impulse response or other upstream asset is redistributed. It measures
1.24 dB RMS against that reference, and matches its dynamic range within 0.1 LU --
the reference's multiband compressor turned out to contribute nothing, so a linear
chain replaces it. Bass Q is capped deliberately: a closer magnitude fit swung
group delay 31 ms across 63-80 Hz, which smears bass transients.
The graph runs as its own PipeWire client under its own config name rather than
loading into the audio daemon. The daemon only reads its config at startup, so a
daemon-loaded tuning could only be switched by restarting PipeWire -- which drops
every PulseAudio client's connection, and applications that do not reconnect
(Spotify) then have to be restarted by hand. Hosting it separately also contains
failure, since a malformed tuning breaks only that service.
Three things about the surrounding audio graph needed fixing for this to behave:
- Volume must live downstream of the tuning. omarchy-audio-output-sink is now the
single definition of which sink an output's volume really uses, shared by the
volume keys, the output switcher's OSD and the audio panel, so they cannot
disagree. It resolves the current default output, which keeps it correct when
headphones are selected while a tuning exists.
- The tuning's own output is a movable sink input, so rerouting "all streams" to a
newly selected output would drag the processing onto headphones, or into the
tuning's own sink, which is a cycle. It is pinned, and stream moves are limited
to streams carrying an application.name.
- The physical sink a tuning fronts is not independently selectable, since picking
it would only bypass the tuning, so it is kept out of the output list.
Applying happens at first-run, not finalize-user, because finalize-user also runs
in the ISO chroot where there is no audio server and nothing would retry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Voxtype/fingerprint invitations run as a transient
omarchy-*-invitation.service that shows a critical toast and waits for a
click. On click the service execs the install terminal, so the service
stays active for the whole life of that terminal.
The installer ends with omarchy-restart-shell, which re-triggers any
still-running omarchy-*-invitation.service on the assumption that a
still-running unit is an unanswered toast wiped by the shell restart. But
an answered invitation is still running only because it is the parent of
the very install terminal that triggered the restart, so it gets falsely
re-fired and the invitation toast pops a second time right after the
install finishes.
Launch the installer in its own transient unit so the invitation service
exits immediately after the click. Genuinely unanswered invitations still
block in notify-send and are still recovered by restart-shell; answered
ones are gone and no longer match the re-show glob.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHPDqx5hwLTwq27f7KpM9d
A bar surface is built per monitor, so a widget in the layout is live once
per screen — but an IPC target only ever routes to the handler that
registered first. `omarchy.indicators refresh` therefore reached a single
bar, and since indicators only re-read their state on that signal, the
other screens kept showing a stale reminder count, tmux alert, or DND
state until the next reload. Clock and system-update refreshes had the
same reach.
Let the bar resolve every live instance of a widget id and relay the call
to all of them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Kernel 7.1 unpacks the initramfs asynchronously, which races /init: the
early /proc, /sys, /dev, and /run mounts fail with EPERM while unpacking
settles, so plymouthd exits when it can't read /proc/cmdline and encrypted
boots fall back to an unthemed text LUKS prompt. Boot still completes only
because the kernel passes key=value cmdline params to init as environment
variables and devtmpfs is automounted.
Force synchronous unpacking via initramfs_async=0 until the race is fixed
upstream: in the packaged defaults for fresh installs, and via migration
for existing systems.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
argv is world-readable in /proc for every local process while the
connection attempt runs. Create the profile without the secret, then
set it through nmcli's scriptable connection editor, which reads from
stdin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The on-battery toggle bought a fraction of a watt while the radio
idled -- twenty-some minutes across a full discharge in the best case
-- and paid for it with 20-300ms latency spikes on idle links and
outright link drops on Intel BE200/BE211 firmware. NetworkManager can
keep power save off natively with one conf.d line, which replaces the
udev rule, the setter script, and any per-card deny-listing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An unsupported terminal notified once per monitor and each monitor
paid a wasted wait; check before the loop instead. The per-read
timeout also let a chatty event stream extend the wait indefinitely,
so spend the 5 seconds against one deadline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Use Region { item: popupColumn } instead of manually mirroring the
column's x/y/width/height. It tracks the item's mapped geometry directly,
so it stays correct without assuming the column shares the window's
coordinate space.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The generator fix only helps fresh installs; existing wrappers in
~/.local/bin keep the recursive exec line until rewritten. Detect the
generated wrapper shape, extract package and bin, and reinstall each
one, which also repairs wrappers users created for their own packages.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A regex over the QML text breaks on innocent reformatting and passes
even when the scrolling behavior regresses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
xdg-settings get also consults $BROWSER, so unset it for the getters too.
A failed best-effort fallback default should not abort the browser removal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restarting the shell kills its notification server, and a pending
invitation's notify-send waiter then hangs forever: the dying server
never emits NotificationClosed. So a still-running
omarchy-*-invitation unit means an unanswered invitation — re-run it
once the new shell is up so its toast reappears.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The setup installs packages (libfprint-git, fprintd, usbutils), which
isn't reliable on a fresh install before the databases have been
synced. Defer the invitation with a post-update hook, like Voxtype.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sending the shortcut through wtype's virtual keyboard let the physically
held SUPER merge into the injected chord, so terminals saw
SUPER+CTRL+Insert instead of CTRL+Insert, and injected CTRL+V could even
trigger the SUPER+CTRL+V clipboard manager bind. Send via send_key_state
with no window target instead: explicit mods go straight to the focused
surface, layer-shell panels included.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XnMDbwJXQA3fvW8o8Pwr97
Session names can contain pipes but never colons, so split fields on
colons instead. Remember refreshes that arrive while the indicator is
already polling, and jump to the most recently used tmux client rather
than an arbitrary one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Show a bar indicator whenever tmux has flagged a window, the same state
that highlights the tab, and jump to it on click or with Super + Ctrl + J.
Tmux hooks push the state to the shell, so nothing polls while no pane is
waiting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pgrep -x tmux never matched, since the processes are named "tmux: server"
and "tmux: client", so config reloads were silently skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the clamshell gate to /etc/pam.d/sudo, not just polkit-1. When the
lid is shut the reader is unreachable, so a terminal sudo would block on
"Place your finger" until pam_fprintd timed out before letting you type the
password. The same pam_exec gate (success=1 skips fingerprint when the lid
is closed) now runs ahead of pam_fprintd in the sudo stack as well.
setup and removal share one gate definition across sudo and polkit; the
migration now gates both stacks on existing installs.
Resolves the clamshell case in #856 and supersedes #6003.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a first-run notification, alongside the keybindings/Wi-Fi toasts, that
invites anyone with a fingerprint sensor to enable it. Clicking launches
omarchy-setup-security-fingerprint in a floating terminal.
Detection lives in a new omarchy-hw-fingerprint helper that reads sysfs
(device product descriptor plus a fingerprint-vendor allowlist), so it
works before fprintd/usbutils are installed and without nagging machines
that have no reader. The setup script reuses the same helper as an early
gate, bailing before installing any packages when no reader is found
(replacing the old post-install fprintd-list probe).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a first-run notification, alongside the keybindings/Wi-Fi toasts, that
invites anyone with a fingerprint sensor to enable it. Clicking launches
omarchy-setup-security-fingerprint in a floating terminal.
Detection lives in a new omarchy-hw-fingerprint helper that reads sysfs
(device product descriptor plus a fingerprint-vendor allowlist), so it
works before fprintd/usbutils are installed and without nagging machines
that have no reader. The setup script reuses the same helper as an early
gate, bailing before installing any packages when no reader is found
(replacing the old post-install fprintd-list probe).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
The menu card re-centered on every resize, so searching or moving into
a submenu made it jump around. It still opens centered like before, but
the first search keystroke or submenu move freezes the top line where
it sits — from then on the card only grows and shrinks downward.
Closing unfreezes. Also drop the 220px minimum height so the card
always hugs its content instead of leaving a void under short lists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Model rows stored the live Notification object in a ref role. When the
server destroyed the notification (sender close, DND untrack, dismiss)
the role became a dangling C++ pointer, and the next read segfaulted in
QQmlListModel::data — typically when replaying history over IPC. Track
live notifications in a JS map keyed by originalId instead, cleaned up
on close and untrack, where a stale reference degrades to a catchable
error instead of a crash.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bring back per-theme Plymouth unlock theming, dropped when walker +
elephant were removed. Selection now uses the same image carousel as
the theme and background switchers instead of a name list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>