Commit Graph
432 Commits
Author SHA1 Message Date
David Heinemeier HanssonandClaude Opus 5 e2d655d265 Add a calendar popup to the clock
Clicking the clock reveals a month grid with ISO week numbers, a year
progress meter, and month stepping. Right click walks the common label
formats and writes the chosen one back to shell.json, so the bar shows
what the config stores. The week start toggles from the grid's "W"
heading and persists as weekStartDay, defaulting to the locale's own
first day.

Rich popup widgets live in their own plugin directories, so the clock
moves out of bar/widgets/ into panels/clock/. The id is unchanged, so
existing layouts and centerAnchor keep working.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 19:10:06 -07:00
David Heinemeier HanssonandClaude Opus 5 c2610c3788 Give the weather panel its host widget's identity in the bar
The bar identifies a panel by the widget mounted in its slot, but the
nested panel handed the popout coordinator itself. The open-panel mark
never lit under the weather pill, and Tab could not leave the panel.
Closing for a popout switch also cleared the shared hover-reveal flag the
incoming panel had just set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 19:10:06 -07:00
David Heinemeier HanssonandClaude Opus 5 6d4fa7acaf Make the open-panel mark find and fit its module
A center-anchored module is mounted twice: the copy that is drawn, and a
zero-size placeholder holding its place in the flow beside the anchor.
findPanelWidget returned whichever registered first, and that order is
not stable across a live bar reconfiguration, so a panel could open
anchored to the invisible copy -- mispositioned, with the drawn slot
never lighting up and switchPanelFrom unable to find it again.

The mark was also always 55% of the slot, which fits an icon but
underlines only a fraction of a text label, and runs the full height of a
multi-line module on a vertical bar. Modules can now say how long the
mark should be along the bar; anything that does not answer keeps the old
proportion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 19:10:06 -07:00
David Heinemeier Hansson fc4caf3c60 Extract notification center bar widget 2026-07-25 10:59:09 -07:00
David Heinemeier HanssonandClaude Opus 5 e43ddf0eaa Say when there's no notification history to replay
Replaying an empty history did nothing at all on screen, which reads as
a dead keybinding rather than an empty log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:41:14 -07:00
David Heinemeier HanssonandClaude Opus 5 b65dde39fc Keep fingerprint unlock offered on the lock screen with the lid shut
The reader is still reachable on an external keyboard or a docked
laptop, so hiding the icon and skipping the scan just forced the
password. sudo and polkit keep their pam_exec lid gates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 08:52:31 -07:00
David Heinemeier HanssonandClaude Opus 5 e880cf77cc Alert on tmux output while its terminal is unfocused
tmux only raises an activity flag for windows that are not currently
selected, so a long-running command in the window you left selected --
the common case, since you switch away by moving your Hyprland focus
elsewhere rather than by selecting another tmux window -- finished
silently and the indicator never lit.

Windows now also count as waiting when they are selected in an attached
client, every client showing them is unfocused, and their window_activity
is newer than an @omarchy_unfocused_activity watermark. The watermark is
stamped by a new `track` subcommand wired to client-focus-in/out and the
existing select-window hooks, so it records where attention last was.
The hooks pass #{window_id} and #{window_activity} as arguments, which
tmux expands when the hook fires; run-shell -b would otherwise let output
arriving during the handoff be swallowed by the new watermark. The focus
hooks take index 100 to leave a user's own bindings alone.

That state has no hook of its own, so it needs polling to be noticed.
The probe therefore moves out of the indicator and into a service plugin,
alongside nightlight and battery. A bar surface exists per monitor and
each one instantiates every indicator twice, once per block, so a timer
on the indicator meant a shell-out per instance per tick -- four probe
processes every three seconds on a two-monitor machine, forever. One
service polls for the whole shell instead.

Sharing the state also fixes what per-instance polling would have papered
over: each indicator used to own its own count, so a timer-driven update
only refreshed the bar it ran on and left the other monitors stale.
Refreshes still arrive over the existing indicator broadcast, which now
coalesces into a single run no matter how many bars relay it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CC9kSQv8ZEogaxDoeKBzL
2026-07-25 07:19:02 -07:00
David Heinemeier Hansson 2503edadfc Jiggle active terminal alert indicator 2026-07-25 07:19:02 -07:00
David Heinemeier HanssonandClaude Opus 5 896beb0c85 Park the indicators just left of the clock
The lineup now reads toward the clock instead of away from it, so Stay
Awake sits nearest the time, then do-not-disturb, night light, reminder,
screen recording, dictation, and tmux alerts trailing off to the left.

Active indicators render after the hover-revealed ones so they stay
against the clock, and a newly active indicator joins on the far side
rather than shoving the ones already showing sideways.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 07:14:58 -07:00
David Heinemeier HanssonandClaude Opus 5 c6ad61448f Stop the launcher from listing an app more than once
The apps list rebuilt its rows by writing into the maps held by the menu's
items and itemOrder var properties. Writing into an object owned by a QML var
property is not reliable: the same row object written into a plain JS object
always lands, but written through the property it occasionally arrives with the
key created and the value undefined. One write per rescan was lost, on a
different app each time.

A lost write left an id in itemOrder with no item behind it. The old purge only
deleted app rows it could find in items, so the orphan survived the next merge,
the add loop appended a second row for the same app, and the list grew by one --
permanently, and again on every later rescan. Touching a single desktop file
fires around a dozen merges, because the entry model emits valuesChanged per
insert and removal while it reconciles, so duplicates piled up quickly: nine
YouTube rows on the reporting machine, and Alacritty doubled before that.

The bookkeeping moves into MenuModel as two pure functions that build fresh maps
for the caller to assign in one shot, so the fragile write disappears. They also
make the merge self-healing rather than merely correct-when-nothing-is-lost: an
id with no item is dropped instead of carried forward, and an id is listed once
even when two desktop entries claim it -- so no single dropped write can compound
into a duplicate row again. The bash-backed providers behind the font and power
profile lists had the same latent bug and get the same treatment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJQJfHXXApUk6En8EZisHg
2026-07-24 19:42:07 -07:00
David Heinemeier HanssonandClaude Opus 5 a7285cfecb Put Stay Awake first among the default indicators
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CC9kSQv8ZEogaxDoeKBzL
2026-07-24 19:11:57 -07:00
David Heinemeier HanssonandClaude Opus 5 aa9f0c54c5 Add per-laptop speaker tunings, starting with the XPS 14
Laptop speakers ship voiced by the vendor's Windows DSP layer, which Linux does
not get. A tuning restores that as a PipeWire filter-chain in front of the
internal speaker sink, matched to the machine by DMI string and expected sink.

Adding a laptop is a directory under default/audio/tunings with two files and no
new code: matching is data. The XPS 14 DA14260 tuning included here was derived by
measuring the xps-audio-linux EasyEffects profile (MIT) and fitting a biquad chain
to it, so no impulse response or other upstream asset is redistributed. It measures
1.24 dB RMS against that reference, and matches its dynamic range within 0.1 LU --
the reference's multiband compressor turned out to contribute nothing, so a linear
chain replaces it. Bass Q is capped deliberately: a closer magnitude fit swung
group delay 31 ms across 63-80 Hz, which smears bass transients.

The graph runs as its own PipeWire client under its own config name rather than
loading into the audio daemon. The daemon only reads its config at startup, so a
daemon-loaded tuning could only be switched by restarting PipeWire -- which drops
every PulseAudio client's connection, and applications that do not reconnect
(Spotify) then have to be restarted by hand. Hosting it separately also contains
failure, since a malformed tuning breaks only that service.

Three things about the surrounding audio graph needed fixing for this to behave:

- Volume must live downstream of the tuning. omarchy-audio-output-sink is now the
  single definition of which sink an output's volume really uses, shared by the
  volume keys, the output switcher's OSD and the audio panel, so they cannot
  disagree. It resolves the current default output, which keeps it correct when
  headphones are selected while a tuning exists.
- The tuning's own output is a movable sink input, so rerouting "all streams" to a
  newly selected output would drag the processing onto headphones, or into the
  tuning's own sink, which is a cycle. It is pinned, and stream moves are limited
  to streams carrying an application.name.
- The physical sink a tuning fronts is not independently selectable, since picking
  it would only bypass the tuning, so it is kept out of the output list.

Applying happens at first-run, not finalize-user, because finalize-user also runs
in the ISO chroot where there is no audio server and nothing would retry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 18:25:02 -07:00
David Heinemeier HanssonandClaude Opus 5 248659de5a Give every OSD card exact padding on all four sides
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LfqzqpU1T6pQRsmpbGxBs5
2026-07-24 16:48:14 -07:00
David Heinemeier HanssonandClaude Opus 5 d2e1587ceb Refresh bar widgets on every monitor, not just one
A bar surface is built per monitor, so a widget in the layout is live once
per screen — but an IPC target only ever routes to the handler that
registered first. `omarchy.indicators refresh` therefore reached a single
bar, and since indicators only re-read their state on that signal, the
other screens kept showing a stale reminder count, tmux alert, or DND
state until the next reload. Clock and system-update refreshes had the
same reach.

Let the bar resolve every live instance of a widget id and relay the call
to all of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 14:16:22 -07:00
David Heinemeier HanssonandClaude Opus 5 49efe2dafa Drop the bluetooth widget's dead middle-click launcher
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 12:30:28 -07:00
David Heinemeier HanssonandGitHub b31036f604 Merge pull request #6334 from KazeTachinuu/enterprise-8021x-wifi
Connect to enterprise (802.1X) Wi-Fi networks from the network panel
2026-07-24 10:51:04 -07:00
David Heinemeier HanssonandClaude Fable 5 6963432600 Send the 802.1X password over stdin instead of argv
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>
2026-07-24 10:50:58 -07:00
7faf53c4c0 Name the monitor SCALE targets when multiple displays are connected
SCALE only applies to the focused monitor, which is easy to lose track
of with several displays. Show the focused monitor's name on the right
of the SCALE header (matching the BRIGHTNESS/TEXT SIZE value
convention), hidden when only one display is enabled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 10:12:28 -07:00
David Heinemeier HanssonandGitHub abc9ff03e1 Merge pull request #6366 from scottjones/fix-notification-surface-resize
Fix notification popups stretching/squishing when the count changes
2026-07-24 09:24:51 -07:00
David Heinemeier HanssonandClaude Opus 4.8 b6bd486c39 Bind notification popup mask to the toast column item
Use Region { item: popupColumn } instead of manually mirroring the
column's x/y/width/height. It tracks the item's mapped geometry directly,
so it stays correct without assuming the column shares the window's
coordinate space.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 09:24:30 -07:00
David Heinemeier HanssonandGitHub bc33381d84 Merge pull request #6348 from InfeCtlll3/agent/scroll-long-tray-menus
Make long tray menus scrollable
2026-07-24 09:09:17 -07:00
Scott JonesandClaude Opus 4.8 b1a9d7912e Give notification popups a fixed-size surface to stop resize scaling
The popups used one Wayland layer surface per output sized to their content
(implicitWidth/Height bound to the toast column), so every time a toast was
added or removed the surface resized. During that resize the compositor
briefly scaled a stale buffer, stretching or squishing the cards mid-
transition -- worst with only one or two toasts, where each resize is a
larger relative change.

Make the surface full-screen and fixed, like the OSD overlay, so adding or
removing a toast only changes the content inside it and the surface never
resizes. Mask the input region to the toast column so the otherwise-
invisible full-screen overlay stays click-through.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:47:53 -04:00
David Heinemeier Hansson 202296324a Keep display scaling options consistent 2026-07-23 20:37:25 -07:00
David Heinemeier Hansson 3e664286cc Use upcase consistently for menu tooltips 2026-07-23 20:09:39 -07:00
David Heinemeier Hansson 63955e787f Hide unavailable display scale options 2026-07-23 18:06:19 -07:00
David Heinemeier HanssonandClaude Fable 5 5b130c1063 Make tmux alert parsing and focus more robust
Session names can contain pipes but never colons, so split fields on
colons instead. Remember refreshes that arrive while the indicator is
already polling, and jump to the most recently used tmux client rather
than an arbitrary one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:40:47 -07:00
David Heinemeier HanssonandClaude Opus 4.8 a382be2645 Indicate and jump to tmux panes waiting for attention
Show a bar indicator whenever tmux has flagged a window, the same state
that highlights the tab, and jump to it on click or with Super + Ctrl + J.
Tmux hooks push the state to the shell, so nothing polls while no pane is
waiting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 17:40:47 -07:00
David Heinemeier Hansson ed9e68957f Match display scale highlights to clean scales 2026-07-23 16:48:29 -07:00
David Heinemeier Hansson 7798ed34b1 Keep apps menu alphabetically sorted 2026-07-23 16:32:31 -07:00
David Heinemeier Hansson 5d83402e75 No need to count the displays 2026-07-23 16:19:29 -07:00
David Heinemeier Hansson 7a9947a732 Combine the Omarchy menu and the launcher
Now that we can deep search, they don't need to be different
2026-07-23 15:04:22 -07:00
David Heinemeier Hansson e68b0f025d Merge branch 'fingerprint-lid-aware' into quattro 2026-07-23 13:52:31 -07:00
David Heinemeier HanssonandClaude Opus 4.8 540e411edf Show fingerprint on lock screen and polkit, gated by lid state
Bring the fingerprint affordance to the Quickshell lock screen and polkit
dialog, matching what hyprlock did on master.

Lock screen: render the md-fingerprint glyph inside the password field's
right edge when a sensor is enrolled, reserving space so long passwords
never run under it.

Polkit dialog: show one method at a time. When a sensor is enrolled and
the reader is reachable, the dialog is just the centered fingerprint icon
(square card); the moment PAM asks for a password it switches to the
password field. Detects pam_fprintd anywhere in the auth stack now that a
gate can precede it.

Lid awareness: a closed lid means the reader is unreachable, so both
surfaces fall back to the password. polkit gets a pam_exec clamshell gate
(auth [success=1 default=ignore] before pam_fprintd) so a shut lid drops
straight to the password prompt instead of blocking on the reader for the
pam_fprintd timeout; the lock screen hides the icon and skips scanning.

The gate points at the fixed /usr/bin path the package always provides so
it survives switching between package installs and dev-link. A migration
adds the gate for existing fingerprint setups.

New helper omarchy-hw-laptop-closed (pure lid state); omarchy-hw-clamshell
now composes it with the external-monitor check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:51:07 -07:00
David Heinemeier HanssonandClaude Fable 5 141432639f Keep the menu anchored to a stable top line and drop its minimum height
The menu card re-centered on every resize, so searching or moving into
a submenu made it jump around. It still opens centered like before, but
the first search keystroke or submenu move freezes the top line where
it sits — from then on the card only grows and shrinks downward.
Closing unfreezes. Also drop the 220px minimum height so the card
always hugs its content instead of leaving a void under short lists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:48:16 -07:00
David Heinemeier HanssonandClaude Fable 5 70f18e9a1c Keep notification QObjects out of the ListModels to stop shell crashes
Model rows stored the live Notification object in a ref role. When the
server destroyed the notification (sender close, DND untrack, dismiss)
the role became a dangling C++ pointer, and the next read segfaulted in
QQmlListModel::data — typically when replaying history over IPC. Track
live notifications in a JS map keyed by originalId instead, cleaned up
on close and untrack, where a stale reference degrades to a catchable
error instead of a crash.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:39:15 -07:00
Carlos Armando be71149500 fix(shell): scroll long tray menus 2026-07-22 18:49:18 -07:00
David Heinemeier Hansson 43e95217f2 Reload weather location when opening panel 2026-07-22 17:31:14 -07:00
David Heinemeier HanssonandClaude Fable 5 6a047462e8 Decode launcher and tray icons at physical resolution
Quickshell's IconImage decodes at logical size, so on HiDPI displays
PNG icons were rendered from a texture at half the needed resolution
and looked blurry next to SVG icons. Use plain Image with sourceSize
scaled by Screen.devicePixelRatio, as the notification widgets
already do.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 17:02:52 -07:00
David Heinemeier Hansson 60a550a646 Add --fit OSD option and use it for system shutdown/reboot/logout 2026-07-22 16:33:54 -07:00
David Heinemeier Hansson 1e928d58db Remove menu keywords feature 2026-07-22 16:06:55 -07:00
David Heinemeier Hansson 3177f7fd2c Use Netflix/fast.com endpoints for network speed test 2026-07-22 15:52:58 -07:00
David Heinemeier Hansson c899172c05 Use 'yy instead of yyyy in vertical clock date mode 2026-07-22 15:37:25 -07:00
David Heinemeier Hansson 0fc1884281 Show monitor hero top and hide brightness slider when no backlight 2026-07-22 15:35:07 -07:00
1c30b83733 fix(clock): display date, week, and year vertically in vertical bar. (#6336)
* fix(clock): display date, week, and year vertically in vertical

* fix vertical date alignment in panel

* Update Clock.qml

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Address review feedback on Clock vertical format

Use the BarWidget-provided `vertical` property instead of repeating the
`bar && bar.vertical` ternary, and clean up stray tab/trailing whitespace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 15:31:39 -07:00
ssuptandGitHub 3481b5614b Fix pointer navigation in Quattro launcher menus (#6343)
Accumulate sub-threshold movement so slow motion with a flat acceleration profile updates selection. Sample pointer state on row entry and carry pointer intent through subordinate menus while preserving predictable keyboard selection.
2026-07-22 15:27:56 -07:00
David Heinemeier HanssonandClaude Fable 5 6ff61289fa Show Remove... as the title of all submenus under Remove
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 12:04:25 -07:00
David Heinemeier HanssonandClaude Fable 5 2bf4f4a9fb Hide menu submenus when none of their children are visible
Static submenus and links now inherit visibility from their descendants,
so hardware sections like Touchpad Haptics disappear on machines without
the hardware instead of opening empty. Laptop Display and Mirror Display
are guarded by omarchy-hw-laptop.

The guard evaluator brace-wraps every when/checked condition before
silencing it, so conditions in the jsonc no longer need their own
>/dev/null plumbing, and parents no longer need to repeat their
children's guards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 16:30:36 -07:00
David Heinemeier Hansson d3cce6e68f Make menu left arrow navigate back 2026-07-21 15:45:08 -07:00
David Heinemeier HanssonandClaude Fable 5 289e6d12fc Simplify service lookups with optional chaining
The bar-null-shell-null-typeof-function ternary guarded against our own
shell missing a method it always defines. bar?.shell?.firstPartyServiceFor()
handles the only real case, delayed bar injection, in one line. The
typeof checks that probe genuinely third-party plugin objects stay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 15:26:30 -07:00
David Heinemeier HanssonandClaude Fable 5 ba91bad3a4 Move Night Light onto a first-party nightlight service
Same treatment as Stay Awake: the indicator polled the toggle CLI over
a Process with a timer to paper over the race after clicking, and the
CLI ended by asking the shell to refresh every indicator over IPC. A new
omarchy.nightlight service owns hyprsunset instead - it probes the
temperature on startup, applies changes itself for in-shell toggles, and
answers on the nightlight IPC target. The indicator becomes a plain
binding. The CLI still drives hyprctl directly so keybindings, the menu,
and ssh work without the shell, but now just nudges the service to
re-probe since hyprsunset has no state file to watch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 15:26:20 -07:00