Five plugins each owned their own colors.toml watcher with subtly
different parsers, plus two ad-hoc per-theme override files
(notifications.json, image-picker-colors.json). This collapses all of
that into one source of truth: Commons/Color.qml watches colors.toml +
shell.toml and exposes Color.bar.*, Color.popups.*, Color.notifications.*,
Color.menu.*, Color.imagePicker.* for every surface to bind to.
shell.toml is generated by the existing template pipeline from
default/themed/shell.toml.tpl. Themes can ship their own shell.toml to
override individual keys; everything missing falls back to the
foundational palette via root.pick(). Settings panel is intentionally
not themable beyond the foundational tokens.
last-horizon and solitude ship a minimal shell.toml to preserve their
historical 'notification border matches Hyprland active border'
behavior, which previously came from parsing the theme's hyprland.conf
(now removed).
Quickshell's UPowerDevice.percentage is a 0.0-1.0 real (energy /
energyCapacity), not 0-100. The bar was treating it as 0-100, so a 99%
charge picked the lowest icon, the tooltip read "1%", and the
low-battery active state was always true.
Settings dialogs were rendering text/spin/combo controls with Qt's
default light style, which clashed with the dark Omarchy theme — black
text on dark fills, white dropdown popups, rounded corners, and a
pill-style toggle that didn't match the native StyleToggleRow switch.
- Style.qml: collapse all radius tokens to 0 so Noctalia-compat widgets
inherit Omarchy's sharp-corner convention
- NToggle: rewrite to mirror the native 48x22 switch with accent fill
and a sliding 16x16 thumb
- NComboBox: skin contentItem, indicator, popup, and delegate so the
dropdown reads on dark themes
- NSpinBox: add a styled background, contentItem, and up/down indicators
- SettingsPanel calendar form + DynamicSettingsForm string field: drop
default TextField chrome for the same translucent foreground fill used
by WidgetCard, accent border on focus
Replace the inline detail flyout with a two-column hero: location chip
and large temperature on the left, big condition icon on the right.
Stats (Feels / Wind / Humidity) move below as caps-labeled columns,
separated from the forecast by a thin divider. Forecast cells now lead
with the day icon and put the day name above hi/lo temps.
Drop the Refresh and wttr.in pill buttons; a Timer refreshes the
forecast every refreshMinutes (default 15). The interval is exposed
through the Settings panel — Bar -> Weather has a gear that opens a
SpinBox-backed form, persisted as "settings": { "refreshMinutes": N }
in shell.json.
Replace the hover-driven detail flyout with a click-toggled popup that
includes a Noctalia-style row of upcoming days. Switching the wttr.in
fetch to the j1 JSON gives us structured current conditions plus a
3-day forecast; each day's representative icon comes from the hourly
entry nearest noon, mapped through the same code → glyph table as the
bar pill.
Text.implicitWidth/Height can be fractional. Passing those values
straight to the PopupWindow rounded the Wayland surface down, clipping
the 1px right/bottom border of the bubble.
Two leftover spots still pointed at command names that were renamed in
this branch:
- default/quickshell/omarchy-shell/README.md:124 said
`omarchy restart quickshell` (renamed to `omarchy restart shell`).
- config/hypr/bindings.lua:35 had a commented-out example using bare
`omarchy-menu`. The wrapper still works that way for now, but the
canonical hot path is `omarchy-shell-ipc menu toggle root` \u2014 update
the example so users copying it learn the right pattern.
The old bash bin short-circuited with close_visible_quickshell_menu at
the top of open_quickshell_menu, so pressing any menu keybind while the
menu was open just closed it. The IPC path I introduced summoned every
time, so keyboard shortcuts couldn't dismiss \u2014 only outside clicks did
(which is why the bar icon appeared to toggle: clicking it while the
menu was open hit the menu's outer-click MouseArea, not the icon).
Add a menu.toggle IPC method:
- if root.opened: cancel() and return "closed"
- otherwise: behaves exactly like summon
Switch every Hyprland binding and the two bar-icon click handlers
(omarchy logo, battery right-click \u2192 power menu) to toggle. summon stays
as the always-open primitive for callers that want explicit summon
semantics (rare \u2014 right now nothing internal uses it).
With bin/omarchy-menu gone there's no caller that summons the menu by
writing a JSON tempfile and polling a done file. Drop everything that
existed only to support that handshake:
- menuJsonFile / menuJsonFileView / loadMenuJson / openMenu(json,...)
- pendingSelectionFile / pendingDoneFile / selectionFile / doneFile
- mergeProviderJson (the JSON-wrapper for provider output)
- applyProc / releaseProc / doneFilesToRelease / finishDoneFile /
releaseNextDoneFile / resetRequest / requestActive / closeMenu
- the menuBin property pointing at the deleted bin
applySelected is now four lines (close + execDetached); cancel is two
(close + clear filter). open() takes either {menu: id} or a bare initial
menu name, and hands off to openExistingMenu which uses the cached items
from the JSONC FileViews.
Net: 91 lines smaller, no perf change.
The shell now owns the menu — both data and dispatch. The 821-line bash
bin was carrying the open path (cold spawn + jq pipeline + tempfile dance
+ IPC + poll) and a grab-bag of helpers that only existed inside it. Both
go away in this commit.
New bins for the few composite helpers that were genuinely worth keeping:
- omarchy-install-app NAME PKG
- omarchy-install-and-launch NAME PKG DESKTOP_ID
- omarchy-install-font LABEL PKG FAMILY
- omarchy-launch-config-editor PATH
- omarchy-reminder-set-interactive
- omarchy-capture-screenrecording-with-webcam
Sweep through default/omarchy/omarchy-menu.jsonc rewrites:
- present_terminal X -> omarchy-launch-floating-terminal-with-presentation X
- install_terminal X -> omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal X'
- install / install_and_launch / install_font / open_in_editor -> new bins above
- terminal X -> xdg-terminal-exec --app-id=org.omarchy.terminal X
- default_browser_is X -> [[ "$(omarchy-default-browser)" == "X" ]]
- default_terminal_is X / default_editor_is X / haptic_touchpad_is X same shape
- $(hypr_config_file X) -> ~/.config/hypr/X.lua
- show_custom_reminder_input -> omarchy-reminder-set-interactive
- screenrecord_with_webcam -> omarchy-capture-screenrecording-with-webcam
- stop_active_screenrecording -> omarchy-capture-screenrecording --stop-recording
Hyprland bindings switch from `omarchy-menu X` to
`omarchy-shell-ipc menu summon X` (the keybind hot path now skips bash
entirely). The Bar.qml omarchy widget and the battery right-click do the
same. ALT+PRINT becomes a one-liner: stop the recording if one is going,
otherwise summon the screenrecord submenu.
Measured: keybind-to-visible is ~30ms (was ~235ms). The shell's plugin
keepLoaded:true stops being theoretical \u2014 the menu data lives in memory
across opens, and the only work between keypress and paint is the IPC
roundtrip and the layer-shell window mount.
omarchy-menu used to spawn an 821-line bash bin on every keybind: 11 jq
invocations against omarchy-menu.jsonc, mktemp dance, summon, poll. The
shell waited ~235ms (keybind to visible) before painting the first row.
The bar plugin host is already a persistent process with the menu plugin
loaded (keepLoaded: true) — it just wasn't allowed to own the menu data.
Now it does:
- parseMenuJsonc / normalizeItem ports the bash bin's jq pipeline to
JS: strip // comments + trailing commas, infer parent from dotted
ids, dedupe keyword tokens across id/aliases/keywords, fill defaults.
- Two FileViews with watchChanges: true on the default and user
extension JSONCs. Edit either file and the in-memory item map
refreshes without a restart.
- IpcHandler exposes menu.summon(initialMenu) / refresh() / close() /
ping(). summon resolves aliases the way the old bash route_target
did, including the leaf-alias-is-an-action shortcut.
- Action dispatch is now direct: when summoned via IPC (no selection
or done file), Quickshell.execDetached(['bash','-lc', action]) runs
the action and the menu closes. The legacy tempfile dance is kept
only for the duration of the migration window.
- provider sub-menus (fonts, power-profiles) are inline: tab-delimited
bash one-liner per provider, parsed by mergeProviderRows.
- when:/checked: bash expressions are batched into one Process at
shell startup (and again whenever the JSONCs change). The menu
opens instantly; visibility/check marks settle within ~50ms.
Measured: keybind-to-visible via the new IPC path is ~30ms (was ~235ms).
The bash bin still works for now and is removed in the next commit.