* Evaluate menu guards one run at a time A second evaluation starting while one was in flight could not replace it. Process ignores a command change until the next run and `running = true` is a no-op while running, so setting them did nothing -- but clearing `collected` first threw away the lines the running script had already emitted. Its tail then landed as the entire result, and every id missing from it went back to showing, since `when:` only hides a row on an explicit false. That is how Setup > Defaults > Browser ends up listing browsers that are not installed. Queue the evaluation instead and run it once the one in flight lands, the way provider enumeration already waits its turn. * Answer repeated menu guard questions once per evaluation The menu opens on the last evaluation's answers, so however long the guard batch takes is how long a row can contradict the state it describes: stop a recording and Screenrecord still offers to stop it, because the `pgrep` that would hide it is queued behind fifty package lookups. Almost none of that time is the questions, it is asking them one process at a time. The shipped menu runs `omarchy-pkg-present` 54 times and `omarchy-cmd-present` 23, and reads `omarchy-default-browser` once per row in Defaults > Browser. Prepend a prelude that answers all of it inside the one guard process, off a single package listing, bash's own PATH lookup, and one capture per reader command. The captures are eager because `checked:` reads them inside `$()`, where a lazy memo would not outlive the subshell. Takes the shipped batch from 1.49s to 0.25s with identical answers for all 175 guards. * Make the guard prelude answer exactly as the commands it stands in for The prelude only helps if it is indistinguishable from the commands it shadows, and it was not: - `pacman -Q` resolves a name through what installed packages provide, so with gvim installed it reports `vim` as present. A set built from `pacman -Qq` sees only names, so `install.editor.vim` came back and offered to install what was already there. Build the set from provides too, and send version constraints, which no set can answer, to pacman. - `omarchy-cmd-present` uses `command -v`, which finds builtins; `type -P` searches PATH alone and disagreed on every one of them. - Shadowing a reader with a function caught far more than the plain `$(reader)` the rows use: `command -v omarchy-dns` got the function name, and `VAR=x omarchy-channel-current` got an answer captured without the variable. Substitute the captured value into the expression instead and leave every other form to run the real command. - A reader that exits nonzero could take the batch down under a login shell with errexit set. Also keep the results of a batch that was killed rather than finished, since a row whose `when:` went unanswered shows, which is the failure this set of changes exists to remove. Costs 0.25s -> 0.33s against 1.49s before any of this, still with answers identical to evaluating each guard on its own. * Read every provide pacman reports, wrapped or not `pacman -Qi` wraps a long list onto indented continuation lines whenever COLUMNS is set in the environment, which the login shell the batch runs under may well have done. Reading only the line that starts with `Provides` dropped the rest: at COLUMNS=80 that is 537 of 856 provides on this machine, which puts back exactly the "offers to install what is already there" failure the provides lookup was added to prevent. Follow the continuation lines instead. The version-constraint case was also not testing what it claimed. Interpolating the argument into the shadow's script text let `bash>=1` parse as a redirection, so the shadow was handed `bash` and quietly agreed for the wrong reason -- and left an `=1` file behind, which got committed. Pass arguments as argv to both sides, drop the file, and wrap gvim's provides in the stub so the parser is held to the format pacman actually emits.
First-party plugins
These plugins ship with Omarchy and are discovered by the shell at startup.
They use the same manifest.json contract as third-party plugins; the
only difference is that the shell flags them with __isFirstParty: true.
First-party non-bar plugins are enabled unless listed in disabledPlugins[];
omarchy.bar is the default bar option and becomes inactive only while another
kind: "bar" plugin is selected. Services and keep-loaded panels are mounted
at startup; other panels, overlays, and menus are loaded on demand.
User-installed plugins live alongside these conceptually but on disk under
~/.config/omarchy/plugins/<plugin-id>/ rather than in this directory.
| Plugin | id | kinds | entry point |
|---|---|---|---|
| Bar | omarchy.bar |
bar |
bar/Bar.qml |
| Image picker | omarchy.image-picker |
overlay |
image-picker/ImagePicker.qml |
| Emojis | omarchy.emojis |
overlay |
emojis/Emojis.qml |
| Clipboard mgr | omarchy.clipboard |
overlay |
clipboard/Clipboard.qml |
| Reminders | omarchy.reminders |
overlay |
reminders/ReminderFlow.qml |
| Omarchy menu | omarchy.menu |
menu, bar-widget |
menu/Menu.qml, menu/BarWidget.qml |
| Notifications | omarchy.notifications |
service |
notifications/Service.qml |
| Audio | omarchy.audio |
bar-widget |
panels/audio/Panel.qml |
| Bluetooth | omarchy.bluetooth |
bar-widget |
panels/bluetooth/Panel.qml |
| Clock | omarchy.clock |
bar-widget |
panels/clock/BarWidget.qml |
| Monitor | omarchy.monitor |
bar-widget |
panels/monitor/Panel.qml |
| Network | omarchy.network |
bar-widget |
panels/network/Panel.qml |
| Power | omarchy.power |
bar-widget |
panels/power/Panel.qml |
| Tailscale | omarchy.tailscale |
bar-widget |
panels/tailscale/Panel.qml |
| Model usage | omarchy.model-usage |
bar-widget |
model-usage/Panel.qml |
| Weather | omarchy.weather |
bar-widget |
panels/weather/BarWidget.qml |
| Media | omarchy.media |
service, bar-widget |
services/media/Service.qml, services/media/BarWidget.qml |
| Battery | omarchy.battery |
service |
services/battery/Service.qml |
| Idle | omarchy.idle |
service |
services/idle/Service.qml |
| Night light | omarchy.nightlight |
service |
services/nightlight/Service.qml |
| Lock screen | omarchy.lock |
service |
lock/Service.qml |
| OSD | omarchy.osd |
panel |
osd/Osd.qml |
| Polkit agent | omarchy.polkit |
service |
polkit/PolkitAgent.qml |
First-party bar-only widgets also carry manifests next to their QML files,
e.g. bar/widgets/Workspaces.manifest.json. Rich popup widgets live in their
own plugin directories, each with its own manifest.json.
Bar
The built-in status bar and default full-bar option. Layout lives in the
top-level bar: subtree of ~/.config/omarchy/shell.json (with the shell
providing config/omarchy/shell.json when
the user has no file). See bar/README.md for the widget catalogue
and customization schema.
Image picker
Fullscreen image-grid selector overlay. Used by omarchy-menu-images
(wallpaper picker) and omarchy-theme-switcher (theme picker) and any
other caller that wants to present a directory of images with previews.
Two ways to drive it:
- Shell-level summon:
omarchy-shell shell summon omarchy.image-picker '<jsonPayload>'. The payload can carryimageDirs,imageRows,selectedImage,selectionFile,doneFile,showLabels,filterable. Best for in-shell callers that already speak JSON. - Direct IPC target:
omarchy-shell image-selector open <imageDirs> <imageRowsB64> <selectedImage> <selectionFile> <doneFile> <showLabels> <filterable>. Positional args;imageRowsB64is base64-encoded so embedded newlines / tabs survive the bash argv handoff. This is whatomarchy-menu-imagesuses. Colors come from the central shell theme singleton; there is no per-call override surface.
The selection round-trip remains file-based: callers create a
selection_file and done_file (both mktemp), pass the paths, and
poll done_file for existence. The plugin writes the chosen path into
selection_file and touches done_file when it's done. cancel IPC
clears it without writing a selection.
The plugin has keepLoaded: true so the layer-shell window survives
between summons within a single shell session.
Lock screen
Session-lock surface using Quickshell's native WlSessionLock and two
separate PAM services: omarchy-lock-password for password auth and,
only when fingerprints are enrolled, omarchy-lock-fingerprint for
fingerprint auth. It mirrors the previous lock screen field dimensions,
colors, blurred wallpaper, placeholder, and Hyprland-driven corners.
Polkit agent
Theme-aware authentication dialog for privileged actions. It uses
Quickshell's native Quickshell.Services.Polkit.PolkitAgent backend and
runs inside the long-lived omarchy-shell process, replacing the old
polkit-gnome-authentication-agent-1 autostart.
Omarchy menu
Quickshell-powered Omarchy command menu.
The menu UI lives in menu/Menu.qml as a first-party menu plugin and is
summoned through the shell (omarchy-shell shell summon omarchy.menu ...),
so it shares the long-running omarchy-shell process instead of starting a
second Quickshell instance.
The menu definition lives outside the shell host code:
- defaults:
default/omarchy/omarchy-menu.jsonc - user extensions:
~/.config/omarchy/extensions/omarchy-menu.jsonc
The shell parses both JSONC files at startup (with watchChanges: true
so edits take effect without a restart), evaluates when: / checked:
bash expressions in a single batched subprocess, and executes the
selected action: string directly via Quickshell.execDetached. The
long-running shell process keeps the parsed menu in memory, so the
keybind → IPC → visible path costs ~30ms cold.
Coming soon
omarchy.theme-switcher— folds theme switching into the shell.