Polish docs and harden plugin loading

This commit is contained in:
Ryan Hughes
2026-05-14 02:21:48 -04:00
parent 714e8a453b
commit 4e8bf99240
12 changed files with 414 additions and 39 deletions
@@ -0,0 +1,48 @@
# 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/<plugin-id>/` rather than in this directory.
| Plugin | id | kinds | activation | entry point |
|-----------------------|-------------------------------|--------------|-------------|----------------------------|
| Bar | `omarchy.bar` | `bar` | persistent | `bar/Bar.qml` |
| Bar settings | `omarchy.bar-settings` | `panel` | on-demand | `bar-settings/BarSettingsPanel.qml` |
| Background switcher | `omarchy.background-switcher` | `overlay` | on-demand | `background-switcher/BackgroundSwitcher.qml` |
## Bar
The status bar. Mounted at startup, lives forever. Layout is configured
through `~/.config/omarchy/bar.json` (deep-merged over
[`bar/bar-defaults.json`](bar/bar-defaults.json)). 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.bar-settings "{}"` (which is what
`omarchy launch bar-settings` ultimately calls). Provides:
- per-section add/move/remove/edit of widget entries
- a Plugin Manager tab for enabling/disabling third-party plugins
- a dynamic settings form driven by each widget's manifest schema
## Background switcher
Fullscreen wallpaper / image picker overlay. Summoned for ad-hoc wallpaper
selection. Keeps its legacy unix socket protocol at
`/run/user/<uid>/omarchy-image-selector.sock` so existing callers like
`omarchy-menu-images` keep working without any wire-format change. The
plugin has `keepLoaded: true` so the socket survives between summons.
## Coming soon
- `omarchy.menu` — folds the existing `omarchy-menu` (currently on another
branch) into the shell as a `menu` plugin. Not present on this branch.
- `omarchy.theme-switcher` — folds theme switching into the shell.
@@ -5,9 +5,19 @@ import QtQuick
import QtQuick.Effects
import QtQuick.Shapes
ShellRoot {
Item {
id: root
// Injected by omarchy-shell. Optional here — the picker doesn't need
// omarchyPath itself, but every plugin gets it so user-installed scripts
// referenced by other plugins can stay path-portable.
property string omarchyPath: ""
// Set by omarchy-shell when summoning the overlay; not currently consumed but
// declared so the host's onLoaded injection doesn't trip a missing-property
// warning.
property var shell: null
property var manifest: null
property string imageDirs: Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIRS") || Quickshell.env("OMARCHY_IMAGE_SELECTOR_DIR") || Quickshell.env("OMARCHY_STOCK_BACKGROUNDS_DIR") || (Quickshell.env("HOME") + "/.config/omarchy/current/theme/backgrounds")
property string imageRows: ""
property string selectionFile: Quickshell.env("OMARCHY_IMAGE_SELECTOR_SELECTION_FILE") || Quickshell.env("OMARCHY_BACKGROUND_SELECTION_FILE")
@@ -293,9 +303,35 @@ ShellRoot {
}
}
Component.onCompleted: {
if (selectionFile)
openSelector(imageDirs, "", selectedImage, selectionFile, Quickshell.env("OMARCHY_IMAGE_SELECTOR_DONE_FILE"), colorsFile, "", false, false)
// The plugin is keep-loaded inside omarchy-shell, so the env-driven
// auto-open path that the standalone background-switcher.qml used would
// now fire once at shell startup with stale env. The legacy callers
// (omarchy-menu-images) deliver their request over the unix socket
// declared below; modern callers go through `shell summon` -> open(payload).
// Lifecycle hooks invoked by omarchy-shell summon/hide. The legacy entry
// point remains the unix socket below — callers that already have a
// selection_file/done_file flow keep using it. summon() with no payload
// simply opens the picker against the user's current theme backgrounds.
function open(payload) {
var args = {}
if (payload) {
try { args = JSON.parse(payload) || {} } catch (e) { args = {} }
}
var dirs = String(args.imageDirs || imageDirs)
var rows = String(args.imageRows || "")
var sel = String(args.selectedImage || selectedImage)
var selFile = String(args.selectionFile || "")
var doneF = String(args.doneFile || "")
var colors = String(args.colorsFile || colorsFile)
var colorsRaw = String(args.colorsRaw || "")
var labels = args.showLabels === true || args.showLabels === "true"
var filter = args.filterable === true || args.filterable === "true"
openSelector(dirs, rows, sel, selFile, doneF, colors, colorsRaw, labels, filter)
}
function close() {
cancel()
}
IpcHandler {
@@ -9,6 +9,12 @@ import "../../ui/settings" as SettingsUi
Item {
id: root
// Plugin lifecycle hooks. omarchy-shell calls open(payloadJson) on summon
// and close() on hide. We don't consume payloads yet, and visibility is
// driven by the host Loader's `active`, so both are no-ops for now.
function open(payloadJson) { /* no payload schema yet; reserved for future use */ }
function close() { /* visibility handled by parent Loader; nothing to clean up */ }
// Injected by the host shell when the panel is summoned. Shared instances
// so the panel sees the same registry state the bar wrote into.
property var barWidgetRegistry: null
@@ -342,12 +348,16 @@ Item {
return result
}
Component.onCompleted: {
// Only log once the registry has actually been injected by the host. The
// raw Component.onCompleted fires before the Loader's onLoaded property
// injection so it would always print `(null)` widgets, which is noise.
onBarWidgetRegistryChanged: {
if (!root.barWidgetRegistry) return
console.log("bar-settings open. omarchyPath=" + root.omarchyPath,
"defaultsPath=" + root.defaultsPath,
"userConfigPath=" + root.userConfigPath,
"registry has",
root.barWidgetRegistry ? root.barWidgetRegistry.availableIds().length : "(null)",
root.barWidgetRegistry.availableIds().length,
"widgets")
}
@@ -1251,12 +1261,33 @@ Item {
Item { width: 8; height: 1 }
Switch {
checked: row.pluginEnabled
enabled: !row.firstParty
opacity: row.firstParty ? 0.5 : 1
Item {
implicitWidth: enabledSwitch.implicitWidth
implicitHeight: enabledSwitch.implicitHeight
anchors.verticalCenter: parent.verticalCenter
onToggled: root.pluginRegistry.setEnabled(row.pluginId, checked)
Switch {
id: enabledSwitch
checked: row.pluginEnabled
enabled: !row.firstParty
opacity: row.firstParty ? 0.45 : 1
ToolTip.visible: row.firstParty && hoverArea.containsMouse
ToolTip.delay: 300
ToolTip.text: "First-party plugin — always enabled"
onToggled: root.pluginRegistry.setEnabled(row.pluginId, checked)
}
// Switch.enabled=false also disables mouse tracking, so the tooltip
// never sees a hover event. Layer a transparent hover-only MouseArea
// on top to surface the explanation.
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
visible: row.firstParty
cursorShape: Qt.ForbiddenCursor
}
}
}
@@ -714,7 +714,7 @@ Item {
}
FileView {
path: root.omarchyPath + "/default/quickshell/bar/bar-defaults.json"
path: root.omarchyPath + "/default/quickshell/omarchy-shell/plugins/bar/bar-defaults.json"
watchChanges: true
printErrors: false
onLoaded: root.loadDefaultBarConfig(text())
@@ -1,7 +1,11 @@
# Omarchy bar
This is the Quickshell implementation of the Omarchy status 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.
- `bar-defaults.json` is the Omarchy-owned default layout and module settings.
- `widgets/` holds first-party widgets — modular, interactive components shipped with Omarchy.
@@ -156,4 +160,13 @@ Widgets receive `bar` (the shell root), `moduleName` (string), and `settings` (o
- `bar.showTooltip(target, text)` / `bar.hideTooltip(target)` — shared tooltip popup
- `bar.requestPopout(owner)` / `bar.releasePopout(owner)` — one-popup-at-a-time coordinator
Drop new widgets into `widgets/<name>.qml`, add the name to the `firstPartyWidgets` registry in `shell.qml`, and reference it by name in any layout list.
First-party widgets live in `widgets/<name>.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/<plugin-id>/` with their own `manifest.json`
declaring `kinds: ["bar-widget"]` and a `barWidget` entry point. See
[../../README.md](../../README.md) for the manifest schema and the
Plugin Manager tab in `omarchy launch bar-settings` for enable/disable
controls.