Network panel now polls 'ip route get 1.1.1.1' for the active interface
and renders a key/value grid with IP/CIDR, gateway, link speed +
duplex (ethernet) or signal/freq/bitrate (Wi-Fi). One bash script
collects everything in TSV.
Wi-Fi list now works on Omarchy's default iwd+impala stack: when
nmcli isn't installed, falls back to 'iwctl station <dev> get-networks'
and parses the connected/SSID/signal/security columns. The list is
hidden entirely when no Wi-Fi station device exists (ethernet-only
machines).
Right-click on the bar widget and the bottom 'Open Wi-Fi manager
(Impala)' button both run omarchy-launch-wifi directly (was wrapped
in extra OMARCHY_PATH= env that wasn't actually doing anything).
Was gated on isConnected; users with paired-but-disconnected devices
(or devices Quickshell hasn't flagged as connected yet) saw no button
at all. Now shows on every known device. Action follows state:
connected -> disconnect, otherwise -> forget. Tooltip label flips to
match.
Bluetooth rows no longer hover-show a tooltip — clicking to connect
or pair is self-evident from the layout. Connected rows now ignore
their own click and surface a small × button on the right that
disconnects (with its own tooltip + urgent-color hover affordance).
Right-click still forgets the device.
PillButton's attached ToolTip is now a styled instance — uses the
widget's foreground color for text + border and a configurable
tooltipBackground (defaults to a near-black, overridable to
bar.background). No more stock OS chrome inside our popups.
Bluetooth panel passes bar.background/foreground through to its
header pills and replaces the row tooltip with the same themed style.
Each device row is now a two-line entry: name on top, status on the
bottom (Connected · 80%, Connecting…, Pairing…, Disconnecting…, Failed,
Available · click to pair, or Paired). Status text and the bluetooth
icon both color-code by state — connected/active draws in foreground,
idle in dim, failed in urgent.
Pending click attempts arm a 10s timer; if the device hasn't reached
the expected state by then the row flips to 'Failed' with an urgent
color so you can see the connect attempt didn't take. State change
listeners clear the pending flag the moment the device actually
connects or pairs.
Hover tooltips and right-click-to-forget moved into the row so the
behavior is self-contained.
- Scan and Manage are now small icon-only buttons in the header beside
the On/Off pill, with hover tooltips via the new PillButton.tooltipText
prop (backed by QtQuick.Controls ToolTip).
- New discoveredDevices list pulls everything in Bluetooth.devices that
isn't paired/connected/bonded/trusted. While scanning, those appear
under a 'Discovered' header. Click pairs and connects.
- Empty-state text only shows when there are no known and no
discovered devices, otherwise it would push down real entries.
- Header now uses anchors so the on/off pill sits cleanly at the right
instead of relying on a fixed-width spacer that drifted as content
grew.
- Discovery toggle moved out of the header and into a labeled action
row alongside a new 'Manage…' button that launches
omarchy-launch-bluetooth (the Impala TUI).
- Empty-state message tells you what to do based on adapter state
(off / scanning / no paired devices) instead of one generic line.
wttr.in query switched from one-blob format to pipe-delimited fields
(location|condition|temp|feels|wind|humidity). The popup parses each
piece into its own property and renders a clean key/value grid:
fort lauderdale, florida, us
Partly cloudy
Temperature +82°F (feels +91°F)
Wind ↗15mph
Humidity 82%
[Refresh] [wttr.in]
PopupCard gains a triggerMode property — "click" stays as the default
(focus-grab dismissal), "hover" skips the grab so the cursor can move
freely between the trigger and the card. PopupCard also exposes a
containsMouse alias for widgets that want to coordinate hover state.
Weather widget now shows on hover and hides 220ms after the cursor
leaves both the trigger and the popup. The redundant tooltip is gone —
the popup is the detail view. Click is still wired for refresh
(middle) and notification dump (right).
PopupCard now uses HyprlandFocusGrab so any popup in the shell closes
when the user clicks outside it (or any other window in the listed set).
Single primitive — every widget gets it for free.
Weather popup rewritten:
- Smaller cloud glyph beside the city name on one row, both vertically
centered.
- Condition string flows on its own line and wraps inside the popup.
- Refresh and wttr.in pills sit side by side instead of stacked, no
more dead vertical space.
- contentHeight tracks the actual content height so the popup is no
taller than it needs to be.
The legacy 'workspaces' built-in renders cleaner than the animated
workspacesPro file widget. Removed the widget file, its first-party
registration in Bar.qml, and updated the default layouts and docs.
'workspaces' loses the (legacy) tag in the catalog since there's no
alternative anymore.
omarchy.bar-settings and omarchy.image-picker are shell infrastructure —
the customizer and image picker themselves. Listing them in 'Other
plugins' with move/remove arrows is misleading: removing the panel
plugin doesn't disable anything (it's summoned on demand via IPC), and
adding them never made sense.
Dropped them from shell-defaults.json's plugins[] and from the inline
builtinShellConfig fallback. normalizeDraft and availableToAdd now skip
manifests with __isFirstParty so existing user shell.json files
self-heal on next load.
Two handlers were declared for the same property-change signal — QML
refuses to load the file with 'Property value set multiple times', so
summoning omarchy.bar-settings failed silently. Merged the debug log
into the existing catalogRevision++ handler.
suppressReload was eating the file-watch callback that would have
called loadConfig and refreshed the draft from disk, so the GUI stayed
on the pre-reset layout even though bar.json was already correct.
Apply the reset payload directly to draft (and bump draftRevision) so
section editors re-render in the same frame.
Every layout mutation (add/remove/reorder, settings dialog Apply,
position or centerAnchor change) now writes ~/.config/omarchy/bar.json
immediately. No more Save button — replaced with an inline 'Auto-saving
to ...' status line next to Reset to defaults.
To stop the FileView feedback loop (write → inotify → reload → set
draft → write again) the shell keeps a one-shot suppressReload flag
that swallows the userFile.onLoaded triggered by our own setText.
Reset previously could zero the bar out when defaultConfig was empty —
either because bar-defaults.json hadn't finished loading or because the
resolved path was wrong. The shell now carries an inline builtinBarConfig
that mirrors bar-defaults.json, and Reset uses it whenever the on-disk
defaults aren't yet populated.
Also: log the resolved omarchyPath / defaultsPath / userConfigPath at
startup, and route FileView load failures through console.warn so the
next time a user hits this they can see why.
The diff serializer would write only the sections the user touched and
rely on the bar merging defaults back in at load time. After a Reset to
Defaults the user file became {}, so the next 'Add widget' diffed
against the section and wrote ONLY the user-added entry, dropping every
default widget the user could see in the GUI.
- Reset to Defaults now writes the merged defaults verbatim so the GUI
and bar.json agree on the full widget list, and any subsequent edit
operates on that explicit list.
- Save writes the full draft, not a diff. The user gets a predictable
what-you-see-is-what-you-save customizer at the cost of Omarchy
defaults changes not silently propagating after the first save.
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.