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.
This commit is contained in:
Ryan Hughes
2026-05-14 14:21:07 -04:00
parent a7f8f2495f
commit c101591284
47 changed files with 1780 additions and 413 deletions
@@ -1,5 +1,6 @@
import QtQuick
import Quickshell
import Quickshell.Bluetooth
import Quickshell.Io
import Quickshell.Services.Pipewire
import Quickshell.Services.UPower
@@ -25,15 +26,22 @@ Item {
return value === undefined || value === null ? fallback : value
}
readonly property var sink: Pipewire.defaultAudioSink
readonly property real currentVolume: sink && sink.audio && isFinite(sink.audio.volume) ? sink.audio.volume : 0
readonly property bool sinkMuted: sink && sink.audio ? sink.audio.muted : false
PwObjectTracker { objects: root.sink ? [root.sink] : [] }
// Volume controls live in the audioPanel bar widget, so the quick-settings
// popup just hosts brightness + toggles. Bluetooth adapter status is
// exposed via Quickshell.Bluetooth.
readonly property var btAdapter: Bluetooth.defaultAdapter
readonly property bool btEnabled: btAdapter ? btAdapter.enabled : false
property int currentBrightness: -1
property int pendingBrightness: -1
property bool dndActive: false
// DND state is bound straight to the notifications service so toggling
// from the quick-settings tile or the bar widget reflects instantly.
readonly property var hostShell: bar && bar.shell ? bar.shell : null
readonly property var notificationService: hostShell && typeof hostShell.firstPartyServiceFor === "function"
? hostShell.firstPartyServiceFor("omarchy.notifications")
: null
readonly property bool dndActive: notificationService ? notificationService.doNotDisturb : false
property bool idleInhibited: false
property bool nightLightActive: false
property bool nightLightAvailable: false
@@ -42,16 +50,6 @@ Item {
readonly property bool powerProfileAvailable: PowerProfiles.hasPerformanceProfile || PowerProfiles.profile === PowerProfile.PowerSaver || PowerProfiles.profile === PowerProfile.Balanced
readonly property int currentProfile: PowerProfiles.profile
function setVolume(value) {
if (!sink || !sink.audio) return
sink.audio.volume = Math.max(0, Math.min(1, value))
}
function toggleMute() {
if (!sink || !sink.audio) return
sink.audio.muted = !sink.audio.muted
}
function setBrightness(percent) {
var clamped = Math.max(1, Math.min(100, Math.round(percent)))
currentBrightness = clamped
@@ -61,7 +59,6 @@ Item {
function refresh() {
if (!brightnessProc.running) brightnessProc.running = true
if (!dndProc.running) dndProc.running = true
if (!idleProc.running) idleProc.running = true
if (!nightLightProc.running) nightLightProc.running = true
if (!themeProc.running) themeProc.running = true
@@ -98,15 +95,6 @@ Item {
}
}
Process {
id: dndProc
command: ["bash", "-lc", "if command -v makoctl >/dev/null && makoctl mode 2>/dev/null | grep -q '^do-not-disturb$'; then echo on; else echo off; fi"]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: root.dndActive = String(text || "").trim() === "on"
}
}
Process {
id: idleProc
command: ["bash", "-lc", "if pgrep -x hypridle >/dev/null 2>&1; then echo running; else echo inhibited; fi"]
@@ -190,49 +178,12 @@ Item {
Column {
width: parent.width
spacing: 10
visible: root.sink !== null
visible: root.currentBrightness >= 0
Row {
width: parent.width
spacing: 8
Common.PillButton {
iconText: root.sinkMuted ? "󰝟" : "󰕾"
foreground: root.bar.foreground
horizontalPadding: 8
verticalPadding: 6
iconSize: 16
onClicked: root.toggleMute()
}
Common.Slider {
bar: root.bar
width: parent.width - 90
value: root.currentVolume
minimum: 0
maximum: 1
step: 0.05
anchors.verticalCenter: parent.verticalCenter
onMoved: function(v) { root.setVolume(v) }
onReleased: function(v) { root.setVolume(v) }
}
Text {
text: Math.round(root.currentVolume * 100) + "%"
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: 11
anchors.verticalCenter: parent.verticalCenter
width: 32
horizontalAlignment: Text.AlignRight
}
}
Row {
width: parent.width
spacing: 8
visible: root.currentBrightness >= 0
Common.PillButton {
iconText: "󰃠"
foreground: root.bar.foreground
@@ -284,7 +235,10 @@ Item {
title: "Do Not Disturb"
subtitle: root.dndActive ? "On" : "Off"
active: root.dndActive
onClicked: { root.run("omarchy-toggle-notification-silencing"); dndProc.running = true }
onClicked: {
if (!root.notificationService) return
root.notificationService.setDoNotDisturb(!root.notificationService.doNotDisturb)
}
}
Tile {
@@ -308,10 +262,15 @@ Item {
Tile {
width: (tileGrid.width - tileGrid.columnSpacing) / 2
glyph: "󰔎"
title: "Theme"
subtitle: root.themeName || "—"
onClicked: { root.run("omarchy-menu themes"); root.popupOpen = false }
glyph: root.btEnabled ? "󰂯" : "󰂲"
title: "Bluetooth"
subtitle: !root.btAdapter ? "—" : (root.btEnabled ? "On" : "Off")
active: root.btEnabled
tileEnabled: root.btAdapter !== null
onClicked: {
if (!root.btAdapter) return
root.btAdapter.enabled = !root.btAdapter.enabled
}
}
}