diff --git a/AGENTS.md b/AGENTS.md index df4e70fd..63ef0897 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -120,7 +120,7 @@ Plugin contract: `~/.config/omarchy/plugins//` (third-party). - Every plugin ships a `manifest.json` declaring `id`, `kinds`, `activation`, and `entryPoints`. The full schema is in - `default/quickshell/omarchy-shell/README.md`. + [`docs/omarchy-shell.md`](docs/omarchy-shell.md). - Entry-point QML files are `Item`s (not `ShellRoot`), and accept the shell-injected properties `omarchyPath`, `shell`, `manifest`, and `pluginRegistry` / `barWidgetRegistry` as appropriate. diff --git a/README.md b/README.md index a630d13c..4bd515f1 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Omarchy is a beautiful, modern & opinionated Linux distribution by DHH. Read more at [omarchy.org](https://omarchy.org). +## Docs + +- [omarchy-shell](docs/omarchy-shell.md) — shell host, plugin manifest, IPC, `shell.json`, custom bar modules + ## License Omarchy is released under the [MIT License](https://opensource.org/licenses/MIT). diff --git a/default/quickshell/omarchy-shell/README.md b/default/quickshell/omarchy-shell/README.md deleted file mode 100644 index ded897ba..00000000 --- a/default/quickshell/omarchy-shell/README.md +++ /dev/null @@ -1,217 +0,0 @@ -# Omarchy shell - -`omarchy-shell` is a single long-running [Quickshell](https://quickshell.org/) -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/ - notifications/ - osd/ - polkit/ -``` - -The plugin discovery path is documented in [plugins/README.md](plugins/README.md). - -## Plugin manifest - -Every plugin ships a `manifest.json` describing what it is and how the -shell should load it. Minimal example: - -```json -{ - "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 | -| `bar` | Reserved for the first-party bar host (`omarchy.bar`). Third-party plugins should ship `bar-widget`s; they do not replace the host bar. | - -`activation` is either `persistent` (loaded on startup, never unloaded) or -`on-demand` (loaded by `shell summon ` 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 - -1. Drop the plugin into `~/.config/omarchy/plugins//`. - The directory must contain a `manifest.json` plus the QML files - referenced from its `entryPoints`. -2. `omarchy-shell-ipc shell rescanPlugins`. -3. Enable the plugin with `omarchy-shell-ipc shell setPluginEnabled true`. -4. 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 ` | `ok` / `unknown` | load + open a panel/overlay plugin | -| `hide ` | — | close a previously-summoned plugin | -| `toggle ` | — | summon if closed, hide if open | -| `rescanPlugins` | — | re-walk plugin dirs and pick up new/changed manifests | -| `setPluginEnabled ` | — | 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-shell` on boot. -Use `omarchy-restart-shell` to reload the long-running shell process. - -A convenience wrapper, [`omarchy-shell-ipc`](../../../bin/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//` | 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 bar to defaults** in `omarchy launch bar settings` -rewrites the `bar` subtree from the current `shell-defaults.json`. - -### shell.json shape - -```json -{ - "version": 1, - "bar": { - "position": "top", - "transparent": false, - "centerAnchor": "calendar", - "fontFamily": "JetBrainsMono Nerd Font", - "layout": { - "left": [ { "id": "omarchy" }, { "id": "workspaces" } ], - "center": [ { "id": "calendar", "format": "HH:mm" } ], - "right": [ - { "id": "audioPanel" } - ] - } - }, - "plugins": [ - { "id": "omarchy.settings" }, - { "id": "omarchy.image-picker" } - ] -} -``` - -### Storage rules - -1. **Every plugin instance is one entry.** Either in `bar.layout.
` - for bar widgets, or in `plugins[]` for panels, overlays, services, - menus, and anything else non-bar. -2. **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. -3. **Enabled ⇔ present.** A plugin is enabled iff its id appears somewhere - in shell.json. For bar widgets, the bar settings UI adds/removes layout - entries; other plugin kinds are enabled with the shell IPC. -4. **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. -5. **`version: 1` is 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. diff --git a/default/quickshell/omarchy-shell/plugins/README.md b/default/quickshell/omarchy-shell/plugins/README.md deleted file mode 100644 index a8fe4cd5..00000000 --- a/default/quickshell/omarchy-shell/plugins/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# First-party plugins - -These plugins ship with Omarchy and are loaded by the shell at startup. -They use the same `manifest.json` contract as third-party plugins; the -only difference is that the shell flags them with `__isFirstParty: true` -so they cannot be disabled. - -User-installed plugins live alongside these conceptually but on disk under -`~/.config/omarchy/plugins//` rather than in this directory. - -| Plugin | id | kinds | activation | entry point | -|---------------|-------------------------|-----------|------------|-------------------------------------| -| Bar | `omarchy.bar` | `bar` | persistent | `bar/Bar.qml` | -| Bar settings | `omarchy.settings` | `panel` | on-demand | `settings/SettingsPanel.qml` | -| Image picker | `omarchy.image-picker` | `overlay` | on-demand | `image-picker/ImagePicker.qml` | -| Emoji picker | `omarchy.emoji-picker` | `overlay` | on-demand | `emoji-picker/EmojiPicker.qml` | -| Clipboard mgr | `omarchy.clipboard-picker`| `overlay` | on-demand | `clipboard-picker/ClipboardPicker.qml`| -| Omarchy menu | `omarchy.menu` | `menu` | on-demand | `menu/Menu.qml` | -| Notifications | `omarchy.notifications` | `service` | persistent | `notifications/Service.qml` | -| OSD | `omarchy.osd` | `panel` | persistent | `osd/Osd.qml` | -| Polkit agent | `omarchy.polkit` | `service` | persistent | `polkit/PolkitAgent.qml` | -| Dev gallery | `omarchy.dev-gallery` | `panel` | on-demand | `dev-gallery/GalleryPanel.qml` | - -## Bar - -The status bar. Mounted at startup, lives forever. Layout lives in the -top-level `bar:` subtree of `~/.config/omarchy/shell.json` (with the shell -providing [`shell-defaults.json`](../shell-defaults.json) when the user has -no file). Owns the `bar` IPC target for refresh hooks fired by indicator -scripts. See [`bar/README.md`](bar/README.md) for the widget catalogue -and customization schema. - -## Bar settings - -Visual editor for the bar layout. Summoned by -`omarchy-shell-ipc shell summon omarchy.settings "{}"` (which is what -`omarchy launch bar settings` ultimately calls). Provides: - -- bar position and center-anchor controls -- per-section add/move/remove/edit of bar widget entries -- dynamic per-widget settings forms that write inline back to the - corresponding shell.json entry - -## Image picker - -Fullscreen image-grid selector overlay. Used by `omarchy-menu-images` -(wallpaper picker) and `omarchy-theme-switcher` (theme picker) and any -other caller that wants to present a directory of images with previews. - -Two ways to drive it: - -- Shell-level summon: `omarchy-shell-ipc shell summon omarchy.image-picker ''`. - The payload can carry `imageDirs`, `imageRows`, `selectedImage`, - `selectionFile`, `doneFile`, `showLabels`, `filterable`. Best for - in-shell callers that already speak JSON. -- Direct IPC target: `omarchy-shell-ipc image-selector open `. - Positional args; `imageRowsB64` is base64-encoded so embedded newlines / - tabs survive the bash argv handoff. This is what `omarchy-menu-images` - uses. Colors come from the central shell theme singleton; there is no - per-call override surface. - -The selection round-trip remains file-based: callers create a -`selection_file` and `done_file` (both `mktemp`), pass the paths, and -poll `done_file` for existence. The plugin writes the chosen path into -`selection_file` and touches `done_file` when it's done. `cancel` IPC -clears it without writing a selection. - -The plugin has `keepLoaded: true` so the layer-shell window survives -between summons within a single shell session. - -## Polkit agent - -Theme-aware authentication dialog for privileged actions. It uses -Quickshell's native `Quickshell.Services.Polkit.PolkitAgent` backend and -runs inside the long-lived `omarchy-shell` process, replacing the old -`polkit-gnome-authentication-agent-1` autostart. - -## Omarchy menu - -Quickshell-powered replacement for the legacy Walker-driven `omarchy-menu`. -The menu UI lives in `menu/Menu.qml` as a first-party `menu` plugin and is -summoned through the shell (`omarchy-shell-ipc shell summon omarchy.menu ...`), -so it shares the long-running `omarchy-shell` process instead of starting a -second Quickshell instance. - -The menu definition lives outside the shell host code: - -- defaults: `default/omarchy/omarchy-menu.jsonc` -- user extensions: `~/.config/omarchy/extensions/omarchy-menu.jsonc` - -The shell parses both JSONC files at startup (with `watchChanges: true` -so edits take effect without a restart), evaluates `when:` / `checked:` -bash expressions in a single batched subprocess, and executes the -selected `action:` string directly via `Quickshell.execDetached`. The -long-running shell process keeps the parsed menu in memory, so the -keybind → IPC → visible path costs ~30ms cold. - -## Coming soon - -- `omarchy.theme-switcher` — folds theme switching into the shell. diff --git a/default/quickshell/omarchy-shell/plugins/bar/README.md b/default/quickshell/omarchy-shell/plugins/bar/README.md deleted file mode 100644 index f8816540..00000000 --- a/default/quickshell/omarchy-shell/plugins/bar/README.md +++ /dev/null @@ -1,174 +0,0 @@ -# Omarchy bar - -This is the Quickshell implementation of the Omarchy status bar. It is -shipped as a first-party plugin of [`omarchy-shell`](../../README.md), the -long-running shell host. The bar is mounted at startup and lives inside -the shell for its whole session. - -- `manifest.json` declares the plugin (`id: omarchy.bar`, `kind: bar`, `activation: persistent`) and points at `Bar.qml` as the entry point. -- `Bar.qml` is Omarchy-owned bar engine code, loaded by the omarchy-shell host. Users should not edit it directly. -- `widgets/` holds first-party widgets — modular, interactive components shipped with Omarchy. -- `common/` holds shared QML helpers (buttons, sliders, popup cards). -- The bar receives its config from the host shell as a `barConfig` property; the host loads it from `~/.config/omarchy/shell.json` (or `shell-defaults.json` when the user has no file). -- `omarchy-style-bar-position` updates only the user shell.json file. - -## Customizing - -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 to open it; double-left-click the same empty space to toggle bar transparency. - -Example `shell.json` (bar subtree only shown): - -```json -{ - "version": 1, - "bar": { - "position": "top", - "transparent": false, - "centerAnchor": "calendar", - "layout": { - "left": [ - { "id": "omarchy" }, - { "id": "spacer", "size": 12 }, - { "id": "workspaces" } - ], - "center": [ - { "id": "media" }, - { "id": "calendar", "format": "HH:mm" } - ], - "right": [ - { "id": "audioPanel" }, - { "id": "battery" } - ] - } - } -} -``` - -`centerAnchor` pins one center module to the exact horizontal/vertical center and flanks others around it. Set to an empty string to disable anchoring (the center list is centered as a group). - -## Module catalogue - -### First-party interactive widgets (in `widgets/`) - -| Name | What it does | Interactions | -|---|---|---| -| `media` | MPRIS now-playing — scrolling track + artist, cover-art popup | left = play/pause · middle = next · scroll = prev/next · right = popup | -| `audioPanel` | Volume icon + popup with master slider, output-device picker, per-app mixer | left = popup · right = mute · middle = audio TUI · scroll = volume | -| `networkPanel` | Wi-Fi/Ethernet icon + popup with Wi-Fi scan, signal, connect, DNS provider selection | left = popup · right = nmtui | -| `bluetoothPanel` | Bluetooth icon + popup with device list, connect/disconnect, battery | left = popup · right = toggle radio · middle = bluetoothctl TUI | -| `calendar` | Clock + popup with month-grid calendar | left = popup · right = tz selector | -| `notificationCenter` | Bell with badge + popup with recent notifications, DND toggle | left = popup · right = toggle DND | -| `systemStats` | Inline CPU + memory sparklines, popup with detail | left = popup · right = terminal | -| `weatherFlyout` | Weather icon + popup with forecast | left = popup · right = full notification | -| `idleInhibitor` | Coffee-cup that toggles `omarchy-toggle-idle` | left = toggle | -| `microphone` | Mic icon + scroll volume | left = mute toggle · middle = audio TUI · scroll = source volume | - -### Built-in legacy modules (in `shell.qml`) - -`omarchy`, `workspaces`, `clock`, `weather`, `update`, `voxtype`, `screenRecording`, `idle`, `notifications`, `tray`, `bluetooth`, `network`, `audio`, `cpu`, `battery`. - -These remain available — set them in `layout` to use them instead of the richer widget versions. - -## Orientation - -All widgets work in `top`, `bottom`, `left`, and `right` positions. Popups anchor on the side opposite the bar edge, sliding into the workspace. Vertical bars use 28px width; widgets that show text fall back to compact icon-only forms (e.g. `media` hides its scrolling label). - -## Custom user modules - -The schema accepts arbitrary module ids that you provide. Set `type` to `command` for shell-driven output or `qml` for a custom QML widget. Both still go under `bar.layout.
` in `shell.json`. - -Command module: - -```json -{ - "version": 1, - "bar": { - "layout": { - "right": [ - { "id": "tray" }, - { "id": "vpn", "type": "command", "exec": "~/.config/omarchy/bar/scripts/vpn-status", "interval": 5, "tooltip": "VPN", "onClick": "nm-connection-editor" }, - { "id": "audioPanel" } - ] - } - } -} -``` - -The command may print plain text or Waybar-style JSON, for example: - -```json -{"text":"󰌆","tooltip":"Work VPN","class":"active"} -``` - -QML module: - -```json -{ - "version": 1, - "bar": { - "layout": { - "right": [ - { "id": "gpu", "type": "qml" }, - { "id": "audioPanel" } - ] - } - } -} -``` - -Then create `~/.config/omarchy/bar/modules/gpu.qml`. If you want to store it elsewhere, add a `source` path. - -Custom QML modules should be an `Item` with `implicitWidth` and `implicitHeight`. They may optionally define these properties, which the bar fills after loading: - -```qml -import QtQuick - -Item { - property var bar - property string moduleName - property var settings - - implicitWidth: 28 - implicitHeight: bar ? bar.barSize : 26 - - Text { - anchors.centerIn: parent - text: "GPU" - color: bar ? bar.foreground : "white" - font.family: bar ? bar.fontFamily : "monospace" - font.pixelSize: 12 - } - - MouseArea { - anchors.fill: parent - onClicked: if (bar) bar.run("omarchy-launch-or-focus-tui btop") - } -} -``` - -## Bar properties available to widgets - -Widgets receive `bar` (the shell root), `moduleName` (string), and `settings` (object) injected at load time. The bar exposes: - -- `bar.foreground`, `bar.background`, `bar.urgent` — theme colors (live-updated) -- `bar.fontFamily` — current monospace family -- `bar.position` — `"top" | "bottom" | "left" | "right"` -- `bar.vertical` — boolean shortcut -- `bar.barSize` — 26 horizontal / 28 vertical -- `bar.run(command)` — fire-and-forget bash exec -- `bar.shellQuote(value)` — safe shell-quote a string -- `bar.showTooltip(target, text)` / `bar.hideTooltip(target)` — shared tooltip popup -- `bar.requestPopout(owner)` / `bar.releasePopout(owner)` — one-popup-at-a-time coordinator - -First-party widgets live in `widgets/.qml` and are picked up by the -shell's `BarWidgetRegistry` at startup; reference one by `id` in any -layout list. - -Third-party widgets ship as separate plugins under -`~/.config/omarchy/plugins//` with their own `manifest.json` -declaring `kinds: ["bar-widget"]` and a `barWidget` entry point. See -[../../README.md](../../README.md) for the manifest schema. Enable or -rescan third-party plugins with `omarchy-shell-ipc shell setPluginEnabled` -and `omarchy-shell-ipc shell rescanPlugins`. diff --git a/docs/omarchy-shell.md b/docs/omarchy-shell.md new file mode 100644 index 00000000..a0ea4e6f --- /dev/null +++ b/docs/omarchy-shell.md @@ -0,0 +1,125 @@ +# omarchy-shell + +A single long-running [Quickshell](https://quickshell.org/) instance +that hosts the Omarchy desktop. The bar, panels, overlays, menus, and +services all run inside as plugins. IPC is the canonical way for CLIs +to talk to a running shell — `omarchy-shell-ipc` auto-starts it on +first call. + +## Plugin manifest + +```json +{ + "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" } +} +``` + +`kinds` (a manifest may declare more than one): + +| Kind | What it is | +|--------------|---------------------------------------------| +| `bar-widget` | Component the bar drops into a section | +| `panel` | Floating window (e.g. bar settings) | +| `overlay` | Fullscreen overlay (e.g. background picker) | +| `menu` | Summoned menu surface | +| `service` | Headless singleton, no UI | + +`activation` is `persistent` (loaded at startup) or `on-demand` +(loaded by `shell summon`, unloaded by `shell hide`). On-demand +plugins can set `keepLoaded: true` to survive between summons. + +Full schema: `services/PluginRegistry.qml`. + +## Installing a third-party plugin + +1. Drop into `~/.config/omarchy/plugins//` with a `manifest.json` + plus the QML referenced from `entryPoints`. +2. `omarchy-shell-ipc shell rescanPlugins` +3. `omarchy-shell-ipc shell setPluginEnabled true` +4. Bar widgets also need adding to a section via bar settings. + +## IPC + +The shell exposes a `shell` target plus extra targets registered by +individual plugins (`bar`, `image-selector`, …). + +| Method | Effect | +|---------------------------------------|---------------------------------| +| `ping` | health check | +| `summon ` | load + open a plugin | +| `hide ` | close a previously-summoned | +| `toggle ` | summon if closed, hide if open | +| `rescanPlugins` | re-walk plugin dirs | +| `setPluginEnabled <"true"\|…>` | flip enabled bit | +| `listPlugins` | JSON of every discovered plugin | + +`setPluginEnabled` takes a string; only literal `"true"` enables. + +## shell.json + +```json +{ + "version": 1, + "bar": { + "position": "top", + "transparent": false, + "centerAnchor": "calendar", + "fontFamily": "JetBrainsMono Nerd Font", + "layout": { + "left": [ { "id": "omarchy" } ], + "center": [ { "id": "calendar", "format": "HH:mm" } ], + "right": [ { "id": "audioPanel" } ] + } + }, + "plugins": [ + { "id": "omarchy.settings" } + ] +} +``` + +Rules: + +1. Every plugin instance is one entry — `bar.layout.
` for + bar widgets, `plugins[]` for everything else. +2. Settings are inline on the entry. No `config:` sub-object, no + merge layers. +3. Enabled ⇔ present. +4. `allowMultiple: true` in the manifest permits multiple instances. +5. `version: 1` is required. + +`shell-defaults.json` describes the fresh-install state. When no +user `shell.json` exists, defaults are used verbatim. Once the user +customizes, `shell.json` is canonical — there is no deep-merge. + +## Custom bar modules + +If a full plugin is overkill, declare a one-off module inline in +`bar.layout.
`: + +```json +{ "id": "vpn", "type": "command", "exec": "~/.config/omarchy/bar/scripts/vpn-status", + "interval": 5, "tooltip": "VPN", "onClick": "nm-connection-editor" } +``` + +Output is plain text or Waybar-style JSON (`{ "text": ..., "tooltip": ..., "class": ... }`). + +For a custom QML widget: + +```json +{ "id": "gpu", "type": "qml" } +``` + +Then `~/.config/omarchy/bar/modules/gpu.qml` (or set `source` to point +elsewhere). The module is an `Item` and receives `bar`, `moduleName`, +`settings` properties. `bar` exposes `foreground` / `background` / +`urgent` / `fontFamily` / `position` / `vertical` / `barSize`, plus +`run(cmd)`, `shellQuote(v)`, `showTooltip(t, s)` / `hideTooltip(t)`, +`requestPopout(o)` / `releasePopout(o)`.