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.
Omarchy shell
omarchy-shell is a single long-running Quickshell
instance that hosts the Omarchy desktop. Hyprland autostarts one shell per
session; everything else — the bar, the bar settings UI, the background
switcher, future panels and overlays — runs inside the shell as a
plugin.
Hosting everything inside one shell means:
- shared services and singletons live once, not once per process
- summoning a panel is an IPC call into a process that is already running,
not a fresh
quickshell -p ...cold start - third-party plugins can be loaded from disk without changing any source code in Omarchy itself
The runtime layout in this branch:
default/quickshell/omarchy-shell/
shell.qml entry point (ShellRoot)
shell-defaults.json canonical out-of-the-box config
services/
PluginRegistry.qml discovers, validates plugins, looks up enabled state in shell.json
BarWidgetRegistry.qml unified registry for bar widgets (1p + 3p)
ui/
settings/
DynamicSettingsForm.qml renders plugin-declared schemas
plugins/
bar/ first-party plugins (see plugins/README.md)
settings/
image-picker/
menu/
The plugin discovery path is documented in plugins/README.md.
Plugin manifest
Every plugin ships a manifest.json describing what it is and how the
shell should load it. Minimal example:
{
"schemaVersion": 1,
"id": "my.org.cool-clock",
"name": "Cool clock",
"version": "1.0.0",
"author": "You",
"description": "A clock that does cool things",
"kinds": ["bar-widget"],
"activation": "on-demand",
"entryPoints": { "barWidget": "Widget.qml" },
"barWidget": {
"displayName": "Cool clock",
"category": "Time",
"allowMultiple": false,
"defaults": { "format": "HH:mm" },
"schema": [
{ "key": "format", "type": "string", "label": "Format" }
]
}
}
Supported kinds:
| Kind | What it is |
|---|---|
bar-widget |
A component that the bar can drop into a section |
panel |
A persistent or summoned floating window (e.g. bar settings) |
overlay |
A fullscreen overlay (e.g. background switcher) |
menu |
A summoned menu surface |
service |
A headless singleton, no UI |
activation is either persistent (loaded on startup, never unloaded) or
on-demand (loaded by shell summon <id> and unloaded by shell hide).
Plugins that need to outlive a single summon can set keepLoaded: true
(e.g. the image picker keeps its overlay window mounted between
summons).
The full schema lives in services/PluginRegistry.qml.
Installing a third-party plugin
- Drop the plugin into
~/.config/omarchy/plugins/<plugin-id>/. The directory must contain amanifest.jsonplus the QML files referenced from itsentryPoints. omarchy-shell-ipc shell rescanPlugins— or open the Plugin Manager tab inomarchy launch settingsand click Rescan.- Enable the plugin (Plugin Manager Enable toggle, or
omarchy-shell-ipc shell setPluginEnabled <id> true). - If it's a
bar-widget, add it to a layout section from the bar editor.
First-party plugins under default/quickshell/omarchy-shell/plugins/
are discovered the same way and cannot be disabled.
IPC contract
The shell exposes a single shell IPC target plus whatever extra targets
individual plugins register (e.g. the bar's bar target for refresh
hooks, the image picker's image-selector target). omarchy-menu uses the
shell target to summon the first-party omarchy.menu plugin instead of
running a separate Quickshell instance.
| Method | Returns | Effect |
|---|---|---|
ping |
ok |
health check |
summon <id> <payloadJson> |
ok / unknown |
load + open a panel/overlay plugin |
hide <id> |
— | close a previously-summoned plugin |
toggle <id> <payloadJson> |
— | summon if closed, hide if open |
rescanPlugins |
— | re-walk plugin dirs and pick up new/changed manifests |
setPluginEnabled <id> <enabled> |
— | flip the persisted enabled bit (see note) |
listPlugins |
JSON | every discovered plugin (id, name, kinds, enabled) |
Direct invocation:
quickshell ipc -p $OMARCHY_PATH/default/quickshell/omarchy-shell call shell ping
Hyprland starts the shell through omarchy-restart-quickshell on boot.
Use omarchy restart quickshell to reload the long-running shell process.
A convenience wrapper, omarchy-shell-ipc,
starts the shell if it is not already running, then forwards a call. It
is the canonical way for other Omarchy CLIs to talk to the shell.
omarchy-shell-ipc shell ping
omarchy-shell-ipc shell summon omarchy.settings "{}"
omarchy-shell-ipc shell listPlugins
omarchy-shell-ipc shell rescanPlugins
Note on setPluginEnabled: the enabled argument is a string. Only the
literal "true" enables the plugin; every other value (including "True",
"1", "yes", or omitted) disables it. This keeps the IPC surface
type-stable across QML's string-only IPC arguments.
Persisted state
There is one user config file. Everything that distinguishes your customization from the shipped defaults lives in it.
| Path | Owner | Purpose |
|---|---|---|
~/.config/omarchy/shell.json |
the shell | full layout + per-entry settings + enabled plugin list |
~/.config/omarchy/plugins/<id>/ |
user | drop-in third-party plugin source files |
The shell-defaults.json bundled with the shell describes the
fresh-install state. When the user has no shell.json, the shell uses
the defaults verbatim. Once the user customizes anything, shell.json
becomes the authoritative file — we do not deep-merge defaults back
in. Pressing Reset to defaults in omarchy launch settings
rewrites shell.json from the current shell-defaults.json.
shell.json shape
{
"version": 1,
"bar": {
"position": "top",
"centerAnchor": "calendar",
"fontFamily": "JetBrainsMono Nerd Font",
"layout": {
"left": [ { "id": "omarchy" }, { "id": "workspaces" } ],
"center": [ { "id": "calendar", "format": "HH:mm" } ],
"right": [
{ "id": "audioPanel" },
{ "id": "controlCenter" }
]
}
},
"plugins": [
{ "id": "omarchy.settings" },
{ "id": "omarchy.image-picker" }
]
}
Storage rules
- Every plugin instance is one entry. Either in
bar.layout.<section>for bar widgets, or inplugins[]for panels, overlays, services, menus, and anything else non-bar. - Settings are inline on the entry. No
config:sub-object, no separate per-plugin settings file, no merge layers. The fields on each entry are the values the plugin sees. - Enabled ⇔ present. A plugin is enabled iff its id appears somewhere in shell.json. To disable, remove it. (The bar settings UI does both.)
- Multiple instances are allowed when a manifest sets
allowMultiple: true. Each instance is independent — e.g. two clocks in different timezones are just two{"id":"calendar", "timezone": ...}entries with their own values. version: 1is required at the top level. The shell will fall back to defaults rather than load an unknown version.
Implementation history
Built up in phases on this branch:
- Phase 1 —
omarchy-shell phase 1: host the existing bar in a single shell - Phase 2 —
omarchy-shell phase 2: plugin registry and bar widget registry - Phase 3 —
omarchy-shell phase 3: fold bar-settings into the shell as a panel plugin - Phase 4 —
omarchy-shell phase 4: absorb background-switcher as a plugin - Phase 5 —
omarchy-shell phase 5: docs, cleanup, and migration crumbs - Phase 6 —
omarchy-shell phase 6: reviewer cleanup (path traversal, collision, races) - Phase 7 —
omarchy-shell phase 7: replace socket with IpcHandler, rename to image-picker - Phase 8a —
omarchy-shell phase 8a: unified shell.json with inline plugin settings
Shared services and Pipewire/UPower/Hyprland consolidation are explicitly out of scope here and deferred to a follow-up after a review pass.