From df1f68617d9cdc18b28ddfa04276e954ce63b29c Mon Sep 17 00:00:00 2001 From: Markus Flatischler Date: Sun, 13 Sep 2026 12:06:47 +0200 Subject: [PATCH] Indicators: load indicators provided by other plugins An items entry of omarchy.indicators may now name a plugin and a QML file inside it: { "id": "Backup", "plugin": "my.org.backup", "source": "Indicator.qml" }. The widget resolves the plugin through the shell registry (__sourceDir) and loads /; the file is a normal BarIndicator and gets the entry's other keys as settings. Entries without "plugin" resolve to ../indicators/.qml as before. Unknown plugins, ".." and absolute paths load nothing. This lets a plugin put its own state (a backup job, a sync client, ...) into the hover-reveal indicator group instead of adding a standalone bar icon. --- shell/plugins/bar/README.md | 9 ++++++ .../bar/widgets/Indicators.manifest.json | 2 +- shell/plugins/bar/widgets/Indicators.qml | 28 ++++++++++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/shell/plugins/bar/README.md b/shell/plugins/bar/README.md index 5741c5f3114..8f1f8cdb409 100644 --- a/shell/plugins/bar/README.md +++ b/shell/plugins/bar/README.md @@ -74,6 +74,15 @@ Example `shell.json` (bar subtree only shown): 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", "Reminder", "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. +Indicators can also come from other installed plugins. An `items` entry may be an object naming the plugin and the QML file inside it: + +```json +{ "id": "omarchy.indicators", + "items": ["Dnd", "NightLight", { "id": "Backup", "plugin": "my.org.backup", "source": "Indicator.qml" }] } +``` + +The file is a normal `BarIndicator`; `source` defaults to `Indicator.qml` and must stay inside the plugin directory. Any other keys of the entry are passed to the indicator as `settings`. + ## 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). diff --git a/shell/plugins/bar/widgets/Indicators.manifest.json b/shell/plugins/bar/widgets/Indicators.manifest.json index 4d8f74204c0..25b79a6097a 100644 --- a/shell/plugins/bar/widgets/Indicators.manifest.json +++ b/shell/plugins/bar/widgets/Indicators.manifest.json @@ -21,7 +21,7 @@ "key": "items", "type": "multiselect", "label": "Indicators", - "description": "Choose which indicators this widget instance should show. Leave empty to show all indicators.", + "description": "Choose which indicators this widget instance should show. Leave empty to show all indicators. In shell.json an entry may also be an object {\"id\", \"plugin\", \"source\"} that loads the indicator QML from another installed plugin.", "noSelectionText": "All indicators", "placeholderText": "Search indicators...", "emptyText": "No indicators", diff --git a/shell/plugins/bar/widgets/Indicators.qml b/shell/plugins/bar/widgets/Indicators.qml index cb35c5a23ce..1b083f48203 100644 --- a/shell/plugins/bar/widgets/Indicators.qml +++ b/shell/plugins/bar/widgets/Indicators.qml @@ -30,6 +30,32 @@ BarWidget { return "" } + // Resolve an items entry to the QML file that implements it. Plain entries + // are first-party indicators in ../indicators/.qml. An entry such as + // { "id": "Backup", "plugin": "my.org.backup", "source": "Indicator.qml" } + // loads (default Indicator.qml) from that installed plugin's + // directory, looked up in the shell's plugin registry, so a plugin can ship + // its own BarIndicator without forking this widget. No "..", no absolute + // paths, and an unknown plugin loads nothing. + function resolveIndicatorSource(entry) { + var id = entryId(entry) + if (!id) return "" + var settings = entrySettings(entry) + var plugin = settings.plugin ? String(settings.plugin) : "" + if (!plugin) return Qt.resolvedUrl("../indicators/" + id + ".qml") + + var registry = bar && bar.shell ? bar.shell.pluginRegistry : null + if (registry) void registry.registryRevision // re-resolve after a rescan + var manifest = registry && registry.installedPlugins ? registry.installedPlugins[plugin] : null + var dir = manifest && manifest.__sourceDir ? String(manifest.__sourceDir).replace(/\/$/, "") : "" + var file = settings.source ? String(settings.source) : "Indicator.qml" + if (!dir || file.indexOf("..") !== -1 || file.charAt(0) === "/") { + console.warn("Indicators: cannot resolve indicator", id, "from plugin", plugin, "source", file) + return "" + } + return Util.fileUrl(dir + "/" + file) + } + function entrySettings(entry) { if (!Util.isPlainObject(entry)) return {} var copy = {} @@ -431,7 +457,7 @@ BarWidget { id: indicatorSource anchors.fill: parent - source: indicatorSlot.indicatorId ? Qt.resolvedUrl("../indicators/" + indicatorSlot.indicatorId + ".qml") : "" + source: root.resolveIndicatorSource(indicatorSlot.entry) onLoaded: { indicatorSlot.injectProps() indicatorSlot.syncActiveState()