Quickshell.shellDir points at the directory holding the loaded
shell.qml, so the bar can find its own widgets and bar-defaults.json
without depending on OMARCHY_PATH being set in the environment. The
old behaviour silently fell back to ~/.local/share/omarchy when the
env var was unset, which is fine for the installed shell but caused
'No such file or directory' for every first-party widget when running
quickshell -p straight from the repo.
- firstPartyWidgetSource now uses Qt.resolvedUrl("widgets/<name>.qml")
so the resolution is anchored to shell.qml itself.
- deriveOmarchyPath() in both bar/shell.qml and bar-settings/shell.qml
walks back from Quickshell.shellDir to find the omarchy root, with
OMARCHY_PATH still allowed as an explicit override and the install
default as a final fallback.
CRITICAL:
- Re-inject working entry into the settings form every time the dialog
opens. Previously the Loader's onLoaded only fires when the
sourceComponent identity changes, so reopening the dialog on the same
widget id (or on a second instance of the same id, e.g. two spacers)
showed stale field values.
- 'Add widget' menu and the centerAnchor dropdown both read
draft.layout[section] without depending on the section array
identity. Mutating the array in place did not invalidate those
bindings, so the menu would show a stale availability list and let
non-spacer widgets be added twice. Layout mutations now replace the
whole object so any binding that reads it re-evaluates.
WARNINGS:
- Save now writes only the diff against the merged defaults so future
bar-defaults changes are not silently shadowed by a stale snapshot.
- FileView onFileChanged handlers simplified to a single reload() —
the explicit second loader call was duplicating loadConfig and
bumping barConfigSerial twice per change (visible flicker).
- userFile onLoaded ignores reloads while the user has an unsaved
working draft so external edits during editing do not clobber the
in-progress state.
- SpinBox forms switched from onValueChanged to onValueModified so the
initial-value bind no longer fires fieldChanged.
- Hyprland window-rule sizes aligned with the FloatingWindow
implicitWidth/Height values (720 main, 380 dialog).
- omarchy-launch-bar-settings falls back to initialTitle when focusing,
so a dialog that has not yet had its title applied still surfaces
the existing instance.
Each layout entry is now an object — {id, ...inline settings} — instead
of a (name, separate modules map) pair. Multiple instances of the same
widget are trivially supported (spacer is the main beneficiary). Settings
travel with the entry, so reordering keeps them attached.
shell.qml:
- normalizeLayout converts string entries to {id} and drops invalid ones
- ModuleSlot binds to a full entry; moduleName and moduleSettings derive
from it via entryId/entrySettings helpers
- centerAnchor lookup walks layoutEntries directly
- builtinBarConfig defaults use the object form
- README + bar-defaults.json updated to the new schema
bar-settings/ (new Quickshell config):
- Top toolbar with Reset / Save and Position / centerAnchor dropdowns
- Three section editors (left/center/right) with current widgets shown
as cards: name + description + move up/down/settings/remove buttons
- '+ Add widget' menu per section, sorted alphabetically by display name
(only spacer can repeat; everything else is one per bar)
- Per-widget settings dialog (FloatingWindow) loads an inline schema for
widgets in the catalog. Initial schemas: spacer (size), calendar
(formats), brightness (step)
- FileView { atomicWrites } writes to ~/.config/omarchy/bar.json on Save
omarchy-launch-bar-settings + hypr window rules pin the settings window
to a 760px floating square. controlCenter gains a 'Customize bar…' button
that launches it.
Single bar button (md-tune-vertical glyph) that opens a popup with the
controls most users reach for repeatedly throughout the day:
- Volume slider + mute toggle (Pipewire defaultAudioSink)
- Brightness slider (brightnessctl, coalesced via writeTimer)
- 2x2 tile grid: Do Not Disturb (makoctl mode), Night Light (omarchy-
toggle-nightlight, temperature probed), Keep Awake (hypridle pid
detection), Theme (current symlink + opens omarchy menu themes)
- Power-profile row (Saver / Balanced / Performance), hidden when
power-profiles-daemon is not installed
Polled every 3s via Process + StdioCollector pairs gated by !running
busy checks. Default layout adds it before powerMenu on the right.
- nightLight queries hyprctl hyprsunset temperature to detect state
instead of pgrep, since omarchy-toggle-nightlight keeps the daemon
running across toggles. Adds a 'toggling' busy flag so repeated
clicks do not stack the toggle script.
- lockKeys reads each LED individually and emits 'missing' when the
class is absent, fixing column-shift mislabels on keyboards that
only expose a subset of the locks. Hides + stops polling when no
LEDs are reported. Poll interval relaxed to 2s.
- activeWindow right-click now calls toplevel.close() against the
captured window instead of dispatching killactive against whatever
is focused at click time.
- Drop unused Common imports from activeWindow and lockKeys.
- activeWindow: shows the focused toplevel title from Quickshell.Wayland
ToplevelManager. Left-click activates, middle-click closes, right-click
killactive. Hidden in vertical bars to avoid title rotation noise.
- nightLight: toggles omarchy-toggle-nightlight; status detected via
pgrep hyprsunset. Hides when hyprsunset isn't installed.
- keyboardLayout: shows current xkb layout short code, click cycles via
hyprctl switchxkblayout. Refreshes on Hyprland activelayout events.
- lockKeys: caps/num/scroll lock indicators read from /sys/class/leds.
Uses plain A/1/S letters; hides each indicator when off.
- spacer: configurable blank space (size) for layout tuning.
Defaults add activeWindow to the left section and nightLight to the
right section between audioPanel and brightness.
- powerProfile now hides on machines without power-profiles-daemon by
checking against the published profile set rather than a never-undefined
enum, and uses mdi-scale-balance for Balanced (not battery-charging).
- networkPanel renames the QML-keyword-shadowing 'signal' property to
'signalStrength'; ethernet glyph corrected to mdi-ethernet (not the
access-point icon).
- workspacesPro indicator color guards against null bar during construction.
- Common.Slider keeps an internal liveValue so external value bindings
survive a drag; sliders fed from PipeWire/brightnessctl now follow
external changes after the user interacts.
- audioPanel drops the redundant default-sink PwObjectTracker since the
candidate-sinks tracker already covers it.
- networkPanel guards scan/connect commands against re-entry while the
previous Process is running.
- notificationCenter only instantiates its NotificationServer when the
module is opted in via settings.replaceMako (default off) so it does
not collide with mako out of the box.
- PopupCard fully opaque while open (previously 0.98) — removes a
visible ghost outline on first render.
- Drop dead code: calendar trailing MouseArea, weatherFlyout's unused
parseForecast/forecast property, idleInhibitor's holdSeconds,
media's scrollAnim.position; gate media's scroll animation off in
vertical bar mode.
- Replace nerd font glyphs that were stripped during the initial widget
writes with verified codepoints sourced from the JetBrains Mono Nerd
Font cmap (cpu-64-bit, scale-balance, notification-clear-all, etc).
- systemStats now switches to a Column layout when the bar is vertical
so the widget no longer overflows a 28px-wide side bar.
- Remove notificationCenter from defaults: it registers a notification
server and would collide with the mako daemon Omarchy autostarts.
- PopupCard owns the popout coordinator lifecycle and delegates close to
its widget owner when provided; calendar and media now pass owner.
- brightness debounces brightnessctl writes through a coalescing timer
to avoid spawning a process per slider tick.
- audioPanel tracks unfiltered Pipewire sinks/streams via PwObjectTracker
so audio metadata becomes available before filtering.
- ModuleSlot re-injects bar/moduleName/settings whenever the bar config
serial changes so widget settings update live with bar.json.
- Guard null bar references in workspacesPro and systemStats components
that are constructed before bar injection.
Introduces a widgets/ directory of first-party QML modules and a common/
helpers library. Widgets auto-load by name from
default/quickshell/bar/widgets/<name>.qml — no edits to shell.qml needed
for additions beyond the firstPartyWidgets registry.
New widgets (all orientation-aware, top/bottom/left/right):
- media: MPRIS now-playing with scrolling label, cover art popup,
play/pause/skip controls
- audioPanel: master volume slider, output device picker, per-app mixer
- networkPanel: Wi-Fi scan + connect, current connection details
- bluetoothPanel: paired device list, connect/disconnect, battery
- calendar: month grid popup keyed to today
- notificationCenter: live notification feed, DND toggle, clear all
- brightness: slider popup with scroll-to-adjust
- powerProfile: power-saver/balanced/performance picker (UPower)
- systemStats: inline CPU + RAM sparklines, expanded popup with btop
- weatherFlyout: weather popup with refresh and full report
- workspacesPro: animated focus indicator that slides between workspaces
- powerMenu: lock/suspend/log out/reboot/shutdown popup
- idleInhibitor: coffee-cup toggle wired to omarchy-toggle-idle
- microphone: mic state + mute toggle + scroll volume
Common helpers (default/quickshell/bar/common/):
- WidgetButton: hover/press scale animation, tooltip integration
- PopupCard: orientation-aware anchor + slide-in opacity transition
- Slider: drag/wheel-able linear slider with knob hover bump
- PillButton: rounded action button with icon + label
Bar root gains requestPopout/releasePopout so only one popup is open at
a time across widgets.
Updates bar-defaults.json to ship the new widgets out of the box while
keeping the legacy modules available.