Namespace omarchy plugins

This commit is contained in:
Ryan Hughes
2026-05-22 23:35:27 -04:00
parent 1e2a3156a9
commit d23d46915f
12 changed files with 215 additions and 113 deletions
+16 -10
View File
@@ -41,9 +41,13 @@ Full schema: [`shell/services/PluginRegistry.qml`](../shell/services/PluginRegis
1. Drop into `~/.config/omarchy/plugins/<id>/` with a `manifest.json` 1. Drop into `~/.config/omarchy/plugins/<id>/` with a `manifest.json`
plus the QML referenced from `entryPoints`. plus the QML referenced from `entryPoints`.
2. `omarchy-shell-ipc shell rescanPlugins` 2. `omarchy plugin rescan`
3. `omarchy-shell-ipc shell setPluginEnabled <id> true` 3. `omarchy plugin enable <id>`
4. Bar widgets also need adding to a section via bar settings. 4. Bar widgets also need adding to a section with `omarchy plugin bar add <id>`
or the visual editor (`omarchy plugin bar edit`).
The lower-level IPC methods remain available through `omarchy-shell shell ...`
for callers that need to talk directly to the running shell.
## IPC ## IPC
@@ -77,9 +81,9 @@ individual plugins (`bar`, `image-selector`, …).
"centerAnchor": "calendar", "centerAnchor": "calendar",
"fontFamily": "JetBrainsMono Nerd Font", "fontFamily": "JetBrainsMono Nerd Font",
"layout": { "layout": {
"left": [ { "id": "Omarchy" } ], "left": [ { "id": "omarchy.menu" } ],
"center": [ { "id": "calendar", "format": "HH:mm" } ], "center": [ { "id": "omarchy.clock", "format": "HH:mm" } ],
"right": [ { "id": "AudioPanel" } ] "right": [ { "id": "omarchy.audio" } ]
} }
}, },
"plugins": [ "plugins": [
@@ -94,10 +98,12 @@ Rules:
bar widgets, `plugins[]` for everything else. bar widgets, `plugins[]` for everything else.
2. Settings are inline on the entry. No `config:` sub-object, no 2. Settings are inline on the entry. No `config:` sub-object, no
merge layers. merge layers.
3. Third-party enabled ⇔ present; first-party plugins are always enabled. 3. Built-in bar widget ids are namespaced (`omarchy.clock`, `omarchy.audio`, …);
4. `allowMultiple: true` in the manifest permits multiple instances. legacy ids such as `Clock` and `AudioPanel` are accepted as aliases.
5. `idle.screensaver` and `idle.lock` are seconds since user idle began. 4. Third-party enabled ⇔ present; first-party plugins are always enabled.
6. `version: 1` is required. 5. `allowMultiple: true` in the manifest permits multiple instances.
6. `idle.screensaver` and `idle.lock` are seconds since user idle began.
7. `version: 1` is required.
`shell-defaults.json` describes the fresh-install state. When no `shell-defaults.json` describes the fresh-install state. When no
user `shell.json` exists, defaults are used verbatim. Once the user user `shell.json` exists, defaults are used verbatim. Once the user
+41
View File
@@ -0,0 +1,41 @@
echo "Namespace Omarchy built-in bar widget ids in shell.json"
config_file="$HOME/.config/omarchy/shell.json"
if [[ -s $config_file ]] && jq -e 'type == "object" and .version == 1' "$config_file" >/dev/null 2>&1; then
tmp=$(mktemp)
jq '
def canonical_widget_id:
if . == "Omarchy" then "omarchy.menu"
elif . == "Workspaces" then "omarchy.workspaces"
elif . == "Media" then "omarchy.media"
elif . == "AudioPanel" then "omarchy.audio"
elif . == "MonitorPanel" then "omarchy.monitor"
elif . == "NetworkPanel" then "omarchy.network"
elif . == "PowerPanel" then "omarchy.power"
elif . == "BluetoothPanel" then "omarchy.bluetooth"
elif . == "Clock" then "omarchy.clock"
elif . == "Indicators" then "omarchy.indicators"
elif . == "NotificationCenter" then "omarchy.notifications"
elif . == "SystemUpdate" then "omarchy.system-update"
elif . == "SystemStats" then "omarchy.system-stats"
elif . == "Tray" then "omarchy.tray"
elif . == "Weather" then "omarchy.weather"
elif . == "Microphone" then "omarchy.microphone"
elif . == "ActiveWindow" then "omarchy.active-window"
elif . == "KeyboardLayout" then "omarchy.keyboard-layout"
elif . == "LockKeys" then "omarchy.lock-keys"
elif . == "Spacer" then "omarchy.spacer"
else . end;
def canonical_entry:
if type == "string" then canonical_widget_id
elif type == "object" and has("id") then .id = (.id | canonical_widget_id)
else . end;
.bar.centerAnchor = ((.bar.centerAnchor // "") | canonical_widget_id) |
.bar.layout.left = ((.bar.layout.left // []) | map(canonical_entry)) |
.bar.layout.center = ((.bar.layout.center // []) | map(canonical_entry)) |
.bar.layout.right = ((.bar.layout.right // []) | map(canonical_entry))
' "$config_file" >"$tmp" && mv "$tmp" "$config_file"
fi
+34 -2
View File
@@ -42,6 +42,34 @@ QtObject {
return value !== null && typeof value === "object" && !Array.isArray(value) return value !== null && typeof value === "object" && !Array.isArray(value)
} }
readonly property var builtinWidgetAliases: ({
"Omarchy": "omarchy.menu",
"Workspaces": "omarchy.workspaces",
"Media": "omarchy.media",
"AudioPanel": "omarchy.audio",
"MonitorPanel": "omarchy.monitor",
"NetworkPanel": "omarchy.network",
"PowerPanel": "omarchy.power",
"BluetoothPanel": "omarchy.bluetooth",
"Clock": "omarchy.clock",
"Indicators": "omarchy.indicators",
"NotificationCenter": "omarchy.notifications",
"SystemUpdate": "omarchy.system-update",
"SystemStats": "omarchy.system-stats",
"Tray": "omarchy.tray",
"Weather": "omarchy.weather",
"Microphone": "omarchy.microphone",
"ActiveWindow": "omarchy.active-window",
"KeyboardLayout": "omarchy.keyboard-layout",
"LockKeys": "omarchy.lock-keys",
"Spacer": "omarchy.spacer"
})
function canonicalWidgetId(id) {
var key = String(id || "")
return builtinWidgetAliases[key] || key
}
// Best-effort base64 decode. Returns "" on parse failure rather than // Best-effort base64 decode. Returns "" on parse failure rather than
// surfacing garbage downstream. // surfacing garbage downstream.
function decodeBase64(value) { function decodeBase64(value) {
@@ -72,8 +100,12 @@ QtObject {
// so the two never drift. Entries are deep-cloned to decouple from the // so the two never drift. Entries are deep-cloned to decouple from the
// input config; consumers can mutate without leaking back to shell.json. // input config; consumers can mutate without leaking back to shell.json.
function normalizeLayoutEntry(entry) { function normalizeLayoutEntry(entry) {
if (typeof entry === "string") return { id: entry } if (typeof entry === "string") return { id: canonicalWidgetId(entry) }
if (isPlainObject(entry) && entry.id) return cloneJson(entry) if (isPlainObject(entry) && entry.id) {
var copy = cloneJson(entry)
copy.id = canonicalWidgetId(copy.id)
return copy
}
return null return null
} }
+30 -12
View File
@@ -89,9 +89,24 @@ The full schema lives in `services/PluginRegistry.qml`.
1. Drop the plugin into `~/.config/omarchy/plugins/<plugin-id>/`. 1. Drop the plugin into `~/.config/omarchy/plugins/<plugin-id>/`.
The directory must contain a `manifest.json` plus the QML files The directory must contain a `manifest.json` plus the QML files
referenced from its `entryPoints`. referenced from its `entryPoints`.
2. `omarchy-shell shell rescanPlugins`. 2. `omarchy plugin rescan`.
3. Enable the plugin with `omarchy-shell shell setPluginEnabled <id> true`. 3. Enable the plugin with `omarchy plugin enable <id>`.
4. If it's a `bar-widget`, add it to a layout section from the bar editor. 4. If it's a `bar-widget`, place it with `omarchy plugin bar add <id>` or
open the visual editor with `omarchy plugin bar edit`.
The lower-level IPC equivalents are still available via `omarchy-shell shell rescanPlugins`,
`omarchy-shell shell setPluginEnabled <id> true`, and `omarchy-shell shell listPlugins`.
The `omarchy plugin` command wraps those calls and can also edit the persisted
bar layout in `shell.json`.
To hack on an existing widget safely, clone it into a user plugin instead of
editing the built-in source. Third-party ids must be namespaced and may not use
the reserved `omarchy.*` prefix.
```bash
omarchy plugin clone omarchy.clock local.clock --replace --open pi
omarchy plugin clone # interactive source/name/tool picker
```
First-party plugins under `shell/plugins/` First-party plugins under `shell/plugins/`
are discovered the same way and cannot be disabled. are discovered the same way and cannot be disabled.
@@ -168,12 +183,12 @@ rewrites the `bar` subtree from the current `shell-defaults.json`.
"bar": { "bar": {
"position": "top", "position": "top",
"transparent": false, "transparent": false,
"centerAnchor": "Clock", "centerAnchor": "omarchy.clock",
"layout": { "layout": {
"left": [ { "id": "Omarchy" }, { "id": "Workspaces" } ], "left": [ { "id": "omarchy.menu" }, { "id": "omarchy.workspaces" } ],
"center": [ { "id": "Clock", "format": "HH:mm" } ], "center": [ { "id": "omarchy.clock", "format": "HH:mm" } ],
"right": [ "right": [
{ "id": "AudioPanel" } { "id": "omarchy.audio" }
] ]
} }
}, },
@@ -189,18 +204,21 @@ rewrites the `bar` subtree from the current `shell-defaults.json`.
2. **Settings are inline on the entry.** No `config:` sub-object, no 2. **Settings are inline on the entry.** No `config:` sub-object, no
separate per-plugin settings file, no merge layers. The fields on each separate per-plugin settings file, no merge layers. The fields on each
entry are the values the plugin sees. entry are the values the plugin sees.
3. **Third-party enabled ⇔ present.** A third-party plugin is enabled iff 3. **Built-in widget ids are namespaced.** Use ids such as `omarchy.clock`,
`omarchy.audio`, and `omarchy.network`. Legacy ids like `Clock` and
`AudioPanel` are accepted as aliases and migrated forward.
4. **Third-party enabled ⇔ present.** A third-party plugin is enabled iff
its id appears somewhere in shell.json. For bar widgets, the bar its id appears somewhere in shell.json. For bar widgets, the bar
settings UI adds/removes layout entries; other plugin kinds are enabled settings UI adds/removes layout entries; other plugin kinds are enabled
with the shell IPC. First-party plugins are always enabled. with the shell IPC. First-party plugins are always enabled.
4. **Multiple instances** are allowed when a manifest sets 5. **Multiple instances** are allowed when a manifest sets
`allowMultiple: true`. Each instance is independent — e.g. two clock `allowMultiple: true`. Each instance is independent — e.g. two clock
widgets in different timezones are just two `{"id":"Clock", "timezone": ...}` widgets in different timezones are just two `{"id":"omarchy.clock", "timezone": ...}`
entries with their own values. entries with their own values.
5. **Idle timings are top-level.** `idle.screensaver` and `idle.lock` 6. **Idle timings are top-level.** `idle.screensaver` and `idle.lock`
are seconds since user idle began, so the default lock fires at 300s are seconds since user idle began, so the default lock fires at 300s
even if the 150s screensaver starts first. even if the 150s screensaver starts first.
6. **`version: 1` is required** at the top level. The shell will fall back 7. **`version: 1` is required** at the top level. The shell will fall back
to defaults rather than load an unknown version. to defaults rather than load an unknown version.
## Implementation history ## Implementation history
+4 -4
View File
@@ -31,7 +31,7 @@ Item {
property var fallbackBarConfig: ({ property var fallbackBarConfig: ({
position: "top", position: "top",
transparent: false, transparent: false,
centerAnchor: "Clock", centerAnchor: "omarchy.clock",
layout: { left: [], center: [], right: [] } layout: { left: [], center: [], right: [] }
}) })
property var layoutConfig: fallbackBarConfig.layout property var layoutConfig: fallbackBarConfig.layout
@@ -133,7 +133,7 @@ Item {
var trayEntry = null var trayEntry = null
var result = [] var result = []
for (var i = 0; i < entries.length; i++) { for (var i = 0; i < entries.length; i++) {
if (entryId(entries[i]) === "Tray") trayEntry = entries[i] if (entryId(entries[i]) === "omarchy.tray") trayEntry = entries[i]
else result.push(entries[i]) else result.push(entries[i])
} }
if (trayEntry) { if (trayEntry) {
@@ -148,7 +148,7 @@ Item {
position = normalizePosition(config.position) position = normalizePosition(config.position)
transparent = config.transparent === true transparent = config.transparent === true
centerAnchor = String(config.centerAnchor || "") centerAnchor = Util.canonicalWidgetId(config.centerAnchor || "")
layoutConfig = normalizeLayout(config.layout) layoutConfig = normalizeLayout(config.layout)
barConfigSerial++ barConfigSerial++
} }
@@ -208,7 +208,7 @@ Item {
} }
function canonicalWidgetId(name) { function canonicalWidgetId(name) {
return String(name) return Util.canonicalWidgetId(name)
} }
function expandPath(path) { function expandPath(path) {
+22 -21
View File
@@ -6,16 +6,16 @@ QtObject {
required property var barWidgetRegistry required property var barWidgetRegistry
readonly property var metadata: ({ readonly property var metadata: ({
"Omarchy": { displayName: "Omarchy menu", description: "Launches the Omarchy menu", category: "Compositor", allowMultiple: false }, "omarchy.menu": { displayName: "Omarchy menu", description: "Launches the Omarchy menu", category: "Compositor", allowMultiple: false, sourceName: "Omarchy", legacyId: "Omarchy" },
"Workspaces": { displayName: "Workspaces", description: "Workspace number indicators", category: "Compositor", allowMultiple: false }, "omarchy.workspaces": { displayName: "Workspaces", description: "Workspace number indicators", category: "Compositor", allowMultiple: false, sourceName: "Workspaces", legacyId: "Workspaces" },
"Media": { displayName: "Media", description: "MPRIS now-playing with playback controls", category: "Media", allowMultiple: false }, "omarchy.media": { displayName: "Media", description: "MPRIS now-playing with playback controls", category: "Media", allowMultiple: false, sourceName: "Media", legacyId: "Media" },
"AudioPanel": { displayName: "Audio", description: "Volume slider, output picker, per-app mixer", category: "Audio", allowMultiple: false, sourceDir: "../panels", sourceName: "Audio" }, "omarchy.audio": { displayName: "Audio", description: "Volume slider, output picker, per-app mixer", category: "Audio", allowMultiple: false, sourceDir: "../panels", sourceName: "Audio", legacyId: "AudioPanel" },
"MonitorPanel": { displayName: "Display", description: "Brightness slider and laptop display controls", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Monitor" }, "omarchy.monitor": { displayName: "Display", description: "Brightness slider and laptop display controls", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Monitor", legacyId: "MonitorPanel" },
"NetworkPanel": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Network" }, "omarchy.network": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Network", legacyId: "NetworkPanel" },
"PowerPanel": { displayName: "Power", description: "Battery, power profile, and system stats", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Power" }, "omarchy.power": { displayName: "Power", description: "Battery, power profile, and system stats", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Power", legacyId: "PowerPanel" },
"BluetoothPanel": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Bluetooth" }, "omarchy.bluetooth": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Bluetooth", legacyId: "BluetoothPanel" },
"Clock": { displayName: "Clock", description: "Day/time label; click to toggle alternate format", category: "Time", allowMultiple: false, settingsForm: "clockSettings" }, "omarchy.clock": { displayName: "Clock", description: "Day/time label; click to toggle alternate format", category: "Time", allowMultiple: false, sourceName: "Clock", settingsForm: "clockSettings", legacyId: "Clock" },
"Indicators": { displayName: "Indicators", description: "Manual state indicators", category: "Status", allowMultiple: true, "omarchy.indicators": { displayName: "Indicators", description: "Manual state indicators", category: "Status", allowMultiple: true, sourceName: "Indicators", legacyId: "Indicators",
schema: [ schema: [
{ key: "items", type: "multiselect", label: "Indicators", description: "Choose which indicators this widget instance should show. Leave empty to show all indicators.", noSelectionText: "All indicators", placeholderText: "Search indicators...", emptyText: "No indicators", { key: "items", type: "multiselect", label: "Indicators", description: "Choose which indicators this widget instance should show. Leave empty to show all indicators.", noSelectionText: "All indicators", placeholderText: "Search indicators...", emptyText: "No indicators",
options: [ options: [
@@ -27,16 +27,16 @@ QtObject {
] }, ] },
{ key: "alwaysShow", type: "boolean", label: "Always Show", description: "Show inactive indicators without waiting for hover.", defaultValue: false } { key: "alwaysShow", type: "boolean", label: "Always Show", description: "Show inactive indicators without waiting for hover.", defaultValue: false }
] }, ] },
"NotificationCenter": { displayName: "Notification center", description: "Recent notifications + DND", category: "Status", allowMultiple: false }, "omarchy.notifications": { displayName: "Notification center", description: "Recent notifications + DND", category: "Status", allowMultiple: false, sourceName: "NotificationCenter", legacyId: "NotificationCenter" },
"SystemUpdate": { displayName: "System update", description: "Indicates available system updates", category: "System", allowMultiple: false }, "omarchy.system-update": { displayName: "System update", description: "Indicates available system updates", category: "System", allowMultiple: false, sourceName: "SystemUpdate", legacyId: "SystemUpdate" },
"SystemStats": { displayName: "System stats", description: "CPU icon — hover for graphs, click to open btop", category: "System", allowMultiple: false }, "omarchy.system-stats": { displayName: "System stats", description: "CPU icon — hover for graphs, click to open btop", category: "System", allowMultiple: false, sourceName: "SystemStats", legacyId: "SystemStats" },
"Tray": { displayName: "System tray", description: "Status notifier items", category: "Status", allowMultiple: false }, "omarchy.tray": { displayName: "System tray", description: "Status notifier items", category: "Status", allowMultiple: false, sourceName: "Tray", legacyId: "Tray" },
"Weather": { displayName: "Weather", description: "Weather pill with detail popup", category: "Info", allowMultiple: false, settingsForm: "weatherSettings" }, "omarchy.weather": { displayName: "Weather", description: "Weather pill with detail popup", category: "Info", allowMultiple: false, sourceName: "Weather", settingsForm: "weatherSettings", legacyId: "Weather" },
"Microphone": { displayName: "Microphone", description: "Mic input state and mute toggle", category: "Audio", allowMultiple: false }, "omarchy.microphone": { displayName: "Microphone", description: "Mic input state and mute toggle", category: "Audio", allowMultiple: false, sourceName: "Microphone", legacyId: "Microphone" },
"ActiveWindow": { displayName: "Active window", description: "Title of the focused window", category: "Compositor", allowMultiple: false }, "omarchy.active-window": { displayName: "Active window", description: "Title of the focused window", category: "Compositor", allowMultiple: false, sourceName: "ActiveWindow", legacyId: "ActiveWindow" },
"KeyboardLayout": { displayName: "Keyboard layout", description: "Current xkb layout, click cycles", category: "Compositor", allowMultiple: false }, "omarchy.keyboard-layout": { displayName: "Keyboard layout", description: "Current xkb layout, click cycles", category: "Compositor", allowMultiple: false, sourceName: "KeyboardLayout", legacyId: "KeyboardLayout" },
"LockKeys": { displayName: "Lock keys", description: "Caps / Num / Scroll lock indicators", category: "System", allowMultiple: false }, "omarchy.lock-keys": { displayName: "Lock keys", description: "Caps / Num / Scroll lock indicators", category: "System", allowMultiple: false, sourceName: "LockKeys", legacyId: "LockKeys" },
"Spacer": { displayName: "Spacer", description: "Configurable blank space", category: "Layout", allowMultiple: true, settingsForm: "spacerSettings" } "omarchy.spacer": { displayName: "Spacer", description: "Configurable blank space", category: "Layout", allowMultiple: true, sourceName: "Spacer", settingsForm: "spacerSettings", legacyId: "Spacer" }
}) })
property var registeredComponents: ({}) property var registeredComponents: ({})
@@ -64,7 +64,8 @@ QtObject {
allowMultiple: meta.allowMultiple === true, allowMultiple: meta.allowMultiple === true,
settingsForm: meta.settingsForm || "", settingsForm: meta.settingsForm || "",
schema: Array.isArray(meta.schema) ? meta.schema : [], schema: Array.isArray(meta.schema) ? meta.schema : [],
source: "first-party" source: "first-party",
legacyId: meta.legacyId || ""
} }
var comp = Qt.createComponent(url, Component.Asynchronous) var comp = Qt.createComponent(url, Component.Asynchronous)
function finalize() { function finalize() {
+32 -31
View File
@@ -16,7 +16,7 @@ the shell for its whole session.
The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). Out of the box the shell uses [`shell-defaults.json`](../../shell-defaults.json). Once you customize anything via `omarchy launch bar settings` or by editing shell.json directly, your file is canonical — there is no deep-merge. The bar config lives under the `bar:` key of [`~/.config/omarchy/shell.json`](../../README.md#shelljson-shape). Out of the box the shell uses [`shell-defaults.json`](../../shell-defaults.json). Once you customize anything via `omarchy launch bar settings` or by editing shell.json directly, your file is canonical — there is no deep-merge.
Launch the visual editor with `omarchy launch bar settings` (or run `omarchy-launch-bar-settings`) to reorder widgets, add/remove them, and tweak per-widget options without editing JSON by hand. You can also right-click empty space to the left or right of the centered clock module to open it; double-left-click the same empty space to toggle bar transparency. Launch the visual editor with `omarchy launch bar settings` / `omarchy plugin bar edit` (or run `omarchy-launch-bar-settings`) to reorder widgets, add/remove them, and tweak per-widget options without editing JSON by hand. For scriptable changes, use `omarchy plugin bar list`, `omarchy plugin bar add`, `omarchy plugin bar move`, `omarchy plugin bar remove`, and `omarchy plugin bar set`. You can also right-click empty space to the left or right of the centered clock module to open it; double-left-click the same empty space to toggle bar transparency.
Example `shell.json` (bar subtree only shown): Example `shell.json` (bar subtree only shown):
@@ -26,20 +26,20 @@ Example `shell.json` (bar subtree only shown):
"bar": { "bar": {
"position": "top", "position": "top",
"transparent": false, "transparent": false,
"centerAnchor": "Clock", "centerAnchor": "omarchy.clock",
"layout": { "layout": {
"left": [ "left": [
{ "id": "Omarchy" }, { "id": "omarchy.menu" },
{ "id": "Spacer", "size": 12 }, { "id": "omarchy.spacer", "size": 12 },
{ "id": "Workspaces" } { "id": "omarchy.workspaces" }
], ],
"center": [ "center": [
{ "id": "Media" }, { "id": "omarchy.media" },
{ "id": "Clock", "format": "HH:mm" } { "id": "omarchy.clock", "format": "HH:mm" }
], ],
"right": [ "right": [
{ "id": "AudioPanel" }, { "id": "omarchy.audio" },
{ "id": "PowerPanel" } { "id": "omarchy.power" }
] ]
} }
} }
@@ -54,17 +54,17 @@ Example `shell.json` (bar subtree only shown):
| Name | What it does | Interactions | | Name | What it does | Interactions |
|---|---|---| |---|---|---|
| `Omarchy` | Omarchy menu launcher | left = menu · right = terminal | | `omarchy.menu` | Omarchy menu launcher | left = menu · right = terminal |
| `Workspaces` | Hyprland workspace switcher | left = focus workspace | | `omarchy.workspaces` | Hyprland workspace switcher | left = focus workspace |
| `Clock` | Date/time label | left = alternate format · right = timezone selector | | `omarchy.clock` | Date/time label | left = alternate format · right = timezone selector |
| `Media` | MPRIS now-playing — scrolling track + artist, cover-art popup | left = play/pause · middle = next · scroll = prev/next · right = popup | | `omarchy.media` | MPRIS now-playing — scrolling track + artist, cover-art popup | left = play/pause · middle = next · scroll = prev/next · right = popup |
| `Indicators` | Manual state indicators | left = indicator action | | `omarchy.indicators` | Manual state indicators | left = indicator action |
| `NotificationCenter` | Bell with badge + popup with recent notifications, DND toggle | left = popup · right = toggle DND | | `omarchy.notifications` | Bell with badge + popup with recent notifications, DND toggle | left = popup · right = toggle DND |
| `SystemUpdate` | Available update indicator | left = update | | `omarchy.system-update` | Available update indicator | left = update |
| `SystemStats` | Inline CPU + memory sparklines, popup with detail | left = popup · right = terminal | | `omarchy.system-stats` | Inline CPU + memory sparklines, popup with detail | left = popup · right = terminal |
| `Tray` | System tray | hover = reveal drawer · right on chevron = manage | | `omarchy.tray` | System tray | hover = reveal drawer · right on chevron = manage |
| `Weather` | Weather icon + popup with forecast | left = popup · right = full notification | | `omarchy.weather` | Weather icon + popup with forecast | left = popup · right = full notification |
| `Microphone` | Mic icon + scroll volume | left = mute toggle · middle = audio panel · scroll = source volume | | `omarchy.microphone` | Mic icon + scroll volume | left = mute toggle · middle = audio panel · scroll = source volume |
### First-party panels (in `../panels/`) ### First-party panels (in `../panels/`)
@@ -76,7 +76,7 @@ Example `shell.json` (bar subtree only shown):
| `panels.bluetooth` | Bluetooth icon + popup with device list, connect/disconnect, battery | left = popup · right = toggle radio · middle = bluetoothctl TUI | | `panels.bluetooth` | Bluetooth icon + popup with device list, connect/disconnect, battery | left = popup · right = toggle radio · middle = bluetoothctl TUI |
| `panels.monitor` | Brightness and laptop display controls | left = popup | | `panels.monitor` | Brightness and laptop display controls | left = popup |
The `Indicators` widget loads individual bar indicators from `indicators/`. Omit `items` (or set it to an empty array) to show all indicators in the default order, or set `items` to a subset such as `["Dnd", "NightLight"]`. Set `alwaysShow` to `true` to keep inactive indicators visible instead of revealing them only on hover. Multiple `Indicators` instances are allowed, so different sections can show different subsets. Rich panels such as `PowerPanel`, `NetworkPanel`, and `AudioPanel` live in `../panels/` above. The `omarchy.indicators` widget loads individual bar indicators from `indicators/`. Omit `items` (or set it to an empty array) to show all indicators in the default order, or set `items` to a subset such as `["Dnd", "NightLight"]`. Set `alwaysShow` to `true` to keep inactive indicators visible instead of revealing them only on hover. Multiple `omarchy.indicators` instances are allowed, so different sections can show different subsets. Rich popup widgets such as `omarchy.power`, `omarchy.network`, and `omarchy.audio` live in `../panels/` above.
## Orientation ## Orientation
@@ -94,9 +94,9 @@ Command module:
"bar": { "bar": {
"layout": { "layout": {
"right": [ "right": [
{ "id": "Tray" }, { "id": "omarchy.tray" },
{ "id": "vpn", "type": "command", "exec": "~/.config/omarchy/bar/scripts/vpn-status", "interval": 5, "tooltip": "VPN", "onClick": "nm-connection-editor" }, { "id": "vpn", "type": "command", "exec": "~/.config/omarchy/bar/scripts/vpn-status", "interval": 5, "tooltip": "VPN", "onClick": "nm-connection-editor" },
{ "id": "AudioPanel" } { "id": "omarchy.audio" }
] ]
} }
} }
@@ -118,7 +118,7 @@ QML module:
"layout": { "layout": {
"right": [ "right": [
{ "id": "gpu", "type": "qml" }, { "id": "gpu", "type": "qml" },
{ "id": "AudioPanel" } { "id": "omarchy.audio" }
] ]
} }
} }
@@ -169,15 +169,16 @@ Widgets receive `bar` (the shell root), `moduleName` (string), and `settings` (o
- `bar.showTooltip(target, text)` / `bar.hideTooltip(target)` — shared tooltip popup - `bar.showTooltip(target, text)` / `bar.hideTooltip(target)` — shared tooltip popup
- `bar.requestPopout(owner)` / `bar.releasePopout(owner)` — one-popup-at-a-time coordinator - `bar.requestPopout(owner)` / `bar.releasePopout(owner)` — one-popup-at-a-time coordinator
First-party bar widgets live in `widgets/<Name>.qml`; first-party panels First-party bar widgets live in `widgets/<Name>.qml`; richer popup widgets
live in `../panels/<Name>.qml` and expose IPC targets such as live in `../panels/<Name>.qml` and expose IPC targets such as
`panels.audio`. Bar layout ids use UpperCamelCase names such as `AudioPanel`, `panels.audio`. Bar layout ids are namespaced, e.g. `omarchy.audio`,
`NetworkPanel`, and so on, and are picked up by the shell's `omarchy.network`, and `omarchy.clock`. Legacy UpperCamelCase ids such as
`BarWidgetRegistry` at startup; reference one by `id` in any layout list. `AudioPanel` and `Clock` are still accepted as aliases, but new configs should
use the namespaced ids.
Third-party widgets ship as separate plugins under Third-party widgets ship as separate plugins under
`~/.config/omarchy/plugins/<plugin-id>/` with their own `manifest.json` `~/.config/omarchy/plugins/<plugin-id>/` with their own `manifest.json`
declaring `kinds: ["bar-widget"]` and a `barWidget` entry point. See declaring `kinds: ["bar-widget"]` and a `barWidget` entry point. See
[../../README.md](../../README.md) for the manifest schema. Enable or [../../README.md](../../README.md) for the manifest schema. Enable,
rescan third-party plugins with `omarchy-shell shell setPluginEnabled` rescan, and place third-party plugins with `omarchy plugin enable`,
and `omarchy-shell shell rescanPlugins`. `omarchy plugin rescan`, and `omarchy plugin bar add`.
+2 -1
View File
@@ -70,7 +70,8 @@ BarWidget {
function persistTrayState(pinned, hidden) { function persistTrayState(pinned, hidden) {
if (!root.bar || !root.bar.shell || typeof root.bar.shell.updateEntryInline !== "function") return if (!root.bar || !root.bar.shell || typeof root.bar.shell.updateEntryInline !== "function") return
root.bar.shell.updateEntryInline("Tray", { id: "Tray", pinned: pinned, hidden: hidden }) var id = root.moduleName || "omarchy.tray"
root.bar.shell.updateEntryInline(id, { id: id, pinned: pinned, hidden: hidden })
} }
function togglePin(iid) { function togglePin(iid) {
+10 -10
View File
@@ -128,16 +128,16 @@ Item {
bar: { bar: {
position: "top", position: "top",
transparent: false, transparent: false,
centerAnchor: "Clock", centerAnchor: "omarchy.clock",
layout: { layout: {
left: [{ id: "Omarchy" }, { id: "Workspaces" }], left: [{ id: "omarchy.menu" }, { id: "omarchy.workspaces" }],
center: [ center: [
{ id: "Clock", format: "dddd HH:mm", formatAlt: "dd MMMM 'W'ww yyyy", verticalFormat: "HH\n\u2014\nmm" }, { id: "omarchy.clock", format: "dddd HH:mm", formatAlt: "dd MMMM 'W'ww yyyy", verticalFormat: "HH\n\u2014\nmm" },
{ id: "Weather" }, { id: "Indicators" }, { id: "SystemUpdate" } { id: "omarchy.weather" }, { id: "omarchy.indicators" }, { id: "omarchy.system-update" }
], ],
right: [ right: [
{ id: "Tray" }, { id: "BluetoothPanel" }, { id: "NetworkPanel" }, { id: "omarchy.tray" }, { id: "omarchy.bluetooth" }, { id: "omarchy.network" },
{ id: "AudioPanel" }, { id: "MonitorPanel" }, { id: "PowerPanel" } { id: "omarchy.audio" }, { id: "omarchy.monitor" }, { id: "omarchy.power" }
] ]
} }
}, },
@@ -145,7 +145,7 @@ Item {
}) })
property var defaultConfig: builtinShellConfig property var defaultConfig: builtinShellConfig
property var draft: ({ version: 1, bar: { position: "top", transparent: false, centerAnchor: "Clock", layout: { left: [], center: [], right: [] } }, plugins: [] }) property var draft: ({ version: 1, bar: { position: "top", transparent: false, centerAnchor: "omarchy.clock", layout: { left: [], center: [], right: [] } }, plugins: [] })
property int draftRevision: 0 property int draftRevision: 0
property bool suppressReload: false property bool suppressReload: false
@@ -179,7 +179,7 @@ Item {
bar: { bar: {
position: String(bar.position || "top"), position: String(bar.position || "top"),
transparent: bar.transparent === true, transparent: bar.transparent === true,
centerAnchor: String(bar.centerAnchor || ""), centerAnchor: Util.canonicalWidgetId(bar.centerAnchor || ""),
layout: Util.normalizeLayout(bar.layout || {}) layout: Util.normalizeLayout(bar.layout || {})
}, },
plugins: plugins plugins: plugins
@@ -320,7 +320,7 @@ Item {
} }
function canonicalWidgetId(id) { function canonicalWidgetId(id) {
return String(id || "") return Util.canonicalWidgetId(id)
} }
function widgetMetadata(id) { function widgetMetadata(id) {
@@ -380,7 +380,7 @@ Item {
function widgetAllowsMultiple(id) { function widgetAllowsMultiple(id) {
var meta = widgetMetadata(id) var meta = widgetMetadata(id)
if (meta.allowMultiple === true) return true if (meta.allowMultiple === true) return true
return String(id) === "Spacer" return canonicalWidgetId(id) === "omarchy.spacer"
} }
function catalogIds() { function catalogIds() {
+5 -3
View File
@@ -229,11 +229,13 @@ QtObject {
var merged = {} var merged = {}
for (var fk in firstParty) merged[fk] = firstParty[fk] for (var fk in firstParty) merged[fk] = firstParty[fk]
// Third-party plugins never shadow a first-party one with the same id. // Third-party plugins never shadow first-party ids. The whole
// `omarchy.*` namespace is reserved for built-ins, including bar widgets
// registered outside the manifest-based plugin registry.
for (var tk in thirdParty) { for (var tk in thirdParty) {
if (firstParty[tk]) { if (firstParty[tk] || String(tk).indexOf("omarchy.") === 0) {
console.warn("PluginRegistry: plugin " + tk console.warn("PluginRegistry: plugin " + tk
+ " rejected: id collides with first-party plugin") + " rejected: id is reserved for first-party Omarchy plugins")
continue continue
} }
merged[tk] = thirdParty[tk] merged[tk] = thirdParty[tk]
+13 -13
View File
@@ -7,51 +7,51 @@
"bar": { "bar": {
"position": "top", "position": "top",
"transparent": false, "transparent": false,
"centerAnchor": "Clock", "centerAnchor": "omarchy.clock",
"layout": { "layout": {
"left": [ "left": [
{ {
"id": "Omarchy" "id": "omarchy.menu"
}, },
{ {
"id": "Workspaces" "id": "omarchy.workspaces"
} }
], ],
"center": [ "center": [
{ {
"id": "Clock", "id": "omarchy.clock",
"format": "dddd HH:mm", "format": "dddd HH:mm",
"formatAlt": "dd MMMM 'W'ww yyyy", "formatAlt": "dd MMMM 'W'ww yyyy",
"verticalFormat": "HH\n\u2014\nmm" "verticalFormat": "HH\n\u2014\nmm"
}, },
{ {
"id": "Weather" "id": "omarchy.weather"
}, },
{ {
"id": "Indicators" "id": "omarchy.indicators"
}, },
{ {
"id": "SystemUpdate" "id": "omarchy.system-update"
} }
], ],
"right": [ "right": [
{ {
"id": "Tray" "id": "omarchy.tray"
}, },
{ {
"id": "BluetoothPanel" "id": "omarchy.bluetooth"
}, },
{ {
"id": "NetworkPanel" "id": "omarchy.network"
}, },
{ {
"id": "AudioPanel" "id": "omarchy.audio"
}, },
{ {
"id": "MonitorPanel" "id": "omarchy.monitor"
}, },
{ {
"id": "PowerPanel" "id": "omarchy.power"
} }
] ]
} }
+6 -6
View File
@@ -44,11 +44,11 @@ ShellRoot {
bar: { bar: {
position: "top", position: "top",
transparent: false, transparent: false,
centerAnchor: "Clock", centerAnchor: "omarchy.clock",
layout: { layout: {
left: [{ id: "Omarchy" }, { id: "Workspaces" }], left: [{ id: "omarchy.menu" }, { id: "omarchy.workspaces" }],
center: [{ id: "Clock", format: "dddd HH:mm" }], center: [{ id: "omarchy.clock", format: "dddd HH:mm" }],
right: [{ id: "AudioPanel" }] right: [{ id: "omarchy.audio" }]
} }
}, },
plugins: [] plugins: []
@@ -264,7 +264,7 @@ ShellRoot {
// new shellConfig in a local clone, and only persist if anything actually // new shellConfig in a local clone, and only persist if anything actually
// changed so reactive bindings do not dirty shell.json unnecessarily. // changed so reactive bindings do not dirty shell.json unnecessarily.
function updateEntryInline(moduleName, settings) { function updateEntryInline(moduleName, settings) {
var stripped = String(moduleName) var stripped = Util.canonicalWidgetId(moduleName)
var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig)) var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig))
if (!Util.isPlainObject(copy.bar)) copy.bar = { layout: { left: [], center: [], right: [] } } if (!Util.isPlainObject(copy.bar)) copy.bar = { layout: { left: [], center: [], right: [] } }
if (!Util.isPlainObject(copy.bar.layout)) copy.bar.layout = { left: [], center: [], right: [] } if (!Util.isPlainObject(copy.bar.layout)) copy.bar.layout = { left: [], center: [], right: [] }
@@ -276,7 +276,7 @@ ShellRoot {
for (var s = 0; s < sections.length; s++) { for (var s = 0; s < sections.length; s++) {
var arr = copy.bar.layout[sections[s]] || [] var arr = copy.bar.layout[sections[s]] || []
for (var i = 0; i < arr.length; i++) { for (var i = 0; i < arr.length; i++) {
if (arr[i] && arr[i].id === stripped) { if (arr[i] && Util.canonicalWidgetId(arr[i].id) === stripped) {
var next = { id: stripped } var next = { id: stripped }
for (var k in settings) if (k !== "id") next[k] = settings[k] for (var k in settings) if (k !== "id") next[k] = settings[k]
if (JSON.stringify(arr[i]) !== JSON.stringify(next)) { if (JSON.stringify(arr[i]) !== JSON.stringify(next)) {