Handle volume, media and notification keys in the shell (#13424)

* Step the volume in the shell instead of a script per keypress

Each volume key ran omarchy-audio-output-volume: several pactl calls in
bash, then omarchy-osd delivering the OSD over a qs IPC client, ~165ms
from keypress to OSD, on keys that repeat while held.

The volume up, down and mute keys now dispatch global shortcuts that the
media service handles over PipeWire, stepping, clamping, unmuting and
debouncing by the script's rules and showing the same OSD. It acts only
when the default sink is an ALSA sink, which is its own physical sink.
Any other default, a DSP chain or EasyEffects above all, falls back to
the script, which resolves the physical sink from the live routing on
every press. The precise +1/-1 keys still run the script.

Keypress to OSD drops from ~168ms to ~20ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Run media and notification keys in the shell without an IPC client

The media keys and the notification dismiss, invoke and history keys ran
omarchy-shell, starting a qs client for one argument-free IPC call.

A new ipc shortcut kind names such a call as target.method. The shell
hands it to the service that owns the target, which runs its own
IpcHandler function, so the key behaves exactly as the omarchy-shell
call did. A call missing from the list binds through omarchy-shell.

Dismissing a notification with SUPER+comma clears the popup in ~11ms
instead of ~40ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier HanssonandClaude Opus 5.5 authored and GitHub committed 2026-09-27 19:02:59 +02:00
1 parent bf44355a98
commit 804749970a
10 files changed
+200 -28

No files matched your search

+9 -9
View File
@@ -1,7 +1,7 @@
-- Volume, brightness, keyboard backlight, and touchpad controls.
o.bind("XF86AudioRaiseVolume", "Volume up", "omarchy-audio-output-volume raise", { locked = true, repeating = true })
o.bind("XF86AudioLowerVolume", "Volume down", "omarchy-audio-output-volume lower", { locked = true, repeating = true })
o.bind("XF86AudioMute", "Mute", "omarchy-audio-output-volume mute-toggle", { locked = true })
o.bind("XF86AudioRaiseVolume", "Volume up", { audio = "raise" }, { locked = true, repeating = true })
o.bind("XF86AudioLowerVolume", "Volume down", { audio = "lower" }, { locked = true, repeating = true })
o.bind("XF86AudioMute", "Mute", { audio = "mute-toggle" }, { locked = true })
o.bind("XF86AudioMicMute", "Mute microphone", "omarchy-audio-input-mute", { locked = true })
o.bind("XF86MonBrightnessUp", "Brightness up", "omarchy-brightness-display +5%", { locked = true, repeating = true })
o.bind("XF86MonBrightnessDown", "Brightness down", "omarchy-brightness-display 5%-", { locked = true, repeating = true })
@@ -21,12 +21,12 @@ o.bind("ALT + XF86MonBrightnessUp", "Brightness up precise", "omarchy-brightness
o.bind("ALT + XF86MonBrightnessDown", "Brightness down precise", "omarchy-brightness-display 1%-", { locked = true, repeating = true })
-- Media controls.
o.bind("XF86AudioNext", "Next track", "omarchy-shell media next", { locked = true })
o.bind("ALT + XF86AudioPlay", "Next track", "omarchy-shell media next", { locked = true })
o.bind("XF86AudioPause", "Pause", "omarchy-shell media playPause", { locked = true })
o.bind("XF86AudioPlay", "Play", "omarchy-shell media playPause", { locked = true })
o.bind("XF86AudioPrev", "Previous track", "omarchy-shell media previous", { locked = true })
o.bind("ALT + SHIFT + XF86AudioPlay", "Previous track", "omarchy-shell media previous", { locked = true })
o.bind("XF86AudioNext", "Next track", { ipc = "media.next" }, { locked = true })
o.bind("ALT + XF86AudioPlay", "Next track", { ipc = "media.next" }, { locked = true })
o.bind("XF86AudioPause", "Pause", { ipc = "media.playPause" }, { locked = true })
o.bind("XF86AudioPlay", "Play", { ipc = "media.playPause" }, { locked = true })
o.bind("XF86AudioPrev", "Previous track", { ipc = "media.previous" }, { locked = true })
o.bind("ALT + SHIFT + XF86AudioPlay", "Previous track", { ipc = "media.previous" }, { locked = true })
o.bind("XF86Eject", "Eject media", "eject", { locked = true })
o.bind("SHIFT + XF86AudioMute", "Switch audio output", "omarchy-audio-output-switch", { locked = true })
+4 -4
View File
@@ -22,11 +22,11 @@ o.bind("SUPER + CTRL + BACKSPACE", "Toggle single-window square aspect", "omarch
o.bind_toggle("SUPER + CTRL + ALT + F", "Toggle full screen desktop", "fullscreen-desktop")
-- xkbcommon names the comma keysym "comma"; the upper-case "COMMA" does not match.
o.bind("SUPER + comma", "Dismiss last notification", "omarchy-shell notifications dismissOne")
o.bind("SUPER + SHIFT + comma", "Dismiss all notifications", "omarchy-shell notifications dismissAll")
o.bind("SUPER + comma", "Dismiss last notification", { ipc = "notifications.dismissOne" })
o.bind("SUPER + SHIFT + comma", "Dismiss all notifications", { ipc = "notifications.dismissAll" })
o.bind_toggle("SUPER + CTRL + comma", "Toggle silencing notifications", "notification-silencing")
o.bind("SUPER + ALT + comma", "Invoke last notification", "omarchy-shell notifications invokeLast")
o.bind("SUPER + SHIFT + ALT + comma", "Open notification history", "omarchy-shell notifications showHistory")
o.bind("SUPER + ALT + comma", "Invoke last notification", { ipc = "notifications.invokeLast" })
o.bind("SUPER + SHIFT + ALT + comma", "Open notification history", { ipc = "notifications.showHistory" })
o.bind_toggle("SUPER + CTRL + I", "Toggle locking on idle", "idle")
o.bind_toggle("SUPER + CTRL + N", "Toggle nightlight", "nightlight")
+5
View File
@@ -98,6 +98,11 @@ local function command_from(value, description)
return shell_dispatcher("menu", value.menu, "omarchy-menu toggle " .. shell_quote(value.menu))
elseif value.panel then
return shell_dispatcher("panel", value.panel, "omarchy-shell shell toggle " .. shell_quote(value.panel))
elseif value.audio then
return shell_dispatcher("audio", value.audio, "omarchy-audio-output-volume " .. shell_quote(value.audio))
elseif value.ipc then
local target, method = value.ipc:match("^([^.]+)%.(.+)$")
return shell_dispatcher("ipc", value.ipc, "omarchy-shell " .. shell_quote(target) .. " " .. shell_quote(method))
elseif value.focus and value.launch then
return o.launch_sole(value.focus, value.launch)
elseif value.launch then
+17 -5
View File
@@ -1,9 +1,11 @@
# Hyprland global shortcuts the Omarchy shell registers, one per line: a menu
# route to toggle, or a panel plugin to toggle. A binding written as
# o.bind(keys, description, { menu = "theme" }) or { panel = "omarchy.emojis" }
# dispatches the matching shortcut straight to the shell, with no IPC client to
# spawn per keypress. A route or panel missing here still binds, through the
# omarchy-menu or omarchy-shell command instead.
# route to toggle, a panel plugin to toggle, a volume key, or an argument-free
# IPC call as target.method. A binding written as o.bind(keys, description,
# { menu = "theme" }), { panel = "omarchy.emojis" }, { audio = "raise" } or
# { ipc = "media.next" } dispatches the matching shortcut straight to the shell,
# with no process to spawn per keypress. Anything missing here still binds,
# through the omarchy-menu, omarchy-shell or omarchy-audio-output-volume
# command instead.
menu root
menu apps
menu capture
@@ -22,3 +24,13 @@ panel omarchy.monitor
panel omarchy.clock
panel omarchy.network
panel omarchy.power
audio raise
audio lower
audio mute-toggle
ipc media.playPause
ipc media.next
ipc media.previous
ipc notifications.dismissOne
ipc notifications.dismissAll
ipc notifications.invokeLast
ipc notifications.showHistory