Every toggle you could only reach by mouse or panel hotkey is now
scriptable through omarchy-shell:
- omarchy.bluetooth toggleBluetooth — the radio (was right-click / B)
- omarchy.network toggleNetwork — Wi-Fi on/off (was W in the panel)
- omarchy.tailscale toggleTailscale — the connection as one call,
matching the widget's right-click, instead of separate up/down
- omarchy.clock toggleWeekStart — calendar week start (was W)
- shell toggleBarTransparency — the bar background (was manage UI only)
Bluetooth and network follow the power/monitor pattern: manageIpc: false
so the panel owns the target's single IpcHandler and can extend it past
the inherited lifecycle methods.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Dismiss bar panels when clicking on another monitor
* Preserve keyboard focus for reopened panels
* Harden cross-monitor panel dismissal
* Wait for panel mapping before releasing focus
---------
Co-authored-by: David Heinemeier Hansson <david@hey.com>
* Add Wi-Fi QR sharing to network panel
* Refine Wi-Fi QR sharing
* Use QR glyph for Wi-Fi sharing
* Add click-to-reveal password to the Wi-Fi share card
Scanning the QR is the fast path, but the person typing on a laptop needs
the actual password. A dimmed "Show password" hint under the QR toggles
the secret in place.
The password stays out of the shell until asked for: a click runs the new
omarchy-network-password helper (a private pipe, never an argument), and
closing the card drops it again. Open and enterprise networks never show
the control.
The card loses its Close button -- Escape and clicking outside already
cover it -- and now sizes itself to its content.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Keep a dismissed Wi-Fi share card closed and make Escape reliable
Closing the card mid-generation killed the helper, but its buffered
stdout still arrived and repopulated the matrix, reopening the card the
user just closed. Both collectors now honor qrExpectedStop, and the flag
survives onExited because exit and stream-finished have no guaranteed
order. The password fetch gets the same treatment so a reveal in flight
during dismissal can't stash the secret into a closed card's state.
The content's focus was claimed while the window was still unmapped, so
Escape could land nowhere. Re-acquire it after mapping, the way
KeyboardPanel does.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Share WEP networks correctly and restore the QR quiet zone
NetworkManager models WEP as key-mgmt "none" plus a wep-key, so the QR
helper encoded WEP networks as open -- a QR that scans fine and then
silently fails to join. Encode them as T:WEP and let the password helper
print the key.
Also widen qrencode's margin from 2 to the spec's 4-module quiet zone;
the card surround is dark, so that white border is all a scanner gets.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Pins the active connection to 2.4/5/6GHz, or leaves the choice automatic.
The section only appears on Wi-Fi when the network answers on more than one
band, or while a pin is in force so it stays clearable.
bin/omarchy-network-band reports and sets the band. It sets NetworkManager's
802-11-wireless.band rather than pinning a BSSID: NM 1.44+ accepts a third
band value so 5 and 6GHz can be pinned apart, and a band survives an AP
rotating its BSSIDs while leaving roaming between APs intact. It refuses a
band the network does not answer on, and restores the previous setting if
reassociation fails rather than leaving the machine offline.
Under Automatic the pills collapse and the header reads the live band
("WI-FI BAND: 2.4GHZ"); pinning reveals them and the header goes plain. The
height is animated, and the section stays mounted through the reconnect a
band change causes -- otherwise `kind` briefly stops being "wifi" and the
whole segment would tear down and rebuild.
Two fixes to the same panel fall out of this:
- Every stat row stays mounted and reads "--" until it has data, instead of
appearing a beat after the panel opens and shoving the rows below down.
- The speed test's Run button is scaled to match the band header's controls
and joins the keyboard cursor chain.
ToggleSwitch gains a settable trackHeight so a compact placement can render a
genuinely small switch instead of scaling one down onto fractional pixels.
Defaults are unchanged.
Claude-Session: https://claude.ai/code/session_01XcKqYe1n5bnRBuAZwikwsr
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add Tailscale header on/off toggle
* Address Tailscale toggle review feedback
* Hide Tailscale tooltip while busy
* Keep Tailscale labels clear of header toggle
* Extract the switch from Toggle into a reusable ToggleSwitch
Toggle rendered its own track and knob inline, so anything else wanting a
switch had to copy the geometry. Pull it into Ui/ToggleSwitch.qml and let
Toggle compose it, and give PanelHero a trailingControl slot so a hero can
pin a control to its trailing edge without the caller doing the layout.
The switch draws its cursor as a ring outside the track: themes give normal
chrome a stronger border than hover-cursor, which is right for controls that
are borderless at rest but would make a bordered track go fainter under the
cursor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Keep the panel cursor on the virtual header section
clampCursor resets focusSection whenever it is not in visibleSections, but
"header" is virtual and never appears there. Any refresh of the underlying
model therefore threw the cursor off the hero toggle: muting republishes the
PipeWire snapshot, and toggling the Bluetooth adapter empties and refills the
device lists. moveCursor already special-cases "header"; clampCursor now does
too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Toggle panel heroes with a switch instead of the status icon
The hero icon doubled as the on/off control, which was invisible as an
affordance and made the icon carry two jobs at once. Give Tailscale, Dropbox,
Bluetooth, Audio, and Network a ToggleSwitch on the trailing edge of the hero
and leave the icon to report status. The switch is the header's only cursor
target, so the keyboard reaches it the same way the mouse does.
Dropping the icon's focus ring also drops heroRingPad, which lets each hero
line up with the rows beneath it. Network's link detail moves inline after the
name -- "Ethernet (2.5gbit)" -- since the pill crowded the switch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Make the network hero switch honestly a Wi-Fi switch
The switch reads and writes Networking.wifiEnabled, but its tooltip claimed to
turn "network" on and off whenever Ethernet was the active connection. A click
asserted nothing; a switch asserts state, so on a wired machine with the radio
off it sat there reading "off" beside a perfectly live Ethernet connection.
Say Wi-Fi, and only offer the switch when there is a radio to switch.
headerActionCount follows the same condition so the keyboard cannot reach a
control that is not there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Stop the header cursor claiming an absent switch
The switch hides when the thing it toggles is unavailable -- no Tailscale CLI,
no Dropbox CLI, no Wi-Fi radio -- but "header" stayed reachable, so the cursor
could sit on a target that never rendered. The old clickable icon was always on
screen, so there was always something to highlight.
"header" stays navigable and Enter still no-ops safely; the cursor just stops
claiming a spot that is not there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Wrap the gallery's switch caption
The caption sat unbounded inside the switch row. The gallery has a 560px
minimum width, horizontal scrolling off, and clipping on, so at that size the
end of the line was simply unreachable. Move it below the row and wrap it, the
way every other description in the gallery already does.
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>
argv is world-readable in /proc for every local process while the
connection attempt runs. Create the profile without the secret, then
set it through nmcli's scriptable connection editor, which reads from
stdin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each panel's hero on/off toggle (Tailscale, Dropbox Wi-Fi, Bluetooth,
network, audio mute) now has a reachable "header" cursor section, a focus
ring around the hero icon, Enter/Space activation, a letter shortcut, and
hover parity. The hero is inset by heroRingPad so the ring stays inside
the Flickable/ScrollView clip box instead of being cut off.
Bluetooth and audio get a virtual "header" section above their device
sections, so the adapter/mute can be toggled by keyboard even when no
device rows exist. Network repurposes its previously dead header section.
For the PanelHero-based panels (Tailscale, Dropbox), route panel state
through the wrapper's `header` id: inside a PanelHero iconComponent `root`
resolves to PanelHero, not the Panel, so `root.headerHasCursor` and
`root.setHeaderCursor()` silently referenced the wrong object.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pill was populated by polling omarchy-network-status (previously
every 3s, then 10s). Derive kind/signalStrength directly from the
Quickshell.Networking service instead: wired-connected → ethernet
(preferred, matching the default-route device), else a connected Wi-Fi
network → wifi with its signal, else disconnected. The service's
properties are change-notified, so the pill updates on connect/
disconnect and signal changes with no polling and no subprocess.
Removes the last periodic poll from the bar; the label/frequency pill
fields were write-only and are dropped.
Verified live: pill tracks ethernet → disconnected → ethernet in real
time as the wired device goes down and up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each `bash -lc` starts a login shell that re-sources the profile
(mise activation, /etc/profile.d) on every invocation — ~16 forks per
call versus ~2 for `bash -c` — which taxes every menu/panel/launcher
action the shell shells out for. The session already exports PATH and
env to the shell, so omarchy commands resolve fine under `bash -c`.
Switch the internal/omarchy-owned spawns (theme+background switches,
brightness, monitor scaling, DNS, lock/fingerprint, keyboard-layout
probe, voxtype status, and the `:`/printf state-file writes) to
`bash -c`. Leave `bash -lc` on the sites that run user-configurable
commands (custom bar-widget exec, menu provider/guard scripts,
launcher scan commands, configurable idle/screensaver command), where
a user's command may rely on their login environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The monitor panel polled `omarchy-monitor-state` every 5s (26 forks per
call) and the network panel polled `omarchy-network-status` every 3s,
both with `running: true` rather than gating like the sibling panels.
Monitor: gate the poll on `opened` (brightness/scale are only shown in
the open panel, which already refreshes on open) and drive the bar glyph
from Quickshell.screens instead of the polled display list, so monitor
count stays correct without polling.
Network: the bar pill needs live-ish state, so keep polling but at 10s
instead of 3s. (Deriving the pill from the native Networking service
would remove the poll entirely; left as a follow-up since it needs Wi-Fi
state testing.)
Idle fork rate drops ~20/s to ~14/s.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>