28 Commits
Author SHA1 Message Date
Ryan Hughes bf2013e6f3 Make --exec take the command as rest-of-line words
Replace --exec-arg with an ergonomic --exec that consumes the rest of the line
as the click command. The caller's shell tokenizes the words into discrete
arguments before the tool sees them, and the shell runs them as positional
parameters (never a re-parsed string), so safety is identical to the argv form
while the call sites read naturally: `--exec omarchy toggle something`.

Crucially the tool never splits a string itself — a single quoted whole-command
argument is rejected and points at the unquoted form, because whitespace-
splitting a string hands argument boundaries to whoever controls its content
(the injection we are avoiding). --exec must come last; migrate every caller.
2026-08-23 14:26:25 -04:00
Ryan Hughes 07443f3970 Run notification click actions as argv, not shell strings
The click action of a notification was a free-form shell string run through
`bash -lc`, safe only when every sender shell-quoted every interpolated value
perfectly. One slip is RCE: a hostile yt-dlp video title forged an output
record and injected an mpv option into the click command (mehmetince.net RCE,
partially addressed by #7847).

Add a parameterized transport: omarchy-notification-send gains --exec-arg
(repeatable), encoding a JSON argv into the omarchy-exec-argv hint. The shell
runs it with Quickshell.execDetached(argv) and no shell, so data an attacker
controls is only ever one argument and can never be reparsed as a command. The
shell fails closed on a malformed argv hint.

The legacy free-form --exec string is retained but honored only from Omarchy's
own omarchy-action toasts, and deprecated. Migrate all in-repo callers
(screenshot, screen recording, taildrop receive, migrate-notify, crash-watch,
yt-dlp host) to --exec-arg. Update docs and tests.
2026-08-23 12:00:03 -04:00
8b9e43d3e5 Only offer video capture devices as webcams (#6732)
* Only offer video capture devices as webcams

V4L2 exposes raw processing and output-only nodes beside usable cameras. The webcam picker and automatic recorder selection treated the first /dev/video node as a camera, so IPU6 laptops opened a black overlay instead of their loopback capture device.\n\nShare one device lister across detection, selection, and recording, and keep only groups whose first video node advertises Video Capture in Device Caps. Cover raw IPU nodes, ordinary capture devices, and capture-less systems.

* Fall through to a later capture-capable node in a webcam group

A group whose first video node is not capture-capable vanished entirely,
even when a later node in the same group could capture. Probe each node
until one qualifies, still emitting at most one device per group. Also
exit zero explicitly: a trailing filtered device used to leak the failed
capability check as the script's exit status.

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>
2026-08-13 12:23:48 +02:00
5a58f79876 Keep clicking a notification working after a shell restart (#6636)
* Keep clicking a notification working after a shell restart

Notification actions lived only in the sending process: `-a` appended
`-A default=default`, so notify-send blocked on a D-Bus ActionInvoked signal and
the caller ran the command when it arrived. Nothing about that reached disk, so a
restored popup had no action to run and its sender stayed blocked forever.

Replace `-a` with `--exec <command>`, carried as an `omarchy-exec` hint into the
snapshot's `exec` role. It travels through the popup files and history, and the
shell runs it on click, so restored toasts behave exactly like live ones and the
sender exits immediately.

That drops the scaffolding whose only job was keeping a blocked sender alive: the
first-run invitations lose their `--show` re-entry and two transient units each,
omarchy-migrate-notify loses its transient service, and the screenshot,
recording, download, and taildrop toasts lose their wrapper subshells.

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

* Keep a failed toast from failing the work it announces

Moving these sends out of their backgrounded subshells put a fallible command
on the foreground path, where the `&` used to swallow its exit status. A
notification outage — including the shell restart this branch targets — now
propagates:

- taildrop's receiver dies under `set -e` mid-delivery
- omarchy-capture-screenshot reports failure for a screenshot it already saved
- a completed download exits before scheduling its thumbnail cleanup, leaking
  the mktemp file

Announcing is best-effort in all three: the work is already done by the time
the toast goes out.

Also drop the first-run sleep that spaced out the welcome and Wi-Fi toasts.
It compensated for the background notify-send processes this branch removes;
each send now returns only once the server has taken the toast, so sending in
order is enough to stack them newest-on-top.

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

* Stop tying the preview cleanup to the toast's expiry

The shell loads a notification thumbnail into memory when the toast appears and
never re-reads the file, so the preview only has to outlive that load. Deriving
the cleanup delay from the expiry was false precision, and it turned -t into a
variable for no reason: -t is already the helper's expiry setting.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 12:30:47 +02:00
ada53b090e Anchor the webcam overlay to the recorded region, not the monitor (#6384)
* Anchor the webcam overlay to the recorded region, not the monitor

Recording a single window on an ultrawide put the camera outside the frame.
The overlay is placed twice, and neither placement knows what is being
captured: the window rules in default/hypr/apps/webcam-overlay.lua position it
from monitor_w/monitor_h, and omarchy-capture-webcam-resize recomputes the same
corner from hyprctl monitors. Both resolve to the monitor's bottom-right, so a
window anywhere but the far right of the display records without the camera in
it, and dragging it into frame by hand is undone by the next resize.

The geometry is already known. select_capture_target resolves the picked window
to a region rectangle before start_webcam_overlay runs, and a bare click in
smart mode snaps that rectangle to the window's own bounds. gpu-screen-recorder
takes the region in the compositor's logical coordinate space, which is the
same space window moves take, so the value needs no conversion to be reused as
an anchor.

Publish it for the resize helper, which becomes the single place that positions
the overlay. It anchors to the region when one is recorded and falls back to
the monitor otherwise, so full-monitor captures, the portal backend, and manual
resizes outside a recording all keep their current behaviour. A malformed or
empty file falls back the same way.

Presets scale from the anchor's height rather than the monitor's, so the camera
keeps its proportion of the frame instead of covering a small capture outright.
A tall, narrow region cannot fit a preset derived from its height, so widths are
capped to the space available and heights follow at the same 8:9 aspect.

Placement happens inside start_webcam_overlay rather than after it returns.
Correcting the position once that function had returned left the move adjacent
to gpu-screen-recorder starting, and the camera was recorded sliding the last
stretch into its corner over the opening frames. The overlay is waited for
explicitly instead, positioned, and only then does capture start.

Waiting for the map is what the blind second was partly guessing at, so the
remainder is trimmed to hold the delay before capture where it was. Starting
later is not free: it eats the opening words of whatever is being narrated.

* Keep the webcam size ladder usable in a narrow region

Capping each preset's width to the region separately collapsed small,
medium and large onto the same width, so Super + Alt + [ and ] had
nothing to step between, and integer division left small taller than
medium. Cap the height the presets scale from instead, which shrinks
the ladder as a whole and keeps the three sizes ordered and distinct.

Cover the anchoring in the test suite: a region the camera follows, the
fallback for an unreadable one, and the narrow-region ladder. Point
XDG_RUNTIME_DIR at the test's own directory while doing so — the resize
helper now reads a region file from there, and the existing geometry
assertions would pick up a real one from a live recording.

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>
2026-07-26 15:27:06 -07:00
David Heinemeier HanssonandClaude Fable 5 f619c397f2 Make status indicators event-driven instead of polling
The indicators widget broadcast a refresh every 2s, fanning out to four
status subprocesses (nightlight, idle, reminder, screen-recording), and
NightLight/StayAwake each ran an additional 5s poll. On an idle desktop
this was the dominant source of process churn (~33 of ~53 forks/sec in a
VM). The state-changing commands already push `omarchy.indicators
refresh` over IPC, so the polling was redundant.

Drop the 2s broadcast and the two 5s timers; indicators now refresh at
startup and on the IPC push. Also fix three callers that pushed to the
wrong target `Indicators` instead of `omarchy.indicators` (screen
recording, notification silencing, and the omarchy-shell help example) —
those pushes silently failed and only appeared to work because the poll
masked them.

Idle fork rate drops ~53/s to ~20/s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 22:46:27 -07:00
Othon FerreiraandDavid Heinemeier Hansson ca0d78e718 Fix documentation example
Fixing to screenrecording instead screenrecord that works only when
using the alias
2026-07-19 20:03:52 -07:00
David Heinemeier Hansson 8862fb22bd Use Omarchy command helpers consistently 2026-07-18 10:08:43 -07:00
David Heinemeier Hansson ca0cf8bff9 Fix webcam resize controls and placement 2026-07-17 18:28:44 -07:00
David Heinemeier Hansson 1174f2a70b Add live webcam overlay sizing 2026-07-17 18:28:44 -07:00
David Heinemeier HanssonandClaude Fable 5 b72aa8f65e Rename region picker to omarchy-capture-region
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 09:30:41 -07:00
David Heinemeier HanssonandClaude Fable 5 362d97c151 Share one region picker between screenshot and recording
Both capture scripts carried the same pipeline: workspace rectangles
from hyprctl, a hyprpicker freeze, slurp, and the bare-click snap loop.
The copies had already diverged once (rotated-display handling), so the
pipeline now lives in one hidden helper, omarchy-capture-region-pick,
which prints the picked geometry in slurp format.

The two callers differ only in what they layer on top. The screenshot
needs the freeze to outlive the pick so grim captures frozen content:
--keep-freeze leaves hyprpicker running and prints its PID first, with
the caller owning the kill. The recording prefers native full-monitor
capture: --match-monitor prints monitor:NAME when the geometry exactly
matches a display.

One deliberate fix along the way: the screenshot snap regex rejected
negative coordinates, so bare clicks on monitors positioned left of or
above the origin never snapped. The helper uses the recording variant,
which handles them.

Verified with an 11-case shim harness (snap precedence, rotated-monitor
match, freeze ownership, cancellation, negative coordinates) and the
live end-to-end screenshot sanity test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 09:26:46 -07:00
David Heinemeier HanssonandClaude Fable 5 54539b4a83 Handle rotated monitors in the recording picker
The screenshot picker swaps monitor dimensions for transform 1/3
displays; the recording copy had drifted and did not, so portrait
monitors got landscape rectangles in the picker. The full-monitor
match had the same blind spot, silently downgrading rotated-monitor
selections to region capture instead of native -w capture.

Both now share the screenshot script's format_geo jq helper, which
also lets the monitor match compare one geometry string instead of
four fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:14:33 -07:00
David Heinemeier Hansson af5e918dd5 Improve actionable notification wrapper 2026-05-25 23:51:48 +02:00
David Heinemeier Hansson f9a2685aab Use capitalized camel for all classes 2026-05-20 22:00:10 +02:00
David Heinemeier Hansson cf4729ca2b Extract indicators bar widget 2026-05-20 18:32:00 +02:00
David Heinemeier Hansson c87bfb8da8 Introduce omarchy-shell -q for fire and forget 2026-05-20 14:24:19 +02:00
David Heinemeier Hansson b0f5d941ba Switch to running quickshell as a systemd service that is auto-restarted if it crashes 2026-05-18 13:58:07 +02:00
David Heinemeier Hansson 83e7f41308 Add --fullscreen to make it easier for agents to record 2026-05-16 16:47:22 +02:00
Ryan Hughes 2ca15dc767 Remove waybar; relocate bar helper scripts into the shell tree
omarchy-shell's bar plugin replaces waybar on every default path, so the
waybar package, configs, indicator scripts, and toggle/restart/refresh
binaries all go away in this commit.

The four small helper scripts that Bar.qml shells out to (weather.sh and
indicators/*.sh) move with the shell rather than disappear \u2014 they live
at default/quickshell/omarchy-shell/scripts/ now, alongside the rest of
the shell code.

Companion script changes:
  - omarchy-shell-ipc gains --if-running so fire-and-forget refresh
    callers (toggle-idle, toggle-notification-silencing,
    update-available-reset, capture-screenrecording) don't accidentally
    spawn the shell when it isn't already up.
  - omarchy-theme-set, omarchy-tz-select, omarchy-voxtype-{install,model},
    omarchy-toggle-nightlight, omarchy-voxtype-config drop their waybar
    branches and comments.
  - omarchy-voxtype-status's summary stops claiming it's a Waybar helper.
  - omarchy-base.packages drops waybar; install/packaging/fonts.sh's
    comment points at the new bar consumer.
  - SKILL.md / AGENTS.md / omarchy-menu.jsonc lose their waybar entries.
  - test/omarchy-cli-test.sh substitutes a remaining toggle command.
2026-05-14 16:33:20 -04:00
Ryan Hughes c101591284 Replace mako with quickshell-backed notification daemon
Adds first-party omarchy.notifications service plugin that hosts a
freedesktop notification server and renders popups + a history popup
inside the shell. Uninstalls mako and retargets every helper, keybind,
indicator, and migration entry to the new daemon.

Plugin (default/quickshell/omarchy-shell/plugins/notifications/):
- Service.qml: NotificationServer, popupModel + pendingModel + pastModel
  (two-tier history, see below), DND via PersistentProperties +
  cache-file backstop, image cache for /tmp screenshots, IpcHandler with
  toggleDnd/setDnd/isDnd/showHistory/clear/clearPending/markAllSeen/
  dismissAll/dismissOne/invokeLast/dismiss, per-theme override file
  ~/.config/omarchy/current/theme/notifications.json honoring
  borderColor/backgroundColor/textColor/countdownColor.
- components/NotificationCard.qml: theme-driven card (Color.foreground/
  background/border tokens from Commons/Color.qml), 32x32 icon slot,
  Nerd Font glyph fallback via omarchy-glyph hint, hero image strip
  for screenshot/image-path notifications, hover-pause progress bar,
  uses bar.fontFamily so all surfaces share one font.

Filtering and DND:
- transient hint and CLI-style senders (app_name in notify-send /
  omarchy-action) bypass history but still pop.
- DND only allows omarchy-action toasts and notify-send -u critical
  through; real-app urgency=critical (Discord, Slack, Vesktop) is
  silenced and lands in pending instead.
- Pending vs past split surfaced via tabs in the bar widget popup;
  past tab is auto-pruned at the 15-minute mark.
- Click-to-jump: notifications without a libnotify default action focus
  the matching Hyprland window via class lookup.

Shell host:
- shell.qml: generic first-party service loader (mirrors the existing
  noctalia-compat path) and an alias for the bar so plugins can read
  barSize / barHidden / position for anchoring.
- Commons/Color.qml: parses the theme's hyprland.conf for
  $activeBorderColor so notifications match Hyprland window borders;
  picks the explicit accent= key over the color4= alias.

Bar widget rebase (plugins/bar/widgets/notificationCenter.qml):
- Drops the chunk-1 stub server, binds count/dnd state to the service,
  hosts the history popup via PopupCard so it drops down from the
  notification glyph the same way Quick Settings does.
- Pending/Past tabs, dismiss-individual close X, mark-all-as-seen and
  clear-recent action buttons, theme-driven palette.

Quick Settings rework (plugins/bar/widgets/controlCenter.qml):
- DND tile binds directly to service.doNotDisturb for instant feedback.
- Drops the volume slider (already in audioPanel) and the no-op Theme
  tile; adds a Bluetooth toggle bound to Quickshell.Bluetooth.
- Bigger 44x44 wallet was scaled back to 32x32 for tighter rows.

Notification scripts (bin/omarchy-*):
- omarchy-notification-send: passes glyph as a custom hint instead of
  prepending to the summary; adds -a omarchy-action and -u urgency
  automatically; supports -e/--transient passthrough.
- User-action toasts in the capture / toggle / hyprland / default-*
  scripts and bindings/utilities.lua now tag themselves
  -a omarchy-action so DND treats them as intent-based bypass.
- omarchy-toggle-notification-silencing, omarchy-notification-dismiss,
  default/waybar/indicators/notification-silencing.sh, and the Hyprland
  comma-keybinds all route through omarchy-shell-ipc notifications.
- omarchy-capture-screenshot / -screenrecording set the image-path
  hint properly so the hero-image rendering kicks in.

Mako removal (migrations/1778743515.sh):
- pkill -x mako, systemctl --user stop mako.service, pacman -Rns mako
  (uninstalling deletes /usr/lib/systemd/user/mako.service so D-Bus
  activation can't respawn it). Removes ~/.config/mako/ and the legacy
  toggle file. Restarts quickshell so it claims the bus name.
- Drops mako from install/omarchy-base.packages, autostart.lua,
  install/config/theme.sh + toggles.sh, default/themed/mako.ini.tpl,
  default/mako/, the omarchy-menu Mako restart row, bin/omarchy
  GROUP_DESCRIPTIONS, the settings panel catalogue, and the
  default/omarchy-skill paths table.
- Removed scripts: bin/omarchy-restart-mako,
  bin/omarchy-style-corners-mako.
- bin/omarchy-style-corners summary updated; corner radius for the
  notification card reads ~/.local/state/omarchy/toggles/quickshell-menu.json
  alongside the rest of the shell.
2026-05-14 14:21:07 -04:00
Ryan Hughes c72a8756b3 Add omarchy-shell with a plugin registry 2026-05-14 02:21:48 -04:00
Ryan Hughes 30dc9fddde Add Quickshell bar 2026-05-14 02:21:47 -04:00
David Heinemeier Hansson 10c42afcf2 Echo result so agents can use them and keep working with them 2026-05-09 11:40:31 +02:00
Ryan Hughes 6a5e53784f Bypass portal by default for screenrecording due to portal bug 2026-05-03 14:42:34 -04:00
David Heinemeier Hansson 9ee810ef87 Prevent mic open pop on the recording 2026-05-03 14:31:18 +02:00
d2a4cc0c4d Add omarchy CLI (#5477)
* Add omarchy CLI

* Remove outdated or internal

* Add bash completions for command

* Add omarchy command documentation

* Add missing docs

* Correct to what's now right

* Fix tests

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
2026-05-01 17:40:22 +02:00
David Heinemeier Hansson ec69817e74 Fix spelling 2026-04-30 22:23:43 +02:00