Files
arthur-os/shell
2026-05-19 15:34:19 -04:00
..
2026-05-19 15:34:19 -04:00
2026-05-19 15:34:19 -04:00
2026-05-19 20:54:20 +02:00
2026-05-19 10:50:36 +02:00
2026-05-19 15:16:10 -04:00
2026-05-19 20:54:20 +02:00
2026-05-19 12:02:28 +02:00
2026-05-19 20:54:20 +02:00

Omarchy shell

omarchy-shell is a single long-running Quickshell instance that hosts the Omarchy desktop. Hyprland autostart launches one shell per graphical 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:

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/
    app-launcher/
    image-picker/
    menu/
    notifications/
    osd/
    polkit/

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"],
  "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-widgets; they do not replace the host bar.

Panels, overlays, and menus are loaded when summoned. Plugins that need to outlive a single summon can set keepLoaded: true (e.g. the image picker keeps its overlay window mounted between summons). First-party services are loaded at startup.

The full schema lives in services/PluginRegistry.qml.

Installing a third-party plugin

  1. Drop the plugin into ~/.config/omarchy/plugins/<plugin-id>/. The directory must contain a manifest.json plus the QML files referenced from its entryPoints.
  2. omarchy-shell shell rescanPlugins.
  3. Enable the plugin with omarchy-shell shell setPluginEnabled <id> true.
  4. If it's a bar-widget, add it to a layout section from the bar editor.

First-party plugins under 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/shell call shell ping

Hyprland autostart launches the shell directly with quickshell -p $OMARCHY_PATH/shell. Use omarchy-restart-shell (quickshell reload) to reload the long-running shell process.

A convenience wrapper, omarchy-shell, forwards IPC calls to the running shell. It does not start the shell.

omarchy-shell shell ping
omarchy-shell shell summon omarchy.settings "{}"
omarchy-shell shell listPlugins
omarchy-shell 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 bar to defaults in omarchy launch bar settings rewrites the bar subtree from the current shell-defaults.json.

shell.json shape

{
  "version": 1,
  "idle": {
    "screensaver": 150,
    "lock": 300
  },
  "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": []
}

Storage rules

  1. Every plugin instance is one entry. Either in bar.layout.<section> 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. Third-party enabled ⇔ present. A third-party 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. First-party plugins are always enabled.
  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. Idle timings are top-level. idle.screensaver and idle.lock are seconds since user idle began, so the default lock fires at 300s even if the 150s screensaver starts first.
  6. 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.