201de1c0d60be79eebbffae18c3548b06909756a
224
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
201de1c0d6 | Normalize bar icon size and alignment | ||
|
|
e71a816e90 |
Cache workspace layouts instead of running a script on every switch (#15028)
* Cache workspace layouts instead of running a script on every switch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test the workspace layout cache by running its logic, not matching its source Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the fake layout read running while its exit handler runs Addresses Codex review: clearing running first let an undeferred, broken retry pass the cache tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3c1f7b780f |
Refresh the passwordless sudo indicator on change instead of polling (#15026)
* Refresh the passwordless sudo indicator on change instead of polling Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Refresh the sudo indicator after failed changes and on a wall-clock deadline Addresses Codex review: a rolled-back renewal skipped the refresh, the monotonic user timer paused through suspend, and default timer accuracy allowed a minute's delay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test the sudo indicator refreshes by running the grant flows Replace source-string checks with runs of timed, renewed, failed, permanent and disable flows against the fixture, asserting the recorded omarchy-shell refresh and wall-clock systemd-run --user timer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a98e55eb91 |
Pick window transparency from Appearance > Window Transparency, and a Glassy bar background (#14991)
* Pick window transparency from Appearance > Window Transparency Transparent, the default, keeps today's windows. Opaque turns the windows Omarchy makes transparent solid. Glassy is the frosted glass Starship (#14726): its blur behind every see-through window and shell surface, and a see-through, blurred background in the theme's terminal files, since foot turns compositor blur down and a Hyprland opacity rule fades text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Leave window transparency to Appearance when animations are off Turning animations off no longer makes windows opaque. VM installs start with opaque windows instead, and a migration keeps them opaque for anyone who already has animations off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add a Glassy bar background, under Menu Bar > Bar Background Menu Bar > Bar Background picks Opaque Bar, Glassy Bar or Clear Bar, and Indicators becomes Bar Indicators to match. Glassy keeps the theme background faintly and Hyprland blurs what is behind it, with the same blur as Glassy windows. omarchy bar background opaque|glassy|none|cycle sets it and prints it when given nothing. omarchy bar transparent true|false|toggle stays for compatibility, as none and opaque. Double-clicking the bar cycles through the three backgrounds the same way, from clear to glassy to opaque. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Stop when a saved corner choice can't be removed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Make btop follow glassy windows --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
18e059d9a4 |
Show the remote session indicator only during a remote session (#14981)
Hovering the indicators revealed it dimmed alongside the ones that can be switched on, though there is nothing to do with it there. An indicator can now opt out of that reveal, and remote session does. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1498718969 |
Add a floating mode with titlebars to the workspace layout cycle (#14482)
* Let the workspace layout test's Lua assertions fail the suite
Lua 5.5's standalone interpreter exits 0 when the script that raised the error came in on stdin, and exits 1 for the same error in a script file. The block here was piped in through a heredoc, so every assertion in it reported as passing however wrong the value was: changing the expected layout to one the toggle never writes still left the file green.
* Add a floating mode to the workspace layout cycle
Super + L cycled a workspace between dwindle and scrolling. It now carries on into floating, where every window on the workspace floats.
Floating cannot be a third `layout` on the workspace rule. Hyprland has no layout by that name and accepts one anyway: `hl.workspace_rule({ layout = "floating" })` returns ok and silently leaves the workspace on dwindle, exactly as a misspelt name does. So the mode floats the windows themselves, through the same `float` dispatcher SUPER + T uses.
Ending the mode has to tile the windows the mode floated and only those, so each one is tagged with `omarchy-mode-floated`. A window already floating when the mode reaches it -- an app with a float rule of its own, a dialog, one popped out with SUPER + T -- is left unclaimed and is still floating when the mode ends. The tag lives on the window rather than in a table here: it dies with the window, follows it between workspaces, and survives the config reload that plugging in a monitor performs, none of which a table on this side would do. A pinned window is left alone entirely, because tiling one unpins it as well.
Windows arrive on a workspace three ways and a window rule only covers one of them, so the mode watches events: window.open for what opens there, window.move_to_workspace for what is carried in, and the same event for what is carried back out, which gives up the floating the mode gave it rather than staying floating on a workspace that tiles. Every tiled window is claimed before any of them is floated, because floating one member of a group floats the rest, and claiming as it went would find those already floating and leave them stranded.
The mode is read back from the saved file rather than from the compositor. Hyprland reports a workspace's tiled layout and floating is not one, so a floating workspace still answers dwindle or scrolling; trusting that answer would skip a step of the cycle. A file saved before floating existed names a layout rather than a mode and falls through to what Hyprland reports, so the cycle keeps working and the next press rewrites it.
Two faults in the old script had to go first, because saving the mode makes both persistent. Named workspaces report a negative id, which passed the `^-?[0-9]+$` guard, and Hyprland reads a leading "-" as a selector relative to the current workspace: pressing Super + L on a named workspace reconfigured workspace 1 instead, and would now save a file that did it again at every login. And the saved state was written under $HOME/.local/state while the Hyprland side reads XDG_STATE_HOME, which also had to reach package.path: the loader lists that directory but loads the files by module name, so a state home anywhere else listed the saved layouts and then loaded same-named ones from the home directory.
Saving is checked too. The mode reaching the compositor and the mode reaching disk are separate things that can fail separately, and a save that failed silently would leave the next press reading a file that never got written, cycling between two modes and never reaching the third.
The guard against named workspaces stops new files being written, but an earlier version already wrote them, and they go on being loaded at every login. A migration removes the saved layouts named for a negative id.
The keyword fallback is gone. `hyprctl keyword` refuses a Lua configuration outright -- "keyword can't work with non-legacy parsers. Use eval." -- and exits 0 while doing so, so as a fallback it neither worked nor reported that it had not. A failed apply now says so and saves nothing, rather than announcing a layout the screen does not show and restoring it at the next login.
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Add titlebars, edge snapping, and a window Shelf to floating workspaces
Floating workspaces become a traditional desktop that works with every
theme. Windows get themed titlebars from the omarchy-hyprland-titlebars
plugin, which is loaded only while something floats. Dragging a titlebar
to the left or right edge snaps the window to that half, and dragging it
to the top or double-clicking maximizes it. The window floating mode
places is sized, centered, and cascaded, and floating workspaces focus
on click.
Super + M, or the minus button, sets a window aside on the Shelf, which
works from any workspace and restores the window onto the current one
in that workspace's layout. Float All Workspaces makes floating the
default while Super + L still sets a single workspace's layout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Bring resize on border to tiling too
* Fix floating mode cleanup, fullscreen windows, and failed Shelf notes
Turning Float All Workspaces off with no workspace layouts left behind
started nothing to give the floated windows back. A window fullscreen
when its workspace began floating now floats once it leaves fullscreen.
A window whose Shelf note cannot be saved stays where it is, and the
titlebars read the theme from where omarchy-theme-set writes it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Place the Shelf on the bar once, instead of on every minimize
Putting it back on each minimize kept a Shelf the user moved where they
put it, but re-added one they had removed. A migration places it once for
existing bars; Super + Alt + M opens the Shelf either way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Raise titlebars off the window background
A titlebar in the theme's background color ran into the window under it.
It takes the theme's lighter background, the raised surface every theme
defines, and the selection color on themes that make the two the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop minimizing and edge snapping from floating workspaces
A floating workspace is now titlebars with maximize and close, and nothing
else. The Shelf (its bar widget, panel, commands, notes, Super + M and
Super + Alt + M) goes with the minimize button, and the titlebar plugin's
edge snapping is left off, along with the shell's snap preview.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stop describing the titlebar package as snapping
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name web app windows in their launchers
Chromium names a web app window after its URL, and titles it with the page
title or just the domain. StartupWMClass ties the window to its launcher,
so the titlebar plugin can show the launcher's Name instead: WhatsApp
rather than web.whatsapp.com. New web apps, the shipped ones, and existing
launchers (through a migration) all carry it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop the titlebar package comment from the base list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Show the workspace layout in the bar and cycle it on click
The focused workspace's mark is a square for dwindle, a triangle for
scrolling, and a circle for floating, and clicking it cycles the layout
as Super + L does. Both layout toggles ask the bar to read the new mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Show scrolling as a circle and floating as a triangle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the look of the desktop to Trigger > Appearance
Menu Bar, Battery Percentage, Workspace Layout, Window Gaps, Animations,
and 1-Window Ratio change how the desktop looks rather than switching a
behavior, so they leave Trigger > Toggle for their own menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Choose a default workspace layout instead of floating everything
Trigger > Appearance > Workspace Layout sets the layout of every workspace
without one of its own: Tiling, Scrolling, or Floating. A tiled default
is Hyprland's own layout, and a floating one is the floating mode. It
replaces Float All Workspaces, which also overrode and set aside the
layouts workspaces had been given with Super + L.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Title the workspace layout menu Default Workspace Layout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use the animation icon for Appearance > Animations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Open clipboard history from Trigger
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Leave edge resizing to looknfeel on floating workspaces
looknfeel now resizes from borders everywhere, so floating workspaces
only need to switch pointer focus to click.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Convert saved workspace layout rules to modes
Released versions saved a workspace's layout as a plain workspace rule,
which names no mode, so a floating default would override a workspace
set to scrolling, and the bar and Super + L would misread it. A migration
rewrites those files as modes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Cycle workspace layouts backwards with Super + Alt + L
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test the Trigger > Appearance split and Clipboard History placement
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Decode escaped web app URLs before naming their windows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Describe the workspace widget's layout mark and click
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Let a workspace cycled onto the default follow it again
Once Super + L gave a workspace a layout of its own, it kept it for good,
so a later change of the default never reached it. Cycling onto the
default layout now forgets the workspace's own instead of saving it.
Without a saved default, the default is Hyprland's own layout, which
the user's looknfeel may have set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
||
|
|
0b9dace018 |
Unify input methods with installer keyboard selection (#14433)
* Unify input methods with the installer keyboard selection * Preserve inherited input keys and later keyboard changes * Exercise input engines across desktop toolkits * Show input modes in the keyboard indicator * Keep Pinyin setup prompts visible * Remember input selections before an app gains focus * Remove the Japanese input welcome notification * Show explicit choices on notification cards * Suppress the Cloudpinyin first-use prompt * Show the input switching shortcut in keyboard hints * Verify input setup and composition stay quiet * Use Super+I for input switching independently of Fcitx keys * Restore linked input settings and attribute test notifications to Fcitx * Correct the input setup shortcut hint * Keep input tooltips to the name and switching shortcut * Place the input language selector with the right bar widgets * Show input switching only for multiple layouts or methods * Unify input method and keyboard layout switching * Select keyboard layouts and input methods in the Typing menu * Apply typing selections immediately from the menu * Use installer keyboard choices and keep typing selections accurate * Queue typing changes and wait for saved selections * Keep queued typing selections when reopening the menu * Cycle keyboard layouts alongside composition engines * Avoid shadowing Python typing and retain pending mixed input selections * Keep input clicks working through controller and compositor failures * Move the keyboard layout widget to the right side of existing bars Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Map keyboard choices to the layouts the installer sets Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the switching order when changing input methods Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Set input method fonts only after the selection saves Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restore confirmed picker checks when an immediate change fails Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Route Super+I through the shell's input reader so unfocused choices stick Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restore the input group when the font preference cannot be written Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the azerty conversion now that the installer no longer offers it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Apply the owner's keyboard layout once before persisting the input choice A quattro merge kept a second apply_keyboard after the retry loop, which re-ran loadkeys and firstboot, ignored their failure, and overwrote the XKB layout setup supplies for keymaps kbd-model-map lacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Give the Lao keyboard choice a Lao desktop layout The row used la-latin1, kbd's Latin American Spanish keymap, so Lao installs typed Spanish. There is no Lao console keymap; like Korean, the console stays US and the desktop gets la, which Omarchy keeps behind US as a non-Latin layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Load engines installed after Fcitx started when they are selected The Typing menu refused an engine whose package an update had installed, since Fcitx discovers engines only at startup. Restart input once and wait for the engine; name the package when it really is not installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cycle input methods back with Super+Shift+I Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop notification action buttons now that the Pinyin prompt is suppressed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cycle input only through the shell's reader Without the shell there is no bar or menu either, so the one-shot cycling path only duplicated the reader's logic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add engines from the command line through the Typing menu's path omarchy setup input <engine> had its own restart, append, select and live-rollback code in configure.py, parallel to the menu's. It now installs the packages and appends through typing_setup, leaving configure.py with install-time seeding and update defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep Ctrl+Space out of Fcitx's toggles instead of probing for it The terminal prefix bind read the Fcitx config and profile on every press to avoid fighting a Ctrl+Space toggle. configure --defaults now removes that key from every trigger list, so the bind only checks for a terminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Run the input-method scripts with the system Python A bare python resolves through PATH, so a mise, pyenv or venv interpreter could run them without the system's PyGObject. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
+12 |
e1b0e5e9bb |
Converge omarchy-mac and omarchy-mx-mac into upstream Omarchy (#14431)
* Add omarchy-hw-platform to tell Apple Silicon and Qualcomm machines apart
aarch64 covers Apple Silicon Macs, Snapdragon laptops, Raspberry Pis and
VMs, so the CPU architecture can't pick platform setup. omarchy-hw-platform
prints apple-silicon, qualcomm, generic-aarch64 or generic from the vendor
prefixes in the device tree's root compatible (/proc/device-tree, then
/sys/firmware/devicetree/base) and the CPU, and fails when they contradict
each other. Its qualcomm answer uses the same evidence as the dragon
branch's omarchy-hw-qualcomm-soc, and the fixtures check the two agree.
omarchy-hw-apple-silicon is the Apple predicate on top of it.
An image built away from its hardware names its target in a root-owned
manifest, /var/lib/omarchy/image/target (format=1, platform=). While the
root is being built rather than booted, the detector answers from it and
never reads the build host's device tree; a booted system always answers
from its hardware. As root it restarts in an empty environment, so no
variable picks the platform for privileged setup.
Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jimmy Van Veen <8206925+JimmayVV@users.noreply.github.com>
* Reject platform gates keyed only on the CPU architecture
An architecture check is right for an ABI, a binary or a repository's
$arch, not for picking a platform. The lint lists each shipped check after
review and fails on a new one, so platform setup goes through
omarchy-hw-platform.
* Compose default packages from base, architecture and platform lists
omarchy-reinstall-pkgs installed the base list on every machine. A
platform's default set is now the base list, then install/omarchy-aarch64.packages
on aarch64, then the platform's own list when it has one
(install/omarchy-qualcomm.packages adds linux-firmware-qcom).
omarchy-pkg-defaults prints the set for the detected platform, or for the
one named, and reinstall installs it. x86_64 still gets exactly the base
list, and an image build gets its target's set.
* Early-load thunderbolt only where the kernel builds the module
linux-aarch64 has no thunderbolt module, and mkinitcpio fails the whole
image over a MODULES entry it cannot find. The optional form, thunderbolt?,
is not enough: a systemd initramfs copies MODULES as written into
modules-load.d, where "thunderbolt?" names no module, and a kernel without
the module would still list it there. thunderbolt_module.conf now adds plain
thunderbolt only when modinfo finds it as a module for the kernel being
built.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Compose mkinitcpio HOOKS from an early baseline drop-in
omarchy_hooks.conf assigned HOOKS outright, and mkinitcpio sources drop-ins in
name order, so it ran after any numbered drop-in (90-*.conf) and threw its
hooks away. HOOKS now comes from 00-omarchy-hooks.conf, which sorts first, and
later drop-ins add to it. The NVIDIA kms filter and the vconsole handling stay
in omarchy_hooks.conf. With the drop-ins Omarchy writes, machines keep the
same HOOKS, MODULES and FILES; a user drop-in that sets HOOKS and sorts before
omarchy_hooks.conf now takes effect instead of being overwritten.
Migration 1786605598 now sources the baseline first, since without it the
migration would read empty HOOKS and also rebuild hybrid systems. It skips a
machine that lacks the baseline.
* Resolve the LUKS parent from raw lsblk output
lsblk -s prints the ancestor tree with box-drawing prefixes ("└─/dev/nvme0n1p6"), so the crypto_LUKS parent came back as a path that does not exist and first-boot setup stopped with "Could not locate the LUKS device to re-key". -r prints raw names. Factory reset uses the same lookup.
* Check the factory-reset passphrase against keyslots only
cryptsetup open tries enrolled tokens before the key file, so a TPM2 or
FIDO2 token could authorize the re-key with a wrong passphrase.
* Test LUKS parent lookup during owner setup and factory reset
Exercise raw ancestry output without invoking provisioning or reset. Cover btrfs subvolume roots, absent encryption, failed probes, and explicit cryptdevice specifications.
* Cover LUKS parent lookup through an LVM volume group
* Journal the first-boot LUKS re-key so an interrupted attempt resumes
A first boot killed after the staged install key's slot was retired left the
key file behind, and every retry then stopped at "the staged LUKS key no
longer unlocks". A lost key file with the boot-time auto-unlock still
configured let setup finish with the disk unlocking unattended.
The re-key now runs from install/provisioning/luks-rekey.sh and records its
phase and slot numbers (never key material) in
/var/lib/omarchy/provisioning/luks-rekey.state. It reuses the owner's slot
instead of adding a duplicate, retires LUKS1 slots too, and destroys the
staged key only after the volume proves it opens nothing. Setup refuses to
finish while any part of the provisioning unlock remains. The journal ends at
phase=done and leaves together with pending, so a retry after the staged key
is retired must use the password the disk already holds.
Key checks exclude LUKS2 tokens: cryptsetup open tries an enrolled token
(say a TPM2 slot a previous owner left behind) before the key and reports its
slot whatever key it was given. Slot listings read only the keyslot section,
since a luks2-keyring token looks like a keyslot in luksDump.
* Change the root volume's LUKS key before the login and root passwords
Installation gives the login user, root and the disk holding / one password,
but omarchy-drive-password changed only the LUKS key, so the three drifted
apart. For the disk holding / it now checks the current password opens the
disk, changes the LUKS key, confirms the new key opens the disk and the old
one no longer does, and only then sets the login and root passwords. Any other
encrypted drive changes its LUKS key alone. Every drive now asks for the
current password with gum instead of cryptsetup's prompt.
A journal in ~/.local/state/omarchy/drive-password.state (disk UUID, key slots
and phase, never a password, written with the re-key journal helpers) lets the
next run finish an interrupted change. The rerun accepts only the old key
(removing the slot the interrupted change added) or the new one (retiring the
old slot), so another key such as a recovery passphrase never becomes the login
password. luksChangeKey is judged by whether the new key opens the disk, not
by its exit status, and key checks exclude LUKS2 tokens, which cryptsetup
open would otherwise accept in place of any password.
Runs are serialised, cryptsetup calls hold a disk lock, a / whose LUKS
partition cannot be resolved fails closed, and passwords are never traced. A
new password equal to the current one is refused, and --pbkdf argon2id is
passed only to LUKS2, so LUKS1 drives can change their password again.
* Add omarchy-lifecycle-dispatch for platform boot operations
Provisioning, factory reset and update call a fixed set of operations
through it. A platform whose boot chain the generic Limine path can't drive
registers a directory of root-owned entrypoints in the dispatcher; every
other platform gets a no-op and keeps its generic path. Apple Silicon is
registered to omarchy-mac-boot's /usr/lib/omarchy/mac-boot, with its
required operations failing clearly when the package or an entrypoint is
missing. docs/lifecycle-dispatch.md is the contract, including how a
Qualcomm boot package would plug in.
* Let the platform's boot package own first-boot setup's boot chain and key slots
Owner provisioning asks provision-prepare before the owner form, hands the
staged unlock to provision-commit and provision-verify where the platform
implements both, and records the kept LUKS slot through luks-slots before
the staged key is destroyed; the disk password change records the owner's
new slot the same way. On x86, Snapdragon and generic aarch64 every call is
a no-op and first boot is unchanged.
* Reset through the platform's boot package where it owns the boot chain
Where a platform implements the reset operations, reset-prepare and
reset-verify stage and prove the factory root's boot files, the throwaway
slot is added only after that, and reset-commit gets the key on standard
input once the factory root is active. A failure before the switch rolls the
boot state back and revokes the slot. Elsewhere the Limine UKI path is
unchanged.
* Check the platform's boot chain after an update
omarchy-update-boot runs update-verify after AUR packages, the last
sudo-capable step, so it also covers the initramfs rebuilds they trigger.
It follows third-party build code, so like AUR it authenticates through the
no-update wrapper. A failed verification finishes the update's remaining
steps, then exits non-zero without offering the reboot. Where the platform
does not implement it nothing runs and nothing asks for root.
* Hand a machine to its platform's migration through lifecycle dispatch
migrations/1790347292.sh resolves the new optional migrate operation and,
where the platform's boot package implements it, runs it as root. The
entrypoint moves the machine onto the platform's official package set,
idempotently and resumably; its failure leaves the migration pending. Once
it succeeds, a root-owned marker spares other accounts the migration and its
root prompt. Everywhere else, Macs without the boot package included, the
migration is a no-op, and an undetermined platform keeps it pending.
* Refuse packages tagged for another platform in a pacman hook
A platform package names its platform with groups=('omarchy-platform-<name>'),
<name> being what omarchy-hw-platform prints. omarchy-settings ships a
pre-transaction hook, the guard it runs and a copy of the detector, so the
guard is resident before the runtime and any platform package. The guard
reads tags from the synced databases and refuses a transaction that installs
a package tagged for another platform; a transaction without a tagged
package passes without asking which machine this is. Image builds answer
from the image's root-owned target manifest, as the detector does.
The guard ships on aarch64 only: no platform package is built for x86_64,
so x86 transactions run no hook. Hardware setup starts by proving the guard
is resident and checking the packages an installer placed without it. On
generic aarch64, where no platform packages exist, a missing guard is
logged, not fatal, and on x86 it is expected.
docs/platform-guard.md is the contract.
Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jimmy Van Veen <8206925+JimmayVV@users.noreply.github.com>
* Enable Voxtype's Vulkan backend only when it is installed
omarchy-hw-vulkan reports a Vulkan runtime, not whether this voxtype build
has a Vulkan backend. The aarch64 voxtype-bin package ships only the CPU and
ONNX binaries, so on any aarch64 machine with a Vulkan driver setup ran
`voxtype setup gpu --enable` and printed "Vulkan backend not installed" in
the middle of an otherwise working install. Require the backend binary too.
x86_64 voxtype-bin always ships /usr/lib/voxtype/voxtype-vulkan, so x86
installs still switch to the GPU backend as before.
Ported from omacom/omarchy-mac#483.
Co-authored-by: Oliver Lukschander <33756270+oliverlukschander@users.noreply.github.com>
* Link the system Widevine CDM into Brave
The widevine package registers its CDM for Chromium and Firefox only.
Brave looks for the CDM in its own install directory, finds nothing, and
DRM sites (Netflix, Apple TV+, Spotify) fail with a blank player and no
useful error. This hits machines where Widevine comes from that package,
such as aarch64 ones, since no Widevine component is published for them.
Link /opt/WidevineCdm/chromium into /opt/brave-bin and /opt/brave-origin-bin
through as_root when Brave or Brave Origin is installed, and add a migration
that repairs existing installs. Both do nothing when the widevine package is
absent or the browser already has a CDM. A standard x86 install has no
widevine package, so nothing changes there.
Ported from omacom/omarchy-mac#474, extended to Brave Origin.
Co-authored-by: Caleb Marshall <68087341+cajomar@users.noreply.github.com>
* Report failed setup steps on the console
With file logging, run_logged sends a failing leaf's output and its
"Failed:" marker to the install log only, so the caller sees setup stop
with an exit status and nothing else. A missing command (exit 127) was the
case that made this hard to diagnose.
Print the failed script, its exit status and the log path on stderr. Stdout
logging mode already shows the failure marker and is unchanged, and
successful steps stay quiet.
Ported from omacom/omarchy-mac#389 (the logging part).
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Let a migration defer itself without stopping the update
A migration that exits 75 changed nothing and cannot finish yet: omarchy-migrate
leaves it pending with its login notification, runs the migrations after it and
exits 0, so omarchy update carries on. Any other failure still stops the queue.
The platform migration passes a deferred refusal of its entrypoint through, and
defers itself where the dispatcher can't resolve its entrypoint (the platform
can't be told, or the entrypoint isn't trusted): nothing has run then, so the
update goes on to its boot verification, which fails there, as documented.
* Keep Apple Silicon's systemd initramfs on the HOOKS baseline
00-omarchy-hooks.conf set the busybox line on every machine. On a Mac,
omarchy-mac-boot's drop-ins then add the Asahi firmware hooks, but its
encryption drop-in reads busybox encrypt as a Mac unlocked through
cryptdevice= and leaves the line alone. The image came out as
"... block encrypt asahi omarchy-vendorfw filesystems ...", with no
systemd, sd-encrypt or omarchy-mac-encrypt, so an encrypted Mac could not
unlock its disk. Reproduced against an M1 Pro's installed drop-ins.
The baseline now asks omarchy-hw-platform. Apple Silicon starts from the
systemd line its boot package builds on. A Mac whose own mkinitcpio.conf
carries the asahi hook, or busybox encrypt, keeps that line, as a legacy
install needs. x86, Snapdragon and generic aarch64 keep today's busybox
line, as does a machine with no detector. The detector is the runtime's,
or else the copy omarchy-settings ships for its platform guard, so a
settings package newer than the runtime still places a Mac. A detector
that cannot place the machine stops the build.
The HOOKS test now composes the baseline on each platform and on legacy
Mac lines. A new test composes it with omarchy-mac-boot 20260926-1.45's
drop-ins, kept as fixtures, and checks the exact HOOKS the M1 Pro builds.
The NVIDIA test pins its platform.
* Hide optional installs that have no package on this machine
Not every optional install exists on every architecture: some vendors ship no
aarch64 build, and some repositories don't carry a package at all. Each
Install row now carries a `when:` guard naming every package its install
command adds, checked with omarchy-pkg-available, so a row with nothing to
install disappears instead of failing halfway through a pacman transaction.
The rows that build from the AUR (Chrome, Edge, Brave, Brave Origin, Zen,
NordVPN) are guarded on the CPU architectures their vendors publish for.
omarchy-pkg-available asks the sync databases: one `pacman -Slq` per batch,
and `pacman -Sp` for a name that list doesn't hold, so a provided name
(libappindicator-gtk3) and a version constraint resolve the way `pacman -S`
will. The menu's guard prelude sources the same function, so the whole batch
still costs one fork. omarchy-reinstall-pkgs reports and skips a default
that no configured repository offers instead of failing the transaction.
On x86_64 every guarded row resolves against the stable and edge
repositories, so the menu is unchanged there.
Tests derive each row's packages from its install command on x86_64 and
aarch64 and fail when the guard names different ones.
Co-authored-by: Ryan Hughes <1630358+ryanrhughes@users.noreply.github.com>
* Run 1Password with software GL where there is no render GPU
Electron draws a blank window when the machine has no DRM render node: a VM,
a headless or passthrough GPU, or a GPU whose driver isn't up yet (Apple M3
and later on Linux today). omarchy-hw-render-gpu tells whether a renderD node
exists, and omarchy-cmd-electron-gl-args prints --ozone-platform=wayland
--disable-gpu only when it doesn't.
omarchy-cmd-electron-gl-wrap installs a marked PATH wrapper in /usr/local/bin
that execs the real binary with those flags, so it follows the machine rather
than the moment it was installed. It refuses to replace a launcher it didn't
write, or to wrap a binary over itself, and stages the write so a failure
leaves the old launcher in place. omarchy-cmd-desktop-exec-repair points the
user's desktop entry at the wrapper by replacing only the executable token,
keeping arguments, other actions and customizations, with a backup.
The 1Password installer uses them when the machine has no render node. With
one, which is every x86 machine with a working GPU, nothing changes.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Record with wf-recorder when gpu-screen-recorder can't start
gpu-screen-recorder knows NVIDIA, AMD, Intel and Broadcom GPUs and aborts
during GL init on anything else, such as Apple Silicon's, before a recording
starts. Screen recording now falls back to wf-recorder, when it is installed,
if gpu-screen-recorder is missing or exits before the file appears. The
picker, file naming, notifications and post-processing are shared. wf-recorder
records one audio device, so desktop plus microphone go through a temporary
null sink that stop unloads, and its track is pinned to 48 kHz. Portal
capture and --resolution stay gpu-screen-recorder only.
The finalize pass keeps 48 kHz too: loudnorm declares 192 kHz output, so
without -ar the AAC encoder wrote 96 kHz from a 48 kHz recording, which many
editors decode as silence, and which on Apple Silicon makes the speaker
safety daemon lock the amps. #11092 makes the same one-line change; whichever
lands second drops it.
Stop and status matched `pgrep -f ^gpu-screen-recorder`, which misses
wf-recorder and matches any command line that starts with that path. The
script now records the recorder's pid, and the new
omarchy-capture-screenrecording-process checks or signals it by its
executable, through a pidfd opened before the check so a reused pid can't be
signalled. When that pid is no longer a recorder (a recording started before
this change, or one that died), every recorder the user runs counts, as it
does for the bar indicator and the Stop row, which use the same helper.
Where gpu-screen-recorder works, which is every x86 GPU it supports today, it
records as before.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Issac Trotts <15530+ijt@users.noreply.github.com>
* Hold logind's lid switch while a display it doesn't count is in use
systemd-logind treats a closed lid as docked only when an external display
is on a connector type it lists (HDMI, DisplayPort, DVI, VGA...). Displays on
"USB" connectors, which Apple Silicon uses for USB-C displays, aren't on that
list, and a missing comma fuses DVI-A and Composite into one entry. With such
a display in use, closing the lid suspended a laptop that was still being
used on it.
omarchy-system-lid-inhibit holds a handle-lid-switch inhibitor while one of
those connectors is connected and enabled in Hyprland, on a laptop where
HandleLidSwitchDocked is ignore, which is exactly the case logind would have
ignored had it counted the display. It releases the inhibitor when the display
goes. A compositor or logind that doesn't answer changes nothing, since
releasing then would suspend a closed lid. The inhibitor runs as a transient
user unit bound to the monitor watcher's unit, so a watcher that dies can't
leave the lid inhibited until logout.
The monitor watcher runs it alongside the clamshell sync. On a machine whose
external displays are all on connectors logind counts, it does nothing.
* Keep the focused workspace in front when a monitor goes away
When a monitor is unplugged, or the lid turns off the laptop panel, Hyprland
first warps focus to whichever monitor it lists first and only then moves the
removed monitor's workspaces over. The workspace you were on lands hidden
behind that monitor's own (Hyprland 0.56.2, CMonitor::onDisconnect).
monitor.focused fires before Hyprland records the new focus, so the monitor
still reported as active is the one being left: its workspace is remembered
until the event loop next turns, and a monitor.removed in that window focuses
it again once the removal has finished. An open special workspace already
comes along, and any focus or workspace change made before the restore runs
wins.
* Drive the keyboard backlight from the ambient light sensor
Laptops with an ambient light sensor and a keyboard backlight LED now light
the keys in the dark and dim them as the room brightens, inverse to display
auto-brightness. omarchy-brightness-keyboard-auto polls the sensor every five
seconds (preferring an IIO device that names itself ALS, and converting a raw
channel with its offset and scale) and maps lux onto the LED, with a deadband
so it doesn't chase noise. It pauses while the screen is locked or the lid is
closed.
A level set with the keyboard-brightness keys wins until the light changes
enough that it no longer fits. omarchy-brightness-keyboard records each level
it sets in the session's runtime directory (a private state directory without
one), so the loop can tell keys turned off on purpose from keys left off by
the lock screen or a restored zero, which it lights again.
The user unit runs it in the graphical session. Its ExecCondition asks
--available, so it is a no-op on machines without both a sensor and a
keyboard LED. First run enables it, and a migration enables it for existing
installs, writing the enable symlink itself when there is no live user
manager.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Gabe Ohlson <3355909+6abe@users.noreply.github.com>
* Defer an image build's hardware setup to the machine's first boot
An image is built away from the machine it will run on, so its hardware
leaves would probe the builder. While the image manifest the detector
already reads (/var/lib/omarchy/image/target) exists, omarchy-apply-hardware
queues every leaf of install/hardware/all.sh instead of running it and arms
omarchy-provision-hardware.service. On the first boot, before owner setup
and the login screen, omarchy-provision-hardware retires the manifest and
runs the queue on the real hardware: a failed step stays queued for the
next boot (exit 75), and the initramfs is rebuilt once after the last step
when a step changed its inputs or asked for it. Bluetooth starts its
service on that boot, whose target is already up. Root ignores any
environment override of these paths. Without a manifest, as on every
ISO install, hardware setup runs exactly as before. Nothing here fetches
package databases: an image carries its sync databases and every package
its steps install.
* Give an image-built root a UTF-8 locale when LANG is still C
A root built from a distribution tarball instead of the ISO (Arch Linux
ARM's ships LANG=C) never went through the ISO's locale step, so it ran
non-UTF-8. The config phase now selects en_US.UTF-8, generating it if
needed, but only when LANG is unset, C or POSIX: any named locale is
somebody's choice and stays. The ISO writes the locale before this runs,
so an ISO install is unchanged.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Let a platform's packages run their own boot, system and user setup
Three dispatch operations give a platform one place for the setup it
needs beyond Omarchy's, instead of inline platform branches in the setup
leaves. The last hardware leaf runs setup-boot, then setup-system, as
root, so an image build queues them with the others and its first boot
runs them, with image-first-boot (offline-safe, asking for the one boot
rebuild instead of building). setup-boot belongs to the boot package and
is required on Apple Silicon, where hardware setup has no other way to
run the Mac's boot setup; a failed setup-boot stops the leaf before
setup-system. setup-user runs as the user being set up, refuses root,
and gets only the user's home and session variables: from the last user
leaf at finalization (skipped inside an image build, where the detector
would read the build host) and again from first run once the session is
up. On Apple Silicon setup-boot resolves in omarchy-mac-boot's directory
and the other two, both optional, in omarchy-mac's, /usr/lib/omarchy/mac;
x86, generic aarch64 and Qualcomm register none, so all three are no-ops
there.
A failed finalization now keeps first run pending, like any failed
first-run step, instead of letting the other steps mark it complete.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Find batteries, lids and panels that aren't named like a PC's
Laptop detection assumed PC names: a battery called BAT*, a lid in /proc/acpi, a
touchpad or trackpad in the device name, a GPU backlight. Apple Silicon has
none of them (macsmc-battery, the SMC lid switch, apple-mtp-multi-touch,
apple-panel-bl), and some Framework and Chromebook firmware calls its battery
CMB0, so those machines lost the battery panel, laptop-only services, lid
handling, the touchpad toggle and display brightness.
- Battery presence goes by type instead of name, and skips a battery scoped
Device (a wireless mouse or keyboard), so a desktop with one isn't a laptop.
- Battery status takes the first UPower battery that powers the machine, reads
thresholds and cycles from it (BAT* stays the fallback), shows the magnitude
of a signed power_now, and rounds the percentage half-up for display like the
bar, while the charge-hold check keeps the unrounded value.
- A laptop is also one with any input device that reports a lid switch
(SW_LID), whatever its name: Apple's SMC input device has the same name on a
Mac mini, where it reports none. Whether the lid is closed comes from ACPI
where there is an ACPI lid, as before; otherwise logind is asked, with a
short timeout, only when a device reports a lid switch.
- A device named touchpad or trackpad still wins; only when there is none does
the touchpad match take a mouse udev marks as a touchpad (Apple's
multi-touch trackpad), never a touchscreen.
- The platform package can describe its displays in
/usr/share/omarchy-platform/displays.conf: backlights that never drive the
panel (a Touch Bar's), the panel's own where there is no GPU backlight, and
a display driver without DDC channels, where a probe walks unrelated I2C
buses for about 3 s: there only a connector with a ddc node is probed, and
a monitor without one reports no brightness instead of dimming the built-in
panel. Omarchy ships no such file and names no device in it; without it the
backlight choice and DDC probing are unchanged.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Ryan Hughes <1630358+ryanrhughes@users.noreply.github.com>
Co-authored-by: Sparsh Kumar <22640187+skkumarsparsh@users.noreply.github.com>
Co-authored-by: dl-alexandre <166029845+dl-alexandre@users.noreply.github.com>
* Keep the user's XDG config and state directories in platform user setup
setup-user runs with an allowlisted environment, which dropped
XDG_CONFIG_HOME and XDG_STATE_HOME. A platform's user setup reads the user's
configuration and keeps its setup markers there, so for a user who sets
either it looked in, and wrote to, the default directories instead. Both now
pass through with the rest of the session; XDG_DATA_HOME and everything else
still don't.
* Let a platform package add Hyprland defaults of its own
A platform's runtime package may need Hyprland defaults of its own (a Mac's
capture keys and three-finger workspace swipe, say), and Omarchy's config
should carry no branch for them. Three directories under the platform root,
/usr/share/omarchy-platform, now take them. The one installed platform package
owns that root, Omarchy reads it by literal path and ships nothing there, so
without a platform package nothing changes. Each file is loaded by its path,
so it can't shadow a module, and one that fails is logged and shown as a
notification while the rest of the config loads.
- hypr/defaults/*.lua loads before Omarchy's defaults. A
chord bound there replaces Omarchy's default for it, and a decorator it adds
runs before every later bind, so it can bind what has to run first. It sees
the command a bind runs, including the command a shell shortcut such as
{ menu = "root" } stands for when it binds the shell's global shortcut.
- hypr/settings/*.lua loads after Omarchy's defaults and
before the theme, for settings that replace Omarchy's.
- hypr/gestures/*.lua loads after the user's files, only for what must
see them: the gesture registry, which now records on every platform, lets a
default gesture step aside for the user's own.
The user's files load after the first two and can unbind or rebind any of it.
* Let a platform package name keys in the keybindings menu
The keybindings menu shows a key by its keysym, which on some keyboards is not
what the key says: on a MacBook, XF86MonBrightnessUp and Down are the F2 and
F1 keys, so the menu listed rows like "SHIFT + XF86MonBrightnessUp". A
platform package can now list names for such keys in /usr/share/omarchy-
platform/key-names, one "<keysym> <name>" per line, and the menu shows those
instead. The file is part of the menu cache's key, so installing or changing
it refreshes the menu. Without it the menu is as before.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: kx0101 <90087463+kx0101@users.noreply.github.com>
* Keep the top bar out of a panel's camera cutout
A laptop panel with a camera notch at its top (Apple Silicon MacBooks, for
one) covered the middle of the top bar, and a bar shorter than the notch left
a sliver of every window beside the camera. Omarchy knows no panel's cutout
itself: the platform's own package describes them in
/usr/share/omarchy-platform/display-cutouts.json, by
connector, physical mode and the rows the cutout covers. On a described panel
a top bar is never shorter than the cutout, its center section sits beside
the right one, and hiding it parks all of it; [bar] notch-height sets that
floor by hand, on that panel only. Without a description, which is every
machine without such a package, the bar is as before.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Keep PC and Intel Mac quirks off Apple Silicon
A few hardware steps and commands written for PCs and Intel Macs do the wrong
thing on Apple Silicon:
- The Intel Mac Wi-Fi quirk turns off the Broadcom firmware supplicant for
every Mac whose DMI vendor is Apple and whose chip is on its list. Apple
Silicon reports Apple Inc. through U-Boot's DMI tables, and the M1 Pro and
Max's BCM4387 is on the list, but there the firmware supplicant is the one
that works: turning it off makes firmware commands time out and breaks
scanning. The install step and its migration now skip Apple Silicon.
- fnmode=2 for Apple-like keyboards is left to the Mac's own platform package
on Apple Silicon, where the built-in keyboard's top row depends on it.
- The SPI keyboard check reads the DMI product name without failing the
hardware pass where there is none.
- The Windows VM, an x86_64 guest under KVM, runs on x86_64 only, and direct
boot (U-Boot finds the bootloader and makes its boot entries itself)
refuses Apple Silicon; the menu hides both there. Hibernation setup skips
it; its kernel has no hibernation today, and resume there is not wired.
Everywhere else each step runs as before.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Let a platform's runtime package adjust the apps it needs to
Some apps need a platform's own touch once they are installed. On Apple
Silicon, Chromium-family browsers must decode video in software (the hardware
decoder renders green frames in them), and Steam only runs through a
FEX launcher, which then depends on Steam and must go before Steam can be
removed. Until now the Mac runtime did this with inline branches in the
installers.
Two optional dispatch operations take their place. post-install <app> runs
as the user once an installer has installed the app and finished its own
setup of it: every browser install (after its flags file, before it says the
browser is installed) and Steam's (after the package, before the 32-bit
drivers and the first launch). pre-remove <app> runs before Steam's removal
drops its packages. Where a platform registers them they refuse root (a
platform with nothing registered is a no-op, so sudo installs still work
there) and get the same home and session variables as setup-user, and a
failure fails the install or stops the removal. On Apple Silicon they
resolve in omarchy-mac's directory; every other platform registers none, so
installs and removal run as before there.
Steam's 32-bit driver step also stops failing the install on a machine with no
Intel, AMD or NVIDIA GPU (Apple Silicon, but also Snapdragon or a VM): it has
nothing to add there and now says so.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Ask the platform's boot package before an update takes over its files
When an Omarchy package's upgrade stops only on files no package owns, the
update moves them aside and retries. On a Mac some of those files belong to its
boot chain or were written by its image (initramfs and pacman configuration,
say), and moving them aside can leave it unable to boot or update. The Mac
runtime refused those paths with an inline list.
update-takeover <path>... asks the platform's boot package instead, before
anything moves: read-only, exit 0 lets the takeover go ahead, anything else
keeps every file and the update fails as it did before the retry existed. The
update resolves it as the user and runs it with sudo only where it resolves,
inside the authorization the move needs anyway. On Apple Silicon it is
required: a Mac without its boot package, or with one too old to answer, keeps
its files. Every other platform registers none, so the takeover goes ahead as
before, asking no one.
* Install the Mac's package set on Apple Silicon
omarchy-pkg-defaults already composes install/omarchy-<platform>.packages
after the base and aarch64 lists, but Apple Silicon had no list, so a Mac got
the generic aarch64 set: no omarchy-mac or omarchy-mac-boot, whose entrypoints
the dispatcher requires there.
install/omarchy-apple-silicon.packages names the Mac's runtime and boot
packages, plus the defaults an owner may remove: the startup-volume tool,
hardware video decode, Widevine, and wf-recorder, since gpu-screen-recorder
can't capture on Apple Silicon and screen recording falls back to it. What a
Mac can't work without (its speaker stack, PipeWire's PulseAudio server and
realtime scheduling, the GPU's Vulkan driver) comes as omarchy-mac's
dependencies, so a new Mac driver needs no Omarchy change. Other platforms
compose exactly what they did.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Give existing aarch64 machines the zram swap Omarchy configures
omarchy-settings configures swap on zram and tunes memory reclaim for it.
x86_64 gets zram-generator from its installer and new aarch64 installs from
their default packages, but an aarch64 machine installed before
omarchy-aarch64.packages named it has no generator, so the configuration asks
for a device nothing creates.
Migration 1790328426 installs zram-generator where the platform's default
packages name it and it is missing, and starts the zram swap wherever it is
down, unless no device is configured (then the generator makes no swap unit)
or the administrator masked it. A swap that won't start, or whose unit can't
be read, only warns: the generator's unit starts with every boot, so a reboot
retries it, and failing the migration would stop every update. A failed
install waits (75) for the next migrate, as it does where the platform can't
be told, changing nothing. It keeps the name it has in omarchy-mac, so Macs
that already ran it skip it. It also retires an /etc copy of the drop-in
identical to the packaged one, which earlier Mac setups made and which would
hide the packaged file's updates; an edited copy stays. On x86_64 the default
packages don't name the generator and there is no copy, so it does nothing.
* Keep an ARM machine's repositories when it changes channel
omarchy-refresh-pacman and install finalization copied x86_64 templates
everywhere, so on an aarch64 machine a channel change or a finished install
wrote Arch Linux's x86 [core]/[extra]/[multilib] and dropped Arch Linux ARM
and the platform's own repositories.
Channel changes now tell the platform apart. x86_64 is unchanged. On aarch64
the machine's configuration is kept, with its repositories, mirrors and
signature policies, and only the [omarchy] server moves to the new channel;
a configuration whose Omarchy server can't be identified is refused
unchanged, and one with no Omarchy repository gets it at the end (not on
Apple Silicon, whose own repository must not come first). Only pacman.conf is
backed up and rewritten, straight from this process through sudo, never
through a file another process could change first.
Before that, a channel must be qualified for the platform: a signed upgrade
and reboot tested there, not only a published repository. x86_64 keeps
stable, rc and edge; Snapdragon and other aarch64 machines take edge, as
dragon does; Apple Silicon takes none until the Mac packages qualify on edge.
The refresh then checks, as the user, that the channel's omarchy.db
publishes the runtime pair, and on Apple Silicon omarchy-mac and
omarchy-mac-boot. omarchy-channel-set refuses an unqualified channel before
anything else, the dev confirmation included.
Install finalization writes a whole template, as before on x86_64. Other
aarch64 platforms get Arch Linux ARM (dragon's pacman-aarch64.conf and
mirrorlist-aarch64, byte for byte) with its keyring, plus Omarchy on a
qualified channel. Apple Silicon gets omarchy-mac's template on a qualified
channel and keeps its image's configuration otherwise. In an image build,
finalization leaves the keyring to the machine's first boot, so machines
flashed from one image don't share a pacman master key. On an unqualified
channel, a machine that gets no Omarchy repository is told why.
Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jim Martin <1334144+jdvmi00@users.noreply.github.com>
* Keep Arch Linux ARM's signing keys current on ARM machines
omarchy-update-keyring reinstalls archlinux-keyring before every system
upgrade, since keys change without a version bump. An aarch64 machine's Arch
Linux ARM repositories are signed with keys from archlinuxarm-keyring, which
nothing refreshed, so a rotated ALARM key failed the upgrade that followed.
On an aarch64 platform where archlinuxarm-keyring is installed, it is
reinstalled in the same transaction. On x86_64 the keyring step runs exactly
as before, even where that package happens to be installed.
* Mark the runtime's platform files as deciding at runtime
The omarchy-settings recipe leaves the mkinitcpio, Limine, memory and USB
drop-ins out of its aarch64 package, because they used to assume x86_64. That
leaves an aarch64 install without the HOOKS baseline its initramfs needs, and
without the zram and reclaim settings every other Omarchy machine gets.
Each of those files now decides at runtime whether it applies: the HOOKS
baseline asks the platform, thunderbolt is added only where the kernel has the
module, the Limine files are read only where Limine is installed, the oomd
drop-ins are inert without systemd-oomd, aarch64 installs the zram generator
the zram drop-in needs, and the USB autosuspend option is ignored by a kernel
that builds usbcore in. default/settings-runtime-profile says so, file by
file; a recipe that finds it ships the same files on every architecture
(omacom/omarchy-pkgs#638), and older sources keep today's aarch64 package. A
test checks that every file it names exists and that each runtime decision is
in place.
* Set the wireless regulatory domain from the owner's timezone at first-boot setup
A deferred install (an Apple Silicon image, an OEM ISO install) runs its
hardware steps before the owner picks a timezone, on the UTC placeholder,
which names no country, so install/hardware/set-wireless-regdom.sh leaves the
regulatory domain unset and the radios stay on the world domain. Owner setup
now runs that step again once the owner's timezone is set, and applies it to
the radios through wireless-regdb's helper, since no reboot follows setup. An
up-front install, which picks the timezone before its hardware steps, is
unchanged.
* Ask again for a keyboard layout first-boot setup could not apply
First-boot setup kept going when the chosen layout did not take: loadkeys
errors were ignored, a keymap localectl did not list kept the default, and a
failed or partial write of /etc/vconsole.conf was only logged. The owner then
typed the disk password in one layout while the boot files asked for it in
another, and could only unlock the disk by typing US-style.
The keyboard step now fails closed. On a console loadkeys must succeed, the
keymap must be in localectl's list exactly (a list localectl cannot produce
counts as a failure too, logged apart), and after systemd-firstboot or the
localectl fallback, /etc/vconsole.conf is read back in a clean shell and must
name the chosen keymap with an XKB layout. Any failure is logged, the owner is
told the layout could not be set, and the layout is asked for again, so the
password form is never reached with a layout that did not take.
* Refresh a platform's own signing keyrings before an update
omarchy-update-keyring reinstalls archlinux-keyring (and Arch Linux ARM's on
aarch64) before the system upgrade, so a rotated key never fails that
upgrade's signature checks. A platform's own repository has a keyring of its
own that nothing refreshed: on Apple Silicon, asahi-alarm-keyring for
[asahi-alarm].
A platform package now lists those keyrings in
/usr/share/omarchy-platform/keyrings, one package name per line, and
omarchy-update-keyring reinstalls each one that is installed in the same
transaction. It adds no repository the machine didn't already trust. A line
that isn't one keyring package name is skipped with a warning, so a mistake in
the list can't stop every update. Without the file, as on x86 today, the step
runs as before.
* Drive an untyped default input from the audio panel
Quickshell types only exact media classes, so a virtual source (an
Audio/Source/Virtual: EasyEffects' source, or the microphone mapping a
platform package adds) has no PwNode.audio. With one as the default input the
audio panel showed the input at 0% and its slider changed nothing, the peak
meter stayed flat, the microphone widget read it as muted, and the panel could
not list it unless it was already the default.
Such an input now goes through wpctl for its volume and mute (UntypedInput in
shell/Commons, one reader and one pactl subscribe for the whole shell), the
panel meters it with the new omarchy-audio-source-level, and the input list
takes an untyped node PulseAudio lists as a source.
A platform whose audio runs through its own processing can also name nodes
that are neither devices nor apps in /usr/share/omarchy-platform/audio.json,
which Omarchy ships none of: the panel and the microphone widget leave them
out, and a "replaced" node only while its replacement exists. Without the
file nothing is hidden.
On every machine: the input list leaves out a source whose ports are all
unavailable (an empty headset jack) unless it is the default, as the output
list does for sinks; choosing an input no longer moves a filter's own capture
(a source output with node.link-group), which would feed the filter from the
new input or from itself; the shell's own level meters no longer light the
microphone widget; and omarchy-audio-sink-availability reads pactl in the C
locale for sinks too, since "not available" is translated.
* Give first-boot setup an XKB layout for keymaps systemd cannot map
systemd-firstboot writes an XKB layout only for keymaps listed in
kbd-model-map. Nine the setup form offers are not (Colemak, Azerbaijani,
Bulgarian, Czech, Swiss German, Kyrgyz, Norwegian, Polish, Ukrainian), so it
wrote KEYMAP= alone, the readback refused it, and owner setup could never get
past the keyboard step for them.
Setup now adds the XKB settings itself when firstboot left none, matching the
console map so the shared password types the same on the desktop: kbd's cz is
QWERTY, bg-cp1251 phonetic and azerty French AZERTY, and non-Latin layouts keep
a US layout to switch to, as kbd-model-map's own rows do. A test checks every
layout the form offers ends with an XKB layout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Leave keys a firmware key turned off dark
The ambient keyboard backlight treated any 0 the keyboard-brightness keys had
not recorded as an accident and relit it within five seconds. Firmware Fn keys
(Fn+Space on ThinkPads and Framework laptops, Fn+F10 on Dells) write the LED
directly, so on those laptops turning the keys off never stuck.
The lock screen's off now records a blank, and only that blank or a 1%
leftover is taken back; any other off is a choice that pauses auto like the
keys do. A wake clears the blank only once the restore succeeded, the command
runs one at a time so a blank racing a wake can't leave the LED and record
disagreeing, and the loop drops a blank once it has taken the keys back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Drop the optional-install availability guards
Each menu open checked every Install row against the sync databases, which
costs most of a second of pacman work on every machine to hide rows only an
ARM machine is missing. The fix for those is publishing the ARM packages, not
hiding the rows, so this removes omarchy-pkg-available, the Install rows'
availability and architecture guards, and reinstall's skipping of defaults no
repository offers.
The Windows VM row keeps its x86_64 guard, since no package fixes a guest that
needs an x86_64 host, and reinstall keeps its aarch64 channel handling.
This reverts commit aea001cbde4d0852b311ef42b5e8c380c719f833.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Put the current root back when the factory root cannot take its place
Factory reset moved the running root aside, marked the reset committed, then
renamed the staged factory root to @. If that second rename failed, cleanup
saw a committed reset: it skipped the boot package's rollback and kept the
throwaway LUKS slot, and since the swap had not happened it deleted the staged
root as well, leaving nothing at @.
The reset now commits only once the factory root is at @. A failed activation
renames the old root back first, then cleanup rolls the platform's boot state
back and revokes the throwaway slot. If that rename fails too, both roots are
kept, cleanup still rolls back and revokes, and the error names the rename
that recovers the machine. The generic Limine path has already rebuilt its
boot files from the factory root by then, as it had before this change.
* Leave other recorders alone when the saved recorder pid is stale
A saved pid that no longer named a recorder made status and stop select every
gpu-screen-recorder and wf-recorder the user ran, so stopping could interrupt,
and after five seconds kill, a recording started somewhere else.
A stale pid now means no recording of ours is running: the toggle starts a new
one, and --stop-recording finds nothing to stop. Stop acts on the recorder the
status check selected rather than selecting again. Without a saved pid, as for
a recording started before the pid was saved, every recorder the user runs
still counts.
* Name the audio a wf-recorder recording could not capture
With desktop and microphone audio both requested, wf-recorder records a null
sink fed by two loopbacks, and a loopback that failed to load was ignored: the
recording went ahead without that source, or silent when both failed. A
missing null sink or default device dropped audio just as quietly.
A source that cannot be captured is now left out with a notification naming
it, the loopbacks that did load are unloaded, and the recording carries the
source that remains, or no audio track at all.
* Show the Edge, Spotify and Dropbox installers on x86_64 only
These vendors currently publish Linux builds for x86_64 alone (the AUR's
microsoft-edge-stable-bin and spotify are arch=('x86_64'), and Dropbox lists
no Linux ARM client), so no native aarch64 build is available and choosing
one on an ARM machine only fails. Like the Windows VM row they keep a uname
check, which the menu answers once per open without asking pacman. The other
Install rows are unchanged.
* Revoke the generic reset's throwaway slot when the reset fails
The generic Limine path added its throwaway LUKS slot without recording it,
so a reset that failed before activation never revoked it. Once the factory
root's UKI was on the ESP, a failed activation that put the old root back left
a machine that booted the factory kernel and unlocked unattended with a key
readable on the ESP, while the screen said the current root was back.
The generic slot is now recorded like the platform's and revoked by cleanup
whenever the reset fails before the factory root is active. A failed
activation on that path says the boot files were already rebuilt from the
factory system and to run limine-update before rebooting.
* Clear a stale recorder pid when a recording starts
The recorder's pid is saved only once its file exists. Until then a pid left
by a recording that ended without a stop still counted as stale, so pressing
the toggle again while the new recorder started found nothing of ours and
could start a second recording. Starting a recording now clears that pid,
and during startup the toggle falls back to the user's recorders as before.
* Word the dropped-audio notice for every reason a source is left out
The notice said the dropped source "could not be captured", which was wrong
when the null sink itself failed to load: the microphone was never tried, and
desktop audio recorded alone as it always has. It now says the source could
not be added to the recording, true for a missing device, a failed loopback
and a failed null sink alike.
* Show the Heroic and Minecraft installers on x86_64 only
Heroic publishes Linux builds for x86_64 alone (its arm64 builds are for
macOS and Windows), and Mojang's Linux launcher is a single x86_64 tarball.
They join Edge, Spotify and Dropbox under the same uname check, so every row
whose vendor ships no native Linux aarch64 build is treated alike.
* Ask the recorder script whether its stop has a recording to end
The bar indicator and the menu's Stop Screenrecording row asked the process
helper whether any recorder the user runs was alive, while the toggle's stop
acts only on the recording it saved. With a stale saved pid alongside an
unrelated recorder, both offered a stop that did nothing.
omarchy-capture-screenrecording --status answers with the selection the stop
uses, and both now ask it. Status needs no recordings directory and never
notifies, so the recordings-directory check moved below argument parsing and
skips it.
* Copy every platform's pacman templates into place, as x86_64 does
On aarch64 a channel change edited the machine's pacman.conf in place: an awk
pass found the [omarchy] server and rewrote its channel, appended a section
where there was none, checked the channel's omarchy.db first, and gated it all
on a per-platform table of qualified channels. Install finalization appended
Omarchy to a template, or on a Mac copied one bundled inside omarchy-mac. What
a machine ended up with was never in the repository.
Each platform now has a pacman.conf and mirrorlist per channel, copied whole:
x86_64's stay in default/pacman, Snapdragon and other aarch64 machines use
default/pacman/aarch64, and Apple Silicon default/pacman/apple-silicon, with
Omarchy and Asahi ALARM ahead of Arch Linux ARM. Omarchy publishes aarch64
packages on edge alone so far, so every ARM channel points at edge for now and
channel changes work there; moving a channel is a template edit. A channel
without both files is refused before anything changes, including a dev
checkout's own templates. Reinstall goes back to resetting to stable, and the
update confirmation loses the missing-repository notice it no longer needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Revert "Copy every platform's pacman templates into place, as x86_64 does"
This reverts commit 9a07e138cc6c3a19b58d2f3d7eb536dd3042ccc5.
* Copy every platform's pacman templates into place, as x86_64 does
On aarch64 a channel change edited the machine's pacman.conf in place: an awk
pass found the [omarchy] server and rewrote its channel, appended a section
where there was none, checked the channel's omarchy.db first, and gated it all
on a per-platform table of qualified channels. Install finalization appended
Omarchy to a template, or on a Mac copied one bundled inside omarchy-mac. What
a machine ended up with was never in the repository.
Each platform now has a pacman.conf and mirrorlist per channel, copied whole:
x86_64's stay in default/pacman, Snapdragon and other aarch64 machines use
default/pacman/aarch64, and Apple Silicon default/pacman/apple-silicon, with
Omarchy and Asahi ALARM ahead of Arch Linux ARM. Omarchy publishes aarch64
packages on edge alone so far, so every ARM channel points at edge for now and
channel changes work there; moving a channel is a template edit. A channel
without both files is refused before anything changes, including a dev
checkout's own templates. Reinstall goes back to resetting to stable, and the
update confirmation loses the missing-repository notice it no longer needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Drop the migrations no installed machine needs
Fresh installs mark every migration done, and every Apple Silicon machine is a
fresh install, so the Mac handoff to omarchy-mac-boot (and the migrate
operation it called), zram for aarch64 installs that predate the package list,
the Brave Widevine link and the Broadcom migration's Apple Silicon guard never
run anywhere. The exit-75 deferral in omarchy-migrate existed only for two of
them. Keyboard backlight auto stays on new installs through first run rather
than switching on for existing ones, and migration 1786605598 goes back to
quattro: the mkinitcpio drop-ins arrive by package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop the generic changes Mac support doesn't need
The pacman platform guard (and the detector copy omarchy-settings shipped for
it, which the HOOKS baseline fell back to), the lint against uname gates, first
run staying pending after a failed finalization, run_logged echoing failures to
stderr, half-up battery rounding, hiding empty-jack inputs and leaving a
filter's capture alone when choosing an input all rode along with the Mac work
without serving it. Each goes back to quattro's behaviour and can come back on
its own merits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop the platform Hyprland slots
A platform package could load its own Hyprland binds, settings and gestures:
binds that replaced Omarchy's chords and decorated every later bind, settings
ahead of the theme, and gestures after the user's files, which needed every
hl.gesture call wrapped so a default could step aside. On a Mac those carried
macOS habits (a three-finger workspace swipe, tap-to-click off, menus opening
on the built-in screen, other screenshot and keyboard-backlight chords), not
anything the hardware needs, and Omarchy should behave the same on every
machine. The one bind a Mac does need, its lid switch under the SMC's name,
joins Omarchy's own lid binds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep aarch64 machines off the release channels until a release supports them
Every aarch64 channel's template pointed at the edge repository, but a switch
to stable or rc installs the release-named omarchy and omarchy-settings. On
edge/aarch64 those are the 4.0.4 release line, which has no omarchy-hw-platform,
lifecycle dispatch or omarchy-pkg-defaults, so `omarchy-channel-set stable` on
a Mac replaced the quattro runtime it runs with one that cannot run it, and
succeeded.
aarch64 platforms now have edge templates alone, so a switch to stable or rc is
refused before anything changes; adding those templates once a release
supports aarch64 opens them. Refresh with no channel named, as reinstall runs
it, and install finalization for a channel the platform has no templates for
take the platform's default channel: stable on x86_64, edge on aarch64.
* Leave a filter's own capture alone when choosing an input again
d0b94d98c returned omarchy-audio-input-set-default to moving every source
output, as not serving Mac support. It does: asahi-audio's microphone DSP on
Apple Silicon records the raw array through a filter-chain capture stream
(audio_effect.jNNN-mic, node.link-group "filter-chain-..."). On an M1 Pro,
choosing the Mac's microphone in the audio panel moved that stream onto the
DSP's own output, and the microphone recorded digital silence (-91 dB, from
-55 dB of room noise) until PipeWire restarted. With the skip it keeps
recording (-54 dB).
This restores the skip of source outputs carrying node.link-group, and its
test, as they were before that commit.
* Say aarch64 offers edge alone where the templates are described
The aarch64 edge templates, the template helper and the update-process docs
still said every platform has a template per channel and every ARM channel
installs from edge, which the previous commit made untrue.
* Point the Mac packages at omacom/omarchy-mac-pkgs
omarchy-mac and omarchy-mac-boot now live in omacom/omarchy-mac-pkgs, with
each package at the top level. The dispatcher, its documentation and the
Apple Silicon package list name that repository, and the drop-in test's
fixtures, byte for byte the same as omarchy-mac-boot/files/etc/mkinitcpio.conf.d
at 4399105, cite it there.
* Stop naming a platform migration among the dispatcher's callers
The migrate operation went with the Mac migrations, and no caller hands a
machine to a platform migration any more, so the header lists only the
flows that still dispatch. The optional-operations comment is rewrapped.
* Keep the Intel Mac Broadcom migration off Apple Silicon again
A fresh install marks every migration done only for the user it creates, so
an account added later runs them all on its first update. On an Apple
Silicon Mac this one matches: an M1 Pro reports sys_vendor "Apple Inc." and
a BCM4387 (14e4:4433), so it would append feature_disable=0x82000, which
stops that chip scanning once brcmfmac reloads. Restore the guard and its
test case.
* Say when Steam's post-install hook runs now that the drivers go first
Steam's installer now adds the 32-bit drivers before Steam itself, so the hook runs after both and before the first launch.
* Release fallback audio after the recorder exits
When wf-recorder ends outside the normal Stop path, status rejects the saved PID before unloading the combined-audio loopbacks. Release those owned modules when the saved recorder is gone, so the next status or stop call ends microphone capture without signaling another recorder. Leave a mix without a saved PID alone: its recorder can still be starting.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Codex Medium <noreply@openai.com>
* Serialize recording state changes and stale audio cleanup
Status can finish checking an exited recorder after another command has replaced its audio modules, and stop finalization can outlive the recorder while a new recording starts. Share a lock across start, stop and cleanup so each command finishes updating its resources before another changes them. Status uses a read-only check when the lock is busy, keeping the bar responsive during the picker. Close the lock descriptor in persistent children so recording and preview cleanup cannot retain it.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
* Keep automatic and manual keyboard brightness updates together
A brightness key can update the LED and its chosen-level record after the automatic loop checks for a manual change but before it applies a new target. The automatic write then overwrites the user's choice and deletes the record. Take the manual command's lock for the whole tick, skipping a busy tick and releasing the descriptor on every return. Create the private fallback directory before opening the lock outside a session runtime directory.
The normal lock screen waits five seconds before blanking, so an ordinary tick should already see the session locked. The regression exercises a brightness key arriving within a tick instead of relying on an implausibly delayed lock-state check.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Read the split hook baseline in the NVIDIA migration
The NVIDIA firmware migration decides whether to rebuild by sourcing the hardware and filter drop-ins. With HOOKS now defined in the earlier baseline, that partial read treats every hybrid machine as if kms had been removed. Source the new baseline first when it exists, keeping the legacy two-file layout working and skipping rebuilds when the effective hooks still include kms.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Use a hardware helper for x86-only menu entries
Keep the x86_64 availability checks for Windows and vendor-specific installers in a hardware predicate, so menu conditions use the same command style as other hardware checks.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
* Keep restored keyboard levels from pausing automatic brightness
Idle wake, quick unlock and hibernate can restore a saved level older than the ambient loop current level without the lock blanking first. Treat that restoration as a one-tick event, preserving existing manual pauses and the normal blank-and-restore path. Consume unmatched and unchanged restore records too, so a later firmware key cannot look like the earlier restore.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Preserve restored keyboard levels across a later lock blank
The ambient loop skips a locked session, so a restored level may still be unconsumed when the next blank saves it. Carry that origin through blanking and restore failures so fingerprint unlock cannot make the old saved level look like a manual choice. A brightness key replaces the marker with its chosen level and retains the ordinary blank behavior.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Preserve firmware brightness choices before a lock blank
Firmware keys change the LED without updating the record. Carry a pending restoration through the next blank only if the observed LED still matches that restoration, or is still zero from its previous blank. A firmware change otherwise follows the ordinary blank-and-restore path so automatic lighting pauses on the chosen level.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Change only the disk password in omarchy-drive-password
Changing the system disk's password also set the login and root passwords
to match. That changes account-password policy on every platform, x86
included, and overwrites passwords someone chose to keep different, so it
moves to its own change with an explicit confirm.
The disk change itself stays as it was: the new key must open the disk and
the old one must not before the change counts, the journal finishes an
interrupted change, and where the platform's boot checks prove the key
slots, the owner's new slot is recorded before the run finishes. The
journal's confirmed phase is now "record" and no longer needs a login user.
* Cancel a pending recording start instead of queueing behind it
Starting a recording holds the recorder lock through the picker and until the
recorder creates its file. A second toggle or a stop then waited on that lock,
with no end while a portal pick or a stuck recorder never finished, and once
the start gave up the queued toggle began another recording.
While a start is pending, a toggle or stop that cannot take the lock within a
second now leaves a cancel marker and returns. The start checks it after the
picker, after the webcam overlay and while waiting on its recorder, then stops
that recorder and releases its audio. A recorder other than the portal's that
has not created its file after 15 seconds is stopped and the next backend
tried.
* Move automatic keyboard lighting to its own change
Ambient-light keyboard backlight is a new feature on every laptop with the
sensor, not something Apple hardware needs to run Omarchy, so it leaves this
change for a separate review. The keyboard-brightness command returns to
upstream's version, and first run no longer enables the user service.
* Move workspace refocus on monitor removal to its own change
Keeping the viewed workspace in front after a monitor is unplugged changes
desktop behaviour on every machine, not just Apple hardware, so it leaves
this change for a separate review.
* Move prebuilt-image hardware deferral to its own change
Building an image away from its machine needs a manifest the detector reads,
a queue of hardware steps for the first boot, a first-boot service and a
per-machine pacman keyring. An install on the machine itself needs none of
it, so it leaves this change for a separate review with an installer and
package boundary of its own. Hardware and user setup now run where they are
installed, and the platform setup operations take no image argument.
* Move the Qualcomm platform to its own change
Snapdragon support is separable from Apple compatibility, so the detector no
longer names a qualcomm platform: a "qcom," device tree is generic aarch64,
which Snapdragon already behaves as everywhere else here, and a tree naming
both Apple and Qualcomm is still refused. linux-firmware-qcom's platform list
leaves with it.
* Keep a cancel that arrives while a recording start finishes
A stop could land after the start's last cancel check, while it saved its
recording as started, and be dropped with the marker. The start now removes
its starting marker before a final check, a request reads that marker only
after leaving its own, and one that finds the start gone waits for the lock
and acts unless the start already used its request. A recorder ended by a
cancel or a readiness timeout gets two seconds after TERM before KILL, so a
recorder ignoring TERM cannot hold the start and its lock.
* Defer an image build's hardware setup to the machine's first boot
Brings back prebuilt-image support split out of #14431 so it gets its own review. An image built away from its machine names its target in a root-owned manifest the detector reads; hardware setup is queued for the machine's first boot, run there by omarchy-provision-hardware, which also makes the machine's own pacman keyring; and the platform setup operations learn they run on an image's first boot. Apple Silicon images built by the omarchy-mac installer need this; an install on the machine itself does not.
* Rebuild the initramfs when deferred setup changes module options
mkinitcpio's modconf hook copies /etc/modprobe.d into the initramfs, but the
deferred first-boot setup only compared mkinitcpio's configuration and the
kernel command line when deciding whether to rebuild. A step that only wrote
module options, as nvidia.sh does, left the old ones in the boot image.
* Put the Omarchy repository first on aarch64, as on x86_64
quattro now searches [omarchy] before the Arch repositories (
|
||
|
|
9d48fc402e |
Install gliff by default and show when a remote session is active (#14712)
* Install gliff by default Gliff is the Hyprland remote desktop over SSH. Ship it in the base package set for new installs, with a migration for existing ones. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Show a bar indicator while a gliff remote session is active The machine being controlled should make it obvious that someone is on it. A first-party remote-session service watches Hyprland's screencast events, which gliff-server triggers through ext-image-copy-capture, and probes for the server process, picking up the ssh client address from its environment. The RemoteSession indicator in the default tray lights up in the urgent colour while a session runs, and a notification announces each session start and end. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Address review of the remote session indicator Probe only the current user's gliff-server processes, forward refresh through the third-party service proxy so cloned indicator widgets can re-probe, exercise the Hyprland screencast event path in the service test with a grim capture instead of calling refresh by hand, and describe the indicator as status only in the manual. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Drop the remote session notifications The bar indicator is the signal; a toast on every connect and disconnect is noise on top of it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Tighten remote session detection after review Hyprland reports a screencast stopped as soon as frames pause for half a second and says nothing more when the server later exits, so poll every two seconds while a session is active instead of trusting a stop event. Match only the screencast event, not its screencastv2 twin, and debounce the burst around one transition so it costs one probe. Move the probe into its own script that the service and the test both run, poll for state in the service test instead of fixed delays, and drop the unused session count. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
c24889096e |
Add selectable Voxtype and Superwhisper dictation (#14552)
* Add selectable dictation backends * Move dictation migration after repository priority update * Extract backend-specific dictation installers * Keep dictation installer dispatcher executable * Call dictation backend installers directly * Drop legacy Superwhisper migration and runtime cleanup * Separate dictation defaults from generic recording protocol * Add backend-specific dictation removers * Preserve existing Voxtype selection during upgrade * Make dictation backend selection safe and retryable * Show the dictation indicator only for selected Voxtype * Allow switching away from stopped Superwhisper * Remove the built-in bar dictation indicator * Store default dictation selection under defaults * Rename dictation backend selector to use * Move dictation removers into their own menu category |
||
|
|
b83d3df084 | Qualify shell palette references for Qt 6.12 (#14553) | ||
|
|
902fd8aebd |
Play matching background intros at boot and on theme switches (#9639)
* Play matching theme background intros at boot * Add Tokyo Night sunset lake boot intro * Stage Tokyo Night boot intros on update * Add user controls for background boot intros * Optimize Tokyo Night boot intro videos * Add Tokyo Night cityscape boot intro * Add Ristretto boot intro videos * Add Solitude boot intro videos * Add White boot intro videos * Add Vantablack boot intro videos * Add Miasma nature boot intro * Add Matte Black boot intro videos * Add Lupine wave boot intro videos * Add Last Horizon blink boot intro * Add Everforest tree tops boot intro * Add Ethereal cosmic boot intro * Add four Retro 82 boot intros * Add Tokyo Night Quattro boot intro * Add Catppuccin Latte color fade boot intro * Add five Gruvbox boot intros * Expand Retro 82 boot intro coverage * Complete Ethereal boot intros * Complete Retro 82 boot intros * Stage boot intros for the active theme * Add Catppuccin boot intros * Complete Tokyo Night boot intros * Expand Lupine boot intros * Add Osaka Jade mountain boot intro * Prime boot intros before revealing wallpaper * Complete Osaka Jade boot intros * Keep kernel migration test valid after timestamp reuse * Shorten packaged boot intros to six seconds * Revert "Shorten packaged boot intros to six seconds" This reverts commit 5530403aa9e789b977a1a1a77ea7acf37a9e3598. * Document five to seven second intro target * Ease Matte Black intros into six seconds * Play boot intros through OWE instead of a shell player The merged desktop video path removes BackgroundVideo.qml and makes BackgroundMedia image-only. This keeps the intro feature by resolving the matching intro as before and playing it with 'owe intro', which owns the decode and the reveal. OWE 0.2.3 adds that command. The shell holds the theme color while resolving and cancels with 'owe intro-stop'. * Make boot intro startup atomic * Retry boot intro through login window * Document required OWE intro protocol * Add Last Horizon video boot intros * Play boot intros with released OWE Use `owe intro` instead of the unreleased intro-prepare/intro-commit protocol from omacom/owe#5. The boot is recorded before playback, and OWE's intro_result status decides whether a failed intro never started (release the boot for retry) or was interrupted (keep it consumed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replace Matte Black boot intros with shorter cuts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hide the still behind black until the boot intro starts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the Everforest boot intro Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document that boot intros should open dark Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Refine the Osaka Jade boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Skip compositor fades on the background layers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the Catppuccin Latte boot intro Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the Catppuccin boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the White boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the Vantablack boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the Gruvbox boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the Retro 82 boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the Solitude boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Rework the Ristretto boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the Tokyo Night boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Complete the Lupine boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Complete the Last Horizon boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Complete the Miasma boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the Nord boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the Rose Pine boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the Flexoki Light boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the Hackerman boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the Kanagawa boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the Lumon boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fade in the Last Horizon intros from black Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Animate the Catppuccin boot intros from their wallpapers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF * Animate the third White boot intro from its wallpaper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF * Animate the Flexoki Light, Lumon and Rose Pine plants intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF * Shorten the Ethereal boot intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF * Animate the remaining placeholder intros from their wallpapers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF * Replace Catppuccin intros with themotion scenes Totoro moonrise, waves swelling from a line, and the blue eye tracing itself open, rendered by https://github.com/yamz8/themotion (scenes/catppuccin/*). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KAPp37DLjmKQy8GZr3vFvF * Re-encode Catppuccin intros with exact final frames Same motion; the settled frames are encoded at a fixed high quality so the handoff lands on the wallpaper (themotion 0c5e86c). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replace Lumon intros with themotion scenes United in severance draws its lines and globe with a small pen-tip light; Opinions equally wakes inside the picture's own screen and types out the mark and tagline. Both keep the wallpaper's proportions, so the handoff holds its shape. Made with https://github.com/yamz8/themotion (scenes/lumon/*). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Land the Last Horizon intros exactly on their wallpapers Same footage and timing; the last 0.4 s now blends into the wallpaper as the shell draws it (libwebp decode, BT.709 limited range), with the settled frames at high quality. Re-finished with themotion fit (09f156e). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add Nord video boot intro for Night Hawks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replace Rose Pine intros with themotion scenes Funky shapes pop in on a jelly spring and the doodles draw on (replacing the reversed stock clip), the dot map spins in from a swelling dot, and the plants spring up before the OMARCHY letters drop in. Made with https://github.com/yamz8/themotion (scenes/rose-pine/*). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replace Hackerman intros with themotion scenes The synthwave terrain unfolds as the sun rises and the valley glow flares into the floor; the geometric network bursts out from a triangle at its core and locks into place. Made with https://github.com/yamz8/themotion (scenes/hackerman/*). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replace White 3-white with a themotion scene and land 1-2 exactly 3-white folds and slides its paper sheets into place along their diagonal (themotion scenes/white/3-white). 1-white and 2-white keep their footage and timing; their last 0.4 s now blend into the wallpaper as the shell draws it (themotion fit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replace Ristretto 0-1 with themotion scenes and land 2-3 exactly 0-launch lifts its stripes off and bends them into their curves, and 1-color-curves fans its colour bands in along their curves (themotion scenes/ristretto/*). 2-coffee-beans and 3-industrial-moon keep their footage and timing; their last 0.4 s now blend into the wallpaper as the shell draws it (themotion fit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replace Vantablack intros: new layer scenes, shared dot-hands cut 0-dot-hands uses the Matte Black cut, since both themes ship the same wallpaper. 1-twisted-stairs keeps its footage and lands exactly on the wallpaper (themotion fit). 2-layers-deep opens its depth front to back and 3-layers-stacked lays its papercut stack down sheet by sheet (themotion scenes/vantablack/*), replacing footage that opened on unrelated shapes and a plain fade-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the shockwave ring from the Totoro intro The moon now opens from its seed of light with only its halo; the thin ring racing outward read as clutter (themotion 6970484). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the shockwave ring from the blue-eye intro The eye closes with its pulse and halo only (themotion, same change as Totoro's). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Land the earlier footage intros exactly on their wallpapers Everforest, Osaka Jade, Catppuccin Latte and two Matte Black intros were finished before the exact-ending tooling and ended 0.9-4 levels off their wallpapers (up to 134 on small spots). Their footage and timing are unchanged; the last 0.4 s now blend into the wallpaper as the shell draws it (themotion fit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fade two Matte Black intros up from black ship-at-sea and dark-waters opened at brightness 17-20, so they popped in after the black cover. They now fade up over 0.3 s; the cuts and their exact endings are otherwise unchanged (themotion fit --fade-in). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the Flexoki Light intro for now Its orb scene isn't good enough yet, so Flexoki Light starts on its still until a better intro is ready. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit bfe60d6c0a7a0983509a2dc2b295693bec2ceeb8) * Skip boot intros while animations are off A VM starts with animations off, and there Hyprland renders on the CPU, so a video intro costs the most. The launcher now consumes the boot without playing when Hyprland reports animations:enabled false, and the shell's cover check treats that, like the intros toggle, as nothing to wait for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add Miasma video boot intro for Crowned Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep boot intro startup outside the background service OWE destroys and recreates the background plugin during playback. Keep the launcher and black cover in the persistent shell host, consume the boot before waiting briefly for OWE, and start on the video's first frame. The startup check runs with a video background too, so enabling the still background service later cannot play an intro over an established desktop. Co-Authored-By: Codex Medium <codex@openai.com> * Release the boot marker lock before waiting for OWE The marker settles startup once it is written. Keeping the lock through readiness and playback makes a fresh shell wait for an older launcher before it can discover that the boot was consumed, leaving the still covered during that wait. * Keep the boot intro cover above native and cloned backgrounds Cloned backgrounds receive a scoped shell interface without the startup coordinator. Keep the input-transparent cover in the persistent host so every background stays hidden until OWE has a frame, without widening the plugin interface. Co-Authored-By: Codex Medium <codex@openai.com> * Map the startup cover only for an unsettled boot The cover must know the boot was already consumed before its first frame, so restarting the shell during playback leaves OWE visible. Snapshot the marker without watching subsequent writes: the launcher consumes this boot before the intro starts, while the cover remains needed until the first decoded frame. * Exercise automatic boot cover release in the clone test The fixture hid the cover itself before the fake launcher exited, so a broken exit handler still passed. Let the launcher finish before checking the actual background pixels and leave cover release to the service. Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com> * Finish the Miasma, Kanagawa, Ethereal and Nord intros Start each intro on its scene instead of fading in from black, since OWE now starts intros on their first frame. - Miasma Crowned: ejuro's intro, shortened to 6.7 s and opening on the lit hand - Nord Night Hawks: ejuro's intro - Kanagawa: rendered in the wallpaper's own shape, so the handoff matches on 16:10 and 3:2 screens too - Ethereal, Nord and Miasma Crowned: 1080p endings that blend into the exact wallpaper, so the handoff no longer sharpens visibly Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Finish the Tokyo Night intros Start each intro on its scene instead of fading in from black, since OWE now starts intros on their first frame. - Winding Road: a new starry night to sunset with no added clouds or sun, ending on the wallpaper - Quattro: a new take where the car lands in the wallpaper's exact pose, colour-matched to the wallpaper - Oma Cityscape: drop the second, larger moon that faded out at the end - Quattro, Swirl Buck, Omakub and Oma Cityscape: 1080p endings that blend into the exact wallpaper, so the handoff no longer sharpens visibly Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pair background intros by filename and play them on theme switches Keep matching clips in backgrounds/intros so the selected image determines playback without registration, overrides, or checksum sidecars. Retain one global toggle and play automatically at boot and on a different theme selection. Launch theme playback after releasing the selection lock. Check the staged theme directory identity so an older launcher cannot play a newer selection when filenames are reused. Skipped or failed playback immediately hands the selected still to the shell, including new pixels behind an unchanged path. Synchronize the clone screenshot fixture with capture instead of a fixed launcher lifetime, while still requiring the real exit handler to release the cover. Co-Authored-By: Codex Medium <codex@openai.com> * Settle pending background colors when applying the current theme An intro applies its theme immediately while a previous still-image reveal may still hold an older palette. Replace and consume that pending palette through the background's existing handoff methods so its reveal or fallback timer cannot restore old colors. Reusing those methods also works with existing background clones. Publish the fake renderer's PID before announcing playback readiness, so the lock test cannot read an unwritten PID file. Regressions reproduce both failures on 7b238cf1 and pass with the fixes, including a deliberate scheduling delay at PID publication. Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com> * Use the existing animations menu toggle for background intros Keep the separate intro command available without another Style menu control. Disabled animations already suppress playback at boot and on theme changes. Use remembered backgrounds with matching clips when testing suppressed theme intros, and require the normal still transition so the checks cannot pass merely because no clip was selected. Co-Authored-By: Codex Medium <codex@openai.com> * Finish the Lupine intros Start each intro on its scene instead of fading in from black, since OWE now starts intros on their first frame. - Cherry Blossom White: a new take that pulls back from fresh leaves to the whole tree, easing to a stop on the wallpaper - Pastel Clouds: drop the unrelated starry opening and its dip to black - Elegant Blue Wave: drop the empty white opening - Cherry Blossom Bokeh, Pastel Clouds, Elegant Blue Wave and Cherry Blossom White: 1080p endings that blend into the exact wallpaper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JBE1V6gjrqn2aXCNNGhrUY * Synchronize theme intro and bar crossfades * Start theme intro fades from a cached first frame * Open theme intros on moving scenes * Start theme crossfades promptly * Prepare theme intros before switching * Finish the Catppuccin intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JBE1V6gjrqn2aXCNNGhrUY * Keep theme intro handoffs covered * Reload Hyprland after theme intro playback * Finish the Rose Pine intros Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JBE1V6gjrqn2aXCNNGhrUY * Restore the wallpaper early on each login * Keep the startup wallpaper cover transparent * Wait for readiness in the intro lifecycle test * Consume login intros for updates outside Hyprland * Fade login directly into the ready desktop intro * Sharpen Quattro, Swirl Buck and Oma Cityscape Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep compositor backgrounds black during login * Keep the shell visible through compositor shutdown * Bound the login cover when startup media fails * Encode the 1440p intros at H.264 level 5.1 At 2560x1440 the previous encodes used 16 reference frames, which puts them at level 6.0. Intel hardware decoding plays those with stray grey blocks. Four reference frames keep them at level 5.1, like the other intros. Swirl Buck's stag now changes colour as one silhouette instead of leaving a shrinking black disc on its head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hide the startup cursor until the desktop fades in * Restore Swirl Buck's original stag The stag now changes colour exactly as in the original clip again; the previous edit made it jump from black to purple in one frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Initialize the startup cursor before the first compositor frame * Render the Hackerman intros at 1440p Both open on their first motion instead of fading in from black. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Render the Lumon intros at 1440p Both open on their first motion instead of fading in from black. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Sharpen the Lupine intros Upscaled from the original 720p clips and exported at 1440p with a gentler encode, keeping the same frames, timing and endings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve login intros before the wallpaper daemon starts * Synchronize cold-start captures with the fixture phases * Sharpen the rest of the Tokyo Night intros Winding Road and Sunset Lake are regenerated natively at 1440p from their wallpapers, since their original clips were 360p and 720p. Omakub and Oma are redrawn from their wallpapers with the same motion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Bjarne Oeverli <1419214+bjarneo@users.noreply.github.com> Co-authored-by: Erik Johansson <172146456+ejuro@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Omarchy Bot <omarchybot@users.noreply.github.com> Co-authored-by: Codex Medium <codex@openai.com> Co-authored-by: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
d9b970dd62 |
Let users choose passwordless sudo duration (#14435)
* Let users choose passwordless sudo duration * Warn in the menu bar when passwordless sudo is active * Fix sudo indicator hover behavior and repeated authentication * Disable passwordless sudo from the bar without a terminal * Shorten passwordless sudo indicator tooltip * Recover interrupted passwordless sudo duration switches * Allow sudo grant changes when listings require authentication * Start passwordless sudo setup with the duration question |
||
|
|
e86c8f1ab7 | Reveal indicators only before the clock (#14267) | ||
|
|
454b67d95f |
Fix the bar startup stall and the shell restart race (#11015)
* Instantiate only the current orientation's indicator tree Indicators.qml built both the horizontal Row and the vertical Column and toggled them with `visible`, so every indicator existed twice per bar, and so did every process an indicator spawns: Dictation.qml ran two `voxtype status --follow` per monitor. On a six-monitor bar that is 72 indicator instances and twelve followers for six visible icons, and each instance registers a click target and re-syncs the active-indicator model as its state resolves at startup. A Loader now instantiates the tree that matches `root.vertical`. Each tree is wrapped in an Item that keeps the stock explicit implicit-size expressions, so the root's size still follows the blocks synchronously; a bare positioner only updates its implicit size on polish, which the indicator contract test's center-hover check catches. Measured on a six-monitor, 23-widget bar (three runs each, `omarchy restart shell`): time from "Configuration Loaded" to "polkit agent registered" 19.8-20.3s -> 15.5-15.7s, quickshell CPU 29-30s -> 24-25s, voxtype followers 12 -> 6. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Coalesce plugin API resyncs and key bar object ownership by target Every WidgetButton registers itself as a bar click target when it is created. registerClickTarget replaced the clickTargets array, the change handler ran syncAllPluginBarApiObjects() inline, and that walked every plugin API times every click target times a linear scan of pluginObjectOwners in pluginObjectRecord. Startup is a few hundred registrations, so the cost is quadratic in bar size and multiplied by the number of monitors: a six-monitor, 23-widget bar spent 16-20 seconds of pegged QML thread before it was populated, and an 8-second qmlprofiler capture showed 1.36 million pluginOwnsBarObject calls and 46-71ms per registration. - pluginObjectOwners is a Map keyed by target, so pluginObjectRecord, markPluginObject, unmarkPluginObject and releasePluginObjects are O(1) per object. Nothing outside Bar.qml read the array. - The activePopout, clickTargets and layoutConfig change handlers schedule one resync per event-loop turn through Qt.callLater, the way onModuleSlotsChanged already defers prunePluginBarApis. bindPluginBarApi still syncs a brand-new API inline, and requestPluginPopout and releasePluginPopout sync the owning API inline, so a plugin never reads a stale API on its own actions. - A flush serialises the layout once and hands each API its own parsed copy instead of deep-copying it once per API per sync. - ModuleSlot's cursorShape read clickTargets for every slot on every monitor (26,700 evaluations per start). It is now gated on the slot's HoverHandler, which is the only time the cursor is over it. Measured on the same bar, launching the shell from a checkout with this and the indicators change (two runs): "Configuration Loaded" to "polkit agent registered" 19.8-20.3s -> 0.70s, bar populated 1.7s after launch (from ~30s), quickshell CPU 29-30s -> 2.0s. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Wait for the old shell to exit before restarting it omarchy-restart-shell stopped the running shell with `quickshell kill` under a five-second timeout and launched the replacement as soon as the loop ended. A six-monitor bar takes 5.4-6.0 seconds to tear down (every widget button unregisters its click target on destruction, and each unregistration re-synced every plugin API), so the client timed out while the shell was still exiting, the fresh instance's no-duplicate check saw the dying one and quit, and the user was left with no bar and "Omarchy shell did not become ready after restart". Give the kill client thirty seconds, then wait, bounded, until `quickshell list` shows no instance of the session config before launching. The readiness check also waits on a sixty-second deadline instead of twenty attempts: a large bar answers ping only after its plugins have loaded, which on the stock bar was well past the old twelve-second window. Verified three consecutive restarts against the stock shell on the six-monitor machine: each returned 0 in about six seconds with exactly one instance and no "already running" in the journal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Say what the bar's ownership Map actually saves Qt's V4 Map (ESTable::get) finds a key by scanning its keys, so ownership lookups are not O(1). The win is that a registration no longer copies the owner array and rescans it in QML. Co-Authored-By: Codex Medium <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com> Co-authored-by: Codex Medium <noreply@openai.com> |
||
|
|
6809987892 | Align panel underlines with native bar icons | ||
|
|
b18ab4952b |
Add a no-animations mode, on by default in VMs (#13550)
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> |
||
|
|
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> |
||
|
|
72648651cd | Preserve shell reference details and repair incoming links | ||
|
|
6ee7479879 |
Stop the layout widget switching a device nobody types on
Clicking the keyboard layout widget switches one device, chosen by filtering the seat through UNTYPED_KEYBOARDS and then taking whichever survivor sits furthest through the layout list. That filter has to recognise every non-keyboard by name, and a laptop registers far more as a keyboard than it lists. On a Dell XPS 14, Hyprland reports ten keyboards and one of them is a keyboard. The filter catches three of the other nine, leaving vendor hotkey blocks (intel-hid-events, intel-hid-5-button-array, dell-privacy-driver, dell-wmi-hotkeys) and two HID endpoints ahead of at-translated-set-2-keyboard, which sorts last. Every click switches hid-sdw:...-consumer-control instead, so the label cycles convincingly while typing never changes. Device order is stable across polls, so it is deterministic rather than a race, and needs no pre-existing bad state. Switch every keyboard holding the same layout list instead, naming an absolute index. "next" advances each device from wherever it sits, so a seat that has already drifted apart stays drifted and merely inverts; one index converges it in a single click, and a seat in lockstep leaves the reading nothing to disagree about. Keyboards given their own kb_layout hold a different list and are left alone, since an index into this list would not mean the same layout to them. The evdev KEY bitmap would separate these cleanly - the real keyboard emits 167 keys, the pseudo-devices at most 19 - but hyprctl devices reports no capability information, so the switch is taken out from behind the name filter rather than the filter being lengthened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
572a34d3e3 |
Refresh cached plugin bar shell facades
Co-Authored-By: GPT-5.6 Sol <noreply@openai.com> |
||
|
|
094c913065 |
Tighten replacement bar plugin boundaries
Reported-by: Roger Piñol <rogerpicar@gmail.com> |
||
|
|
1702cf0bee |
Restrict third-party shell plugin capabilities
Reported-by: Roger Piñol <rogerpicar@gmail.com> |
||
|
|
e1fc502286 |
Nudge the bar over IPC when toggling visibility
The shell notices the bar-off flag through a FileView watch on the toggles directory, and that watch can permanently stop delivering events after flag changes land in quick succession — the bar then stays parked off screen until the shell restarts. Have omarchy-toggle-bar nudge the bar's probe over IPC after flipping the flag, so the toggle no longer depends on the watch staying alive. The watch remains for other writers of the flag. |
||
|
|
3af7675a10 |
Require textFormat declaration for all Text elements.
|
||
|
|
c0a20e2e43 |
Stop the bar sticking in move mode after a press-and-hold on a widget (#6943)
Bar widgets propagate their composed press-and-hold down to the center gesture area without handing over the grab, so the gesture area started a bar move and then received neither a release nor a cancel to end it. The move ghost stayed on screen for the rest of the session. Ignore the gesture unless we hold the press. Closes #6881 Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b68c22208a |
Correct stale claims in menu jsonc headers and bar README
Providers never returned JSON rows: they are shell-defined row sources emitting tab-delimited lines, and extensions cannot declare new names. bar.shellQuote moved to Util.qml, and the UpperCamelCase widget id migration no longer exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
dc698e5df0 |
Suppress LocalSend's redundant tray item (#6852)
LocalSend registers an Ayatana item with no ItemIsMenu and no Activate handler, so its primary click is a silent no-op and the menu offers only Open and Quit. Share > Receive already opens it, so drop the item the way Dropbox's is dropped when its dedicated widget owns the surface. Closes #6838 Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3a4eda7f08 |
Drop retired nmtui and bluetoothctl interactions from the bar README
The network and bluetooth panels stopped launching them; the module catalogue still said otherwise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f5893ddd9e | Launch the default agent from the agents widget right-click (#6759) | ||
|
|
66e3f479a6 |
Follow the keyboard being typed on in the layout widget (#6740)
* Read the keyboard being typed on rather than the one holding main The main flag names no keyboard for long. fcitx5 takes it with the virtual keyboard it binds to inject, and those are filtered out, so on a seat running an input method the pick lands on nothing at all: no label, and the widget hides itself off the bar. #6727 keeps polling in that state rather than settling it, and the poll has nothing new to read. Once fcitx5 unbinds, the flag lands on whichever device libinput listed last, as easily a lid switch as a keyboard, and a device that never receives the toggle reports the layout it started on forever, which is the reading #6574 opened. Every device carries the seat's layout list, but only the keyboard being typed on advances through it, so read the furthest-advanced one. activelayout names the keyboard it moved ahead of the layout, so take that name and let it settle the pick, and the click that switches it. * Leave the buttons out of the seat the widget reads Reading the keyboard being typed on left keyboardName standing for two things at once: the device a click switches, and the device activelayout last named. Only the second was still being set, so the first went empty until a switch happened -- which left the click doing nothing on a seat whose only switch is the click, and left the poll running forever on the one-keyboard install it was written to leave alone. Give each its own property, and set the switch target from the reading that confirmed the keyboard is there. Layout progress only points at the keyboard being typed on while the other devices stay where they started, and the ACPI power button, lid switch and sleep key never do move on their own -- but they answer to switchxkblayout and can hold the main flag, so anything that reads or switches whatever the seat hands back can end up describing a button, and unplugging the keyboard beside one leaves it standing in for the seat. Drop them where the virtual keyboards are already dropped. A reading that reaches hyprctl and finds no keyboard now clears the label rather than leaving a device that is gone described on the bar, told apart from the empty output a killed query leaves by the device list itself, and the watchdog asks again rather than waiting for a poll that a settled seat has already stopped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5c74f82300 |
Ask for the keyboard layout only when the answer can change (#6727)
The widget polled hyprctl every 10 seconds per monitor, including on the single-layout install where it never shows. Keep the poll only where its answer can change - a seat with more than one keyboard, where Hyprland moves the main flag with no event to announce it - and stop it entirely once a one-keyboard seat has been read. Also coalesce a refresh that arrives mid-query instead of dropping it, time out a query that never returns rather than letting it hold the guard shut for good, and re-read the layout on configreloaded. Co-Authored-By: markbus-ai <markbus-ai@users.noreply.github.com> |
||
|
|
5b2c02dee3 |
Fix unclickable tray submenus by drilling down inside the popup (#6703)
* Fix unclickable tray submenus by drilling down inside the popup Clicking a tray menu entry that has children was a silent no-op: the row called QsMenuEntry.display(), which renders a *platform* menu, and Quickshell refuses that unless the shell root sets `//@ pragma UseQApplication` -- shell.qml does not. The log shows "Cannot display PlatformMenuEntry as quickshell was not started in QApplication mode" and nothing opens. Apps whose whole menu is submenus, like radiotray-ng's station list, were unusable. Adding the pragma would be the wrong fix: it switches the entire shell from QGuiApplication to QApplication, dragging QtWidgets into the process and changing application-class behavior for the sake of one popup -- which would then render as an unstyled platform menu beside omarchy's own popup styling anyway. Instead, submenus drill down inside the existing popup. A child QsMenuEntry inherits QsMenuHandle, so it can feed a nested QsMenuOpener and render through the same row delegate. Each level keeps its own live opener on a stack -- a child entry is owned by its parent opener's model, so collapsing to a single reassigned opener would destroy the very entry being displayed. A back header row walks out one level; at the root the menu renders exactly as before, and items without a DBusMenu still use the platform fallback. * Destroy submenu openers deepest-first and reset before switching items resetTrayMenu() destroyed openers front-to-back and only cleared submenuStack afterward. A deeper opener's menu entry is owned by its parent's children model, so destroying the parent first could invalidate an entry a still-live child opener referenced. Clear the stack before tearing anything down, then destroy deepest-first so a child is always gone before the parent whose model owns its entry. openTrayMenu() reassigned activeTrayItem before calling resetTrayMenu(). trayMenuOpener.menu binds to activeTrayItem.menu, so that reassignment immediately swaps what the root opener's children expose -- invalidating entries any live submenu opener still referenced, before resetTrayMenu() got a chance to tear them down. Reset first, then switch items. Thanks @Copilot for catching both. * Defer submenu reset until the popup's fade-out actually finishes onTrayMenuOpenChanged reset the submenu stack the instant trayMenuOpen went false, but the popup stays visible for the whole 140ms opacity fade (PopupCard's own visible: open || card.opacity > 0) -- dismissing from a submenu flashed the root menu mid-fade, and could resize or reposition the fading popup if the two have different geometry. Moved the reset to trayMenuPopup's own onVisibleChanged, which only fires once the fade has genuinely completed. Switching to a different tray item is unaffected: openTrayMenu() already resets explicitly before assigning the new item, independent of whether the popup ever dips to invisible (rapid reopen mid-fade never does). Thanks @Copilot for catching this. * Ignore tray menu clicks for a beat after changing submenu level Changing level swaps the Repeater's model, which rebuilds the row delegates synchronously -- a fresh row lands under a cursor that hasn't moved. Submenu clicks used to be silent no-ops, which trained users to click them twice, so that second click now fires whatever entry took the spot. On radiotray-ng that means an accidental station switch. Gate row and back-header clicks for 250ms after each level change. A deliberate follow-up click is slower than that; a double-click is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Pin the submenu back header above the scrolling menu rows The back header lived inside the Flickable's Column, so in a submenu taller than the 420px cap -- exactly the long station list this drill-down exists for -- scrolling down pushed the only way back off screen, with no Escape or right-click alternative. Move it into a pinned Column above the Flickable and account for its height in the popup's contentHeight. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Reset the tray menu scroll offset when the drill-down is torn down Flickable keeps its contentY across a model swap whenever the new content is still tall enough to hold it. A menu dismissed while scrolled therefore reopened part-way down with its first entries off screen: reproducible on any tray app whose root menu outgrows the 420px cap, and now reachable on every app once a long submenu has been scrolled. Zero the offset in resetTrayMenu(), which runs both on teardown and before switching items. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Toni Nowak <t.nowak@ai-flow.no> Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
66f3155f0c |
Label the keyboard widget with the xkb language code (#6699)
* Label the keyboard widget with the xkb language code The label was the first word of the layout description cut to three characters, so a US layout read ENG and a Portuguese one read POR. xkb already pairs every layout and variant with a short language code, which is the code GNOME shows in its own indicator. Read that table once at startup from xkbcli list and key it by description, which is what hyprctl reports as the active keymap, so the same layouts read EN and PT. The code is a language rather than a country, so it stays sensible for the layouts named after neither: Esperanto is EO, Arabic is AR, and Latin American Spanish is ES. Layouts missing from the table keep the old truncated description. * Read the exotic xkb rulesets for the keyboard label xkbcli list leaves out the exotic rulesets, so layouts like trans were missing from the table and fell back to the truncated description: the IPA layout read INT rather than IPA. Those layouts ship in the same xkeyboard-config package and set just as well, so read them too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep the keyboard label to three characters The brief was used verbatim while the fallback was truncated, but not every brief is two or three characters: Burmese (Zawgyi) is my-zwg and Shan (Zawgyi) is shn-zwg. Selecting either widened the widget past its neighbours on the bar. Drop the script suffix and cap the brief the same way the fallback is capped, so those read MY and SHN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Stop an xkb brief carrying past its own block The brief was only cleared once a description consumed it, so a block printing a brief without one would hand its code to the next block's description and label it wrongly rather than falling back. Nothing in the current xkb data does that, and the option groups were skipped only because the last layout happened to consume its brief first. Clear the brief when a line starts a new block so the pairing is explicit, and cover the option list the 2-space match is what keeps out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fall back when a layout description names a built-in A custom xkb group called constructor or toString reached an inherited member of the lookup rather than a brief, and splitting it threw a TypeError that took the whole label binding down instead of falling back to the truncated description. Take the lookup only when it returns a string. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5edc3497fa |
Bind SUPER + CTRL + a number to the bar's right panels (#6702)
The letters name a panel; the numbers count them. One is the leftmost panel in the right section, so the number matches the icon a user would point at: a widget with no panel of its own is passed over, and so is one that is hiding itself. Counting rather than naming means the hotkeys follow the bar. Rearranging the section, or adding a widget to it, renumbers the panels with no binding to rewrite. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
567e24cd90 |
Hold the indicator peek open while the pointer is on the bar (#6663)
* Hold the indicator peek open while the pointer is on the bar Revealing the hidden indicators widens their section, and a section that grows can slide a neighbouring widget under a pointer that never moved. Collapsing the peek on that un-hover narrowed the section again, moved the neighbour back out, and re-opened the peek, so a pointer resting in the bar space beside a grown section stuttered the bar until it moved away. Hold the peek while the pointer is anywhere on the bar and close it only once the pointer has left, which keeps the reveal-on-empty-space gesture and drops the feedback loop. Fixes #6581 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Assert the whole-bar hover helper does what the peek depends on The earlier assertions all held against a no-op setBarHovered, which would leave barHovered false and let the oscillation straight back in. Pin the assignment and the collapse re-run too, so the helper cannot be emptied without the suite noticing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Let the delayed peek re-check collapse only, never open The timer assigned centerSectionRevealHeld outright, so it opened the peek from bar hover alone. A pointer resting on the left section that dipped off the bar and returned inside 120ms left the timer pending with barHovered true again, and the indicators revealed without the pointer ever touching the center section. Opening stays the center section's own gesture in setCenterSectionHovered. The timer now only closes what that opened, and the test asserts the invariant against the whole file rather than one helper body that never had the offending assignment in it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Tally bar hover per monitor instead of sharing one flag Every screen's bar wrote the same barHovered bool, last writer wins. Sliding along the top edge from one monitor's bar to the next can deliver the enter before the leave, leaving the flag false under a live pointer; the collapse then fired on a peek the user was still hovering, and no further hover change arrived to correct it until the pointer left and came back. Counting each surface's hover makes the order irrelevant. A bar destroyed mid-hover — unplugging a monitor — never sends a leave, so it hands its tally back on destruction rather than holding the peek open for good. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
7633d8dee4 |
Keep the bar mapped while hidden so revealing it is instant (#6677)
* Keep the bar mapped while hidden so revealing it is instant Hiding the bar set the panel invisible, which unmaps the layer surface and releases the scene graph with it. Every reveal then had to rebuild all of it: a new layer surface, a configure roundtrip, re-shaped glyphs and re-uploaded textures, and a first frame before anything appeared. Measured on a 2560x1440 screen, showing took 155-175ms against 20ms to hide, and 400-595ms on the first reveal after a cold start. Splitting the cost showed the exclusive-zone reflow was not to blame: show latency was the same on an empty workspace as on a tiled one, and windows finished moving ~15ms after the bar was already on screen. Park the bar one bar-width past its anchored edge instead, and drop its exclusion zone while hidden. The surface stays alive, so showing is only a margin change: 10-14ms in both directions, at every bar position. Since a hidden bar is now mapped, layer_present no longer proves the bar is visible; the session acceptance test asserts on-screen geometry. * Fix layer visibility checks on offset monitors * Handle rotated outputs in layer visibility checks * Cover hidden bar behavior in acceptance tests --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
e1d0c4e0a8 |
Ship the keyboard layout widget on the bar and make clicking it work (#6659)
* Hide the keyboard layout widget on a single-layout install There is nothing to read or switch when only one layout is configured, so the label is noise on the bar most people have. Hide it until the keyboard reports more than one, and keep showing it on a Hyprland that doesn't report the list at all rather than hiding the widget everywhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Put the keyboard layout widget on the bar by default The widget hides itself unless the active keyboard has more than one layout, so shipping it costs a single-layout machine nothing and saves everyone else from finding it in the plugin list. Sit it just right of the clock, and add it to existing bars the way the agents widget was added, leaving a curated bar and a disabled widget alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Cycle the layout with the hyprctl command that exists switchxkblayout is a hyprctl command, not a dispatcher, so sending it over the dispatch socket only produced a Lua syntax error and clicking the widget did nothing. Run it instead, against the keyboard the label was read from. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add an idempotent bar add command Nothing put a widget on the bar without going through the running shell: plugin enable and bar move both forward to it over IPC, which a migration cannot rely on. Add writes the config file the way position and transparent already do, and leaves a widget that is already on the bar where the user put it, so callers can ask for it repeatedly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Put the keyboard layout widget on bars through the bar CLI The hand-written jq was a normalizer, a presence check and a splice for what is now one command that carries all three. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep bar add from writing a bar the shell was not reading The shell takes a user shell.json only when it parses, says version 1, and carries a bar layout, and does not deep-merge; anything else leaves the shipped defaults on screen. Reading and writing the user file regardless turned a config holding nothing but an idle timeout into a bar holding nothing but the new widget, and made an unparsable one abort the migration chain on every update. Work against whichever layout is actually in effect, seeding the defaults before placing a widget they do not already carry. A malformed hand-installed manifest fails the whole plugin catalog, which was enough to refuse a first-party widget, so treat an unreadable catalog as no answer rather than a no. Leave a widget listed in disabledPlugins off the bar instead of writing a layout entry the registry refuses to load, and re-check presence inside the mutation so two adds cannot both miss it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Read a widget's default bar section in one place cmd_defaults spelled out the same "defaultSection, or center when it is missing or not a section" rule that the add path already asks for by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Rename bar add to bar put 'omarchy plugin add' installs a plugin and 'omarchy bar add' placed one that was already installed, which is too much meaning for one verb. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Place a newly added bar widget with bar put plugin add reached the bar through plugin enable, which forwards to the running shell, so it first had to poll until the shell noticed the clone and then failed outright when no shell was there to ask. Putting a widget on the bar is a config edit, so do that directly and leave plugin enable to the plugins that need registering rather than placing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Put bar widgets through the shell instead of the config file Placing a widget existed twice: once in PluginRegistry, which the shell uses and owns the config it holds in memory, and once as jq against shell.json. The second was there so migrations could run without a shell, which they do not need to: the Quattro upgrade hands over the shipped shell.json before it runs any, and every other path runs inside a session with a shell up. Ask the shell, and say so and carry on when there is none to ask. putBarWidget enables only what is not already on the bar, which is what a caller that cannot know whether it ran before needs, and is the one thing the existing enable path would not do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9d61915b2e |
Fix KeyboardLayout plugin label permanently displaying ENG on some systems (#6646)
* Fix hyprctl output parsing in KeyboardLayout plugin * Never fall back to a non-active keyboard for the layout label find(k => k.main) returning nothing fell through to keyboards[0], which is the case the fix is for: on hardware whose first device is a permanently English (US) radio-control keyboard, the label was wrong and the 10s poll kept it wrong. The seat can also hold no active keyboard while a device is re-added, and older Hyprland has no main field at all. Keep the last known value instead, and skip entries without an active_keymap, since assigning undefined to the string property throws before the label is ever set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Run hyprctl directly from the keyboard layout widget The shell wrapper only existed for a pipeline that is gone, so spawn the command directly, as Style.qml already does for its own hyprctl query. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Read the layout from the keyboard the user types on Every Omarchy install runs fcitx5 for ~/.XCompose, and it binds a virtual keyboard that takes the seat's main flag whenever it injects. That keyboard keeps the us layout the input method gave it, so on a machine configured for another layout the widget flipped to ENG and the poll kept it there until the next physical keypress. Skip virtual keyboards and hold the last known layout instead, which the next poll corrects once a real keyboard is active again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep tracking the keyboard the layout was last read from Holding a frozen label while fcitx5 owns the main flag went stale as soon as the layout changed underneath it, and cycling still dispatched against "current", which is that same virtual keyboard. Remember the keyboard the label came from, re-read its layout on every poll, and cycle it by name so the widget shows and switches one device. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
667d2d2f31 |
Open panel hotkeys on the focused monitor (#6613)
A bar surface is built per monitor, so panel routing had several live copies of the same widget to choose from and took whichever registered its slot first. Pick the one on the monitor Hyprland has focused instead, preferring an already-open copy so hide and toggle still reach the visible panel. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bb8d2f2cb3 |
Split agent usage into data files and rename the plugin to omarchy.agents (#6603)
* Add agent usage collectors that write display-ready data files
One omarchy-agent-usage-scan-<agent> collector per AI coding agent prints a
complete display-ready usage record — identity, tier, status, rate limits,
and today/week/all-time stats. omarchy-agent-usage-update runs every
collector it finds and writes the records atomically to
~/.local/state/omarchy/agents/usage/, so anything that displays usage only
ever reads JSON from there.
The Claude collector absorbs what the shell previously did in-process:
transcript scanning, the stats-cache/history fallback, credentials parsing,
and the OAuth limits probe, now with a probe throttle and last-good limits
kept across network failures. The Codex collector is the existing scanner
reshaped to the shared record contract.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Redo the model-usage plugin as omarchy.agents watching usage data files
The panel is now strictly a display. It discovers the JSON records that
omarchy-agent-usage-update maintains under
~/.local/state/omarchy/agents/usage/, watches them for changes, and draws
whatever appears — so adding an agent means shipping a collector, never
touching the panel. Marks resolve by convention (assets/<id>.svg with an
optional -light twin), the limits meters read a generic limits array, and
the per-provider QML adapters and in-plugin scanner scripts are gone.
Cross-device sync aggregation stays in the shell and keeps the snapshot
field names older versions wrote, so mixed-version fleets still merge in
both directions.
With the provider fan-out gone, the widget takes its real name: the plugin
id becomes omarchy.agents. A migration renames it wherever a user's config
mentions it — layout entries keep their settings and position, a disabled
widget stays disabled — then primes the data files once and drops the old
scanner cache. The migration test also drops a stale assertion that expected
migrations to restart the shell themselves, which
|
||
|
|
2daeaa7078 |
Remap bar and background surfaces when their monitor moves
Hyprland leaves an already-mapped layer surface at its old global position when its monitor moves within the layout: undocking disables the internal panel, the external monitor shifts to x=0, and the bar and background keep rendering at the old offset until unmapped and remapped. Watch each screen's origin and briefly unmap the window when it moves so the compositor re-places the surface at the monitor's new origin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1f10c78c6a |
Patch settings-only bar config changes in place instead of rebuilding every widget
A shell.json write used to reassign the whole layout, and the module Repeaters recreate every delegate when their array model changes — so toggling an inline widget setting (battery percentage, clock format, tray pinning) tore down and rebuilt every widget on every monitor, closing any open panel along the way. When the layout structure is unchanged, hand the new settings to the running widgets instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3ffd9cdfc5 | Add battery percentage toggle to power widget | ||
|
|
57ea0b4cd0 | Simplify bar plugin management (#6435) | ||
|
|
f8835df644 |
Plugin cloning via menu (#6433)
* Add plugin cloning via menu * Split plugin commands by action * Keep plugin enablement in action commands * Remove unused plugin edit command * Simplify plugin rescan arguments * Assume Omarchy shell is running for plugin commands * Remove plugin compatibility dispatcher * Flatten plugin clone command * Keep only shared plugin helpers * Remove plugin rescan wrapper * Keep plugin commands self-contained * Simplify plugin clone lifecycle |
||
|
|
09b955dc75 |
Manage plugins from Setup > Plugins (#6420)
* Give built-in plugins an honest on/off state Every built-in reported itself enabled no matter what. A bar widget said "enabled" while sitting nowhere near the bar, and disabling a built-in service silently did nothing, because enabled meant "listed in plugins[]" and a built-in never is. Nothing surfaced that, since the only caller listing plugins was the CLI. For a widget, on and off is its place in the bar, so listPlugins reports layout membership -- what enable/disable actually toggles. For everything else built in, loading by default is the right behaviour to keep, so switching one off is recorded the other way round, in disabledPlugins[]. shell.json still carries only the deviation from the defaults: the key is dropped the moment nothing is switched off, leaving a config that never disabled anything byte-identical. isEnabled still answers a separate question -- whether the component loads at all -- and deliberately does not follow a widget out of the bar. omarchy.menu is both a widget and the menu itself, so tying the two together would let taking its button off the bar lock the menu out of the shell, with no way back that isn't the CLI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Manage plugins from Setup > Plugins Plugins were CLI-only. Setup > Plugins now offers Enable, Disable, Add, and Remove, each list living in the menu itself so picking a row acts on it. Enable and Disable cover the built-ins as well as anything installed -- the bar widgets you can put in the bar, the services and overlays you can switch off. Remove is limited to plugins the user installed, since a built-in has no checkout to delete, and stays hidden until there is one. Whole-bar replacements are left out; those are chosen under Style. Enabling a bar widget asks for a section first, because enabling alone drops it on the right and the only way to move it was a follow-up bar plugin move. The CLI asks the same question after its own add, so both paths place a widget the same way. Add and Remove run in a terminal: one needs a git URL and shows the trust warning before cloning, the other deletes a checkout and prints where it backed it up. Providers grew two hooks for this. placementFor turns a row into a submenu instead of an action, and volatile re-runs the enumeration when its submenu is entered -- picking from these lists is what changes them, and rows a provider no longer returns now drop out instead of lingering forever. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Trim the plugin menu after review Menu.qml carried its own shellQuote while already importing Util and calling Util.shellQuote a few lines up; two copies of the same escaping is one place for a future fix to miss. isDisabled walked the array by hand to compare values it writes itself, and dropDisabled was an eight-line helper with one caller. Two bugs came out of the same pass. A whole-bar replacement belongs under Style rather than these lists, but the exclusion sat in the shared row builder, so a third-party bar could be installed and never removed -- Remove would show an empty list under a guard that said something was there. The exclusion now sits on the two lists that mean it. Rows are keyed by id, and distinct plugin ids can slugify alike: acme.foo, acme_foo and acme-foo all give acme-foo. The merge keeps the first row per id, so the rest simply vanished from the list with nothing to say why. Row ids are now made distinct before merging. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Pick a plugin the way we pick a theme Setup > Plugins listed plugins as menu rows, which needed three providers, a placement submenu, a volatile-refresh hook and a row-swap in the merge. Only Font and Apps are built that way. Theme, Background, Unlock, Timezone and Keybindings all pipe a list into omarchy-menu-select instead, which is one action string and a small script -- so that is what these use now. The trade is search: a plugin name is no longer findable from the root prompt. Neither is a theme name or a timezone, and Enable Plugin still is, so the loss sits where the rest of the menu already puts it. Two pieces of the row machinery stay, because they are worth having for the lists that remain. A volatile provider re-runs when its submenu is entered, so a font installed since the shell started now shows up without restarting it, and rows a provider stops returning drop out. Row ids are still made distinct before merging: Fira Code and Fira-Code both slug to fira-code, and a repeated id was silently dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Let a picked option carry an icon Moving the plugin lists onto omarchy-menu-select cost them their glyphs: the select mode has always hardcoded an empty icon, which is why Timezone and Keybindings have none either. An option may now lead with one, as "<glyph><TAB><label>". The menu shows the glyph, filters on the label, and hands the label back, so a caller never strips a glyph off its own selection and a list of plain strings behaves exactly as before. The plugin picker uses it for the puzzle glyph on each plugin and the align glyphs on the sections, which also regain the capitals they lost when the section names were passed through raw. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Switch bars by enabling one A bar option was kept out of Enable and Disable on the grounds that picking which bar to run belongs under Style -- but nothing under Style ever offered it, so an installed bar could be added and removed and never actually put to use. The menu was guarding a door to a room that was never built. Enabling one is the switch. setEnabled already assigns bar.id for a bar option, so a bar has always replaced the one before it; only the picker's filter stood in the way. Dropping it costs nothing else, because enabled for a bar option means active: the bar in use is the one row absent from Enable, every other installed bar is one pick away, and the built-in is just another entry, so going back to it is enabling Bar. Disable keeps the exclusion. That is the one verb a bar cannot answer -- there is no off, only a successor -- and offering it would have listed the built-in bar on a stock system, where turning it off deletes a bar.id that was never set and nothing happens. A bar carries the bar glyph rather than the puzzle one, so a row that replaces the whole bar does not read like one more widget to switch on, and enable now says "Now using X as the bar" instead of "Enabled X", which understated a whole-bar swap in both the enable and the freshly-added path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Refuse a plugin that declares a kind it cannot load A kind is a promise to supply something to load, and the shell reads that something from a fixed key: entryPoints.bar to draw a bar, entryPoints.menu to open a menu. Nothing checked the promise. A manifest could claim kinds ["bar"] with no bar entry point, pass validation, install, and enable -- and then the bar would fall back to the built-in and the widget would be skipped, leaving a plugin that does nothing, explained only by a console.warn nobody reads. Our own plugins have been held to this table by plugins-test.sh all along. This holds third-party ones to the same table, at add and update time, where there is still someone to tell. A kind outside the table is left alone rather than guessed at, so a shell that learns a new kind does not need this list updated first. The cost is that a misspelled kind still installs quietly. omarchy-plugin-validate had no tests; it has some now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Act on the plugin whose row was picked The picker showed a name and then looked that name up again across every plugin, filtered set or not, taking the first match. Two plugins can share a name: cloning one keeps the name it was cloned from, so the documented `omarchy plugin clone omarchy.clock local.clock` leaves two plugins called Clock. Enable listed the clone -- the built-in was already enabled, so only the clone was eligible -- and then enabled omarchy.clock, moving the built-in widget instead. Remove listed the clone and tried to delete a built-in that has no checkout to delete. A row now carries its id alongside its label, and the id is read back off the row that was picked instead of being derived from the name a second time. Where a name is not unique among the rows on offer, the label carries the id too, so two rows that would both say Clock can be told apart at all -- which they could not before, whichever one the pick resolved to. The verb prompt only ever sees the first two fields, so the menu shows what it always did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Never ask a bar where to sit in the bar A manifest may declare both bar and bar-widget, and validation accepts it. The picker saw bar-widget, asked for a section, and passed it to enable. setEnabled takes bar as the dominant kind: it writes bar.id and returns, adding nothing to any layout, so the move that followed had no widget to find and failed -- after the bar had already been switched. A partial success with an error on the way out. Bar wins ahead of bar-widget now, in the picker and in the placement prompt `plugin add --enable` asks, so a bar is enabled without a placement it cannot use. The CLI refuses a placement on a bar outright, before the bar is switched rather than after, since `omarchy plugin enable <bar> --section left` could reach the same half-applied state without going through either. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Only replacement, no off * Add default placement for bar widgets * Simplify plugin menu actions * Document plugin placement behavior * Allow dropping widgets in empty bar space * Treat plugin dependencies as runtime invariants * Reject duplicate plugin ids on add --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9b693cca63 | Refresh the look and layout of the model-usage plugin | ||
|
|
bdcdfeb428 | This tmux alert system didn't work as nicely as I imagined | ||
|
|
8728791c93 |
Build only the module list the bar is showing
The center section declares both an anchored and an unanchored arrangement and shows whichever fits, but a hidden ModuleList is still a loaded Loader. With a center anchor set — the default — every center module was therefore mounted twice for the life of the session: two IPC handlers registered for the same target, two clocks ticking, two of every timer and network fetch behind them, one set of which nothing could reach. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |