How the Quickshell Desktop in Omarchy Works: Architecture and Plugin System

The Quickshell desktop in Omarchy is a modular QML-based compositor centered on shell.qml, which orchestrates a dynamic plugin system for bars, widgets, and panels through a JSON-driven configuration and IPC handlers.

Omarchy’s desktop environment is built on Quickshell, a QML-based compositor designed for flexibility and extensibility. The system revolves around a central ShellRoot defined in shell/shell.qml, which manages global state, discovers plugins, and exposes an IPC bridge for external control.

Core Architecture Components

The architecture follows a registry-based design where functionality is loaded dynamically through manifests rather than hardcoded logic.

ShellRoot and Global State

The entry point shell/shell.qml instantiates a long-running ShellRoot object that maintains global application state. This component reads the user’s shell.json configuration, manages hot-reloads, and routes all IPC calls between external tools and the desktop internals. It holds references to the PluginRegistry, BarWidgetRegistry, and panel loading mechanisms.

PluginRegistry and Discovery

The PluginRegistry (implemented in shell/services/PluginRegistry.qml) handles scanning and validation of both first-party and third-party plugins.

  • Scanning: The rescan() method executes a Bash script that emits manifest files found in $OMARCHY_PATH/shell/plugins and ~/.config/omarchy/plugins.
  • Validation: The parseScanOutput function processes results through validateManifest to ensure manifests contain required fields (id, kinds, entryPoints) before storing them in installedPlugins.
  • State Management: The registry tracks enabled status and provides setEnabled(id, true/false, placement) to modify the active plugin set.

Bar System and Widgets

The visual bar is implemented in shell/plugins/bar/Bar.qml, which renders top or bottom bars and manages widget layout.

  • BarWidgetRegistry: Located at shell/services/BarWidgetRegistry.qml, this service holds concrete QML components for each bar widget and exposes metadata for the settings UI.
  • Layout: Widget placement follows shellConfig.bar.layout.{left,center,right}, defined in the user configuration.
  • Integration: Widgets declare kind "bar-widget" in their manifest; when enabled, the shell creates a Component from entryPoints.barWidget and registers it with the BarWidgetRegistry.

Services and Panel Loading

Service plugins (kind "service") are loaded as singletons into an internal _services map. These run as children of an invisible serviceHost object and are accessible via shell.serviceFor(id), receiving the same injection properties as panels (omarchyPath, shell, manifest).

Panels, overlays, and menus load on-demand through a dynamic system:

  1. Discovery: computePanelEntries() gathers enabled plugins with kinds "panel", "overlay", or "menu".
  2. Instantiation: A QML Instantiator creates delegates containing a Loader (panelLoaders[id]) for each entry.
  3. Activation: Calling shell.summon(id, payload) sets openPanelIds[id], activating the Loader to fetch the QML component from entryPoints.<kind>.
  4. Payload Delivery: If the component loads asynchronously, data queues in pendingPayloads until the open() method becomes available.

IPC Handlers for External Control

Two IpcHandler objects expose a JSON RPC-style API for external manipulation:

  • shell: Provides commands including listPlugins, listShellConfig, toggleBarTransparency, enablePlugin, putBarWidget, moveBarWidget, summon, hide, toggle, and generic call.
  • image-selector: Offers a specialized interface for the image-picker plugin.

These handlers enable the omarchy CLI and other tools to control the desktop without direct QML access.

Configuration Flow

Configuration follows a cascading merge strategy:

  1. Defaults: The system initializes builtinShellConfig with minimal bar layouts and idle settings.
  2. User Config: A FileView loads ~/.config/omarchy/shell.json.
  3. Merging: The applyShellConfig() function prefers the user file only when it contains valid JSON with version: 1; otherwise, it falls back to defaults.
  4. Reactive Updates: Modifying shellConfig triggers onShellConfigChanged, causing the PluginRegistry to re-evaluate enabled plugins and the bar to rebuild if necessary.

Plugin System Implementation

Each plugin ships a manifest.json (or *.manifest.json) defining:

  • Identity: id field for unique identification.
  • Capabilities: kinds array declaring types (bar-widget, service, panel, overlay, menu).
  • Entry Points: entryPoints object mapping kinds to QML file URLs.
  • Metadata: Optional fields like barWidget configuration or keepLoaded flags.

First-party plugins reside in $OMARCHY_PATH/shell/plugins and are implicitly enabled. Third-party plugins require explicit listing in shell.json under plugins[] or exclusion via disabledPlugins[]. The setEnabled() method writes appropriate entries to shell.json, handling bar widget placement through the putBarWidget logic or top-level plugin activation.

Practical Usage Examples

Toggle a Panel from the Terminal

Activate the weather panel using the shell IPC handler:

omarchy launch shell toggle "omarchy.weather" "{}"

This invokes shell.toggle(id, payload), which delegates to internal summon or hide logic based on current visibility state.

Add a Custom Bar Widget

Insert a widget into the right section after the system tray:

PLACEMENT='{"section":"right","after":"omarchy.tray"}'
omarchy launch shell putBarWidget "my.custom.widget" "$PLACEMENT"

The putBarWidget command checks existing placements, validates the widget ID against BarWidgetRegistry, and updates shell.json.bar.layout before triggering a bar reload.

Enable a Third-Party Plugin

Activate a community plugin with default placement:

omarchy launch shell enablePlugin "my.thirdparty.plugin" "{}"

This calls PluginRegistry.setEnabled(id, true, placement), which updates shell.json.plugins[] and triggers the configuration reload sequence via onShellConfigChanged.

Summary

  • Entry Point: shell/shell.qml serves as the ShellRoot, coordinating all desktop functionality.
  • Plugin Architecture: A manifest-driven system using PluginRegistry supports first-party and third-party extensions via JSON configuration.
  • Dynamic Loading: Services load as singletons (shell.serviceFor()), while panels instantiate on-demand through QML Loader objects controlled by summon() and hide() calls.
  • Configuration: User settings in ~/.config/omarchy/shell.json merge with defaults through applyShellConfig(), with reactive updates triggering UI rebuilds.
  • External Control: IPC handlers expose the full API to CLI tools, enabling scriptable desktop management without modifying QML source.

Frequently Asked Questions

What file serves as the main entry point for the Omarchy Quickshell desktop?

The primary entry point is shell/shell.qml, which instantiates the ShellRoot object. This file initializes the PluginRegistry, loads user configuration from shell.json, sets up IPC handlers, and manages the lifecycle of bars, services, and panels.

How does Omarchy distinguish between first-party and third-party plugins?

First-party plugins located in $OMARCHY_PATH/shell/plugins are implicitly enabled by the system. Third-party plugins installed in ~/.config/omarchy/plugins must be explicitly added to the plugins[] array in shell.json or placed via the bar layout configuration. The PluginRegistry.setEnabled() method handles validation and writes the appropriate configuration entries.

Can I programmatically move bar widgets without editing JSON manually?

Yes. Use the putBarWidget or moveBarWidget IPC commands via the omarchy CLI. For example, omarchy launch shell putBarWidget "widget.id" '{"section":"left"}' invokes the registry to update shell.json.bar.layout and triggers an immediate bar rebuild through onShellConfigChanged.

How do panels receive data when summoned via IPC?

When shell.summon(id, payload) is called, the payload parameter passes through to the panel loader. If the QML component is still loading, the data queues in pendingPayloads until the component finishes instantiation and exposes its open() method, ensuring reliable data delivery even for slow-loading plugins.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →