From 4835119e48240fb335e6d612f8d43acfc375cd8b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 20 May 2026 18:27:10 +0200 Subject: [PATCH] Move first-party bar widget catalog out of Bar --- shell/plugins/bar/Bar.qml | 68 +----------------------- shell/plugins/bar/FirstPartyWidgets.qml | 69 +++++++++++++++++++++++++ shell/shell.qml | 3 ++ 3 files changed, 73 insertions(+), 67 deletions(-) create mode 100644 shell/plugins/bar/FirstPartyWidgets.qml diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index 83115117..af7401e4 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -298,73 +298,7 @@ Item { return source ? Util.fileUrl(source) : "" } - // First-party modules are registered with the BarWidgetRegistry at startup. - // Each entry maps a module id to its display metadata; the QML source is - // loaded asynchronously via Qt.createComponent. - readonly property var firstPartyWidgetMetadata: ({ - "omarchy": { displayName: "Omarchy menu", description: "Launches the Omarchy menu", category: "Compositor", allowMultiple: false }, - "workspaces": { displayName: "Workspaces", description: "Workspace number indicators", category: "Compositor", allowMultiple: false }, - "media": { displayName: "Media", description: "MPRIS now-playing with playback controls", category: "Media", allowMultiple: false }, - "audioPanel": { displayName: "Audio", description: "Volume slider, output picker, per-app mixer", category: "Audio", allowMultiple: false, sourceDir: "../panels", sourceName: "Audio" }, - "monitorPanel": { displayName: "Display", description: "Brightness slider and laptop display controls", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Monitor" }, - "networkPanel": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Network" }, - "powerPanel": { displayName: "Power", description: "Battery, power profile, and system stats", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Power" }, - "bluetoothPanel": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Bluetooth" }, - "daytime": { displayName: "Daytime", description: "Day/time label with date tooltip", category: "Time", allowMultiple: false, settingsForm: "daytimeSettings" }, - "notificationCenter": { displayName: "Notification center", description: "Recent notifications + DND", category: "Status", allowMultiple: false }, - "systemStats": { displayName: "System stats", description: "CPU icon — hover for graphs, click to open btop", category: "System", allowMultiple: false }, - "weather": { displayName: "Weather", description: "Weather pill with detail popup", category: "Info", allowMultiple: false, settingsForm: "weatherSettings" }, - "microphone": { displayName: "Microphone", description: "Mic input state and mute toggle", category: "Audio", allowMultiple: false }, - "activeWindow": { displayName: "Active window", description: "Title of the focused window", category: "Compositor", allowMultiple: false }, - "keyboardLayout": { displayName: "Keyboard layout", description: "Current xkb layout, click cycles", category: "Compositor", allowMultiple: false }, - "lockKeys": { displayName: "Lock keys", description: "Caps / Num / Scroll lock indicators", category: "System", allowMultiple: false }, - "spacer": { displayName: "Spacer", description: "Configurable blank space", category: "Layout", allowMultiple: true, settingsForm: "spacerSettings" } - }) - - property var registeredFirstPartyComponents: ({}) - - Component.onCompleted: { - registerFirstPartyWidgets() - applyBarConfig() - } - - function registerFirstPartyWidgets() { - var ids = Object.keys(firstPartyWidgetMetadata) - for (var i = 0; i < ids.length; i++) { - var id = ids[i] - if (barWidgetRegistry.has(id)) continue - registerOneFirstPartyWidget(id) - } - } - - function registerOneFirstPartyWidget(id) { - var meta = firstPartyWidgetMetadata[id] || {} - var sourceDir = meta.sourceDir || "widgets" - var sourceName = meta.sourceName || id - var url = Qt.resolvedUrl(sourceDir + "/" + sourceName + ".qml") - var enrichedMeta = { - displayName: meta.displayName || id, - description: meta.description || "", - category: meta.category || "Misc", - allowMultiple: meta.allowMultiple === true, - settingsForm: meta.settingsForm || "", - source: "first-party" - } - var comp = Qt.createComponent(url, Component.Asynchronous) - function finalize() { - if (comp.status === Component.Ready) { - barWidgetRegistry.register(id, comp, enrichedMeta) - var next = ({}) - for (var k in registeredFirstPartyComponents) next[k] = registeredFirstPartyComponents[k] - next[id] = comp - registeredFirstPartyComponents = next - } else if (comp.status === Component.Error) { - console.warn("first-party widget " + id + " failed to load: " + comp.errorString()) - } - } - if (comp.status === Component.Loading) comp.statusChanged.connect(finalize) - else finalize() - } + Component.onCompleted: applyBarConfig() function run(command) { if (!command) return diff --git a/shell/plugins/bar/FirstPartyWidgets.qml b/shell/plugins/bar/FirstPartyWidgets.qml new file mode 100644 index 00000000..348885c7 --- /dev/null +++ b/shell/plugins/bar/FirstPartyWidgets.qml @@ -0,0 +1,69 @@ +import QtQuick + +QtObject { + id: root + + required property var barWidgetRegistry + + readonly property var metadata: ({ + "omarchy": { displayName: "Omarchy menu", description: "Launches the Omarchy menu", category: "Compositor", allowMultiple: false }, + "workspaces": { displayName: "Workspaces", description: "Workspace number indicators", category: "Compositor", allowMultiple: false }, + "media": { displayName: "Media", description: "MPRIS now-playing with playback controls", category: "Media", allowMultiple: false }, + "audioPanel": { displayName: "Audio", description: "Volume slider, output picker, per-app mixer", category: "Audio", allowMultiple: false, sourceDir: "../panels", sourceName: "Audio" }, + "monitorPanel": { displayName: "Display", description: "Brightness slider and laptop display controls", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Monitor" }, + "networkPanel": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Network" }, + "powerPanel": { displayName: "Power", description: "Battery, power profile, and system stats", category: "System", allowMultiple: false, sourceDir: "../panels", sourceName: "Power" }, + "bluetoothPanel": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false, sourceDir: "../panels", sourceName: "Bluetooth" }, + "daytime": { displayName: "Daytime", description: "Day/time label with date tooltip", category: "Time", allowMultiple: false, settingsForm: "daytimeSettings" }, + "notificationCenter": { displayName: "Notification center", description: "Recent notifications + DND", category: "Status", allowMultiple: false }, + "systemStats": { displayName: "System stats", description: "CPU icon — hover for graphs, click to open btop", category: "System", allowMultiple: false }, + "weather": { displayName: "Weather", description: "Weather pill with detail popup", category: "Info", allowMultiple: false, settingsForm: "weatherSettings" }, + "microphone": { displayName: "Microphone", description: "Mic input state and mute toggle", category: "Audio", allowMultiple: false }, + "activeWindow": { displayName: "Active window", description: "Title of the focused window", category: "Compositor", allowMultiple: false }, + "keyboardLayout": { displayName: "Keyboard layout", description: "Current xkb layout, click cycles", category: "Compositor", allowMultiple: false }, + "lockKeys": { displayName: "Lock keys", description: "Caps / Num / Scroll lock indicators", category: "System", allowMultiple: false }, + "spacer": { displayName: "Spacer", description: "Configurable blank space", category: "Layout", allowMultiple: true, settingsForm: "spacerSettings" } + }) + + property var registeredComponents: ({}) + + Component.onCompleted: registerWidgets() + + function registerWidgets() { + var ids = Object.keys(metadata) + for (var i = 0; i < ids.length; i++) { + var id = ids[i] + if (barWidgetRegistry.has(id)) continue + registerOne(id) + } + } + + function registerOne(id) { + var meta = metadata[id] || {} + var sourceDir = meta.sourceDir || "widgets" + var sourceName = meta.sourceName || id + var url = Qt.resolvedUrl(sourceDir + "/" + sourceName + ".qml") + var enrichedMeta = { + displayName: meta.displayName || id, + description: meta.description || "", + category: meta.category || "Misc", + allowMultiple: meta.allowMultiple === true, + settingsForm: meta.settingsForm || "", + source: "first-party" + } + var comp = Qt.createComponent(url, Component.Asynchronous) + function finalize() { + if (comp.status === Component.Ready) { + barWidgetRegistry.register(id, comp, enrichedMeta) + var next = ({}) + for (var k in registeredComponents) next[k] = registeredComponents[k] + next[id] = comp + registeredComponents = next + } else if (comp.status === Component.Error) { + console.warn("first-party widget " + id + " failed to load: " + comp.errorString()) + } + } + if (comp.status === Component.Loading) comp.statusChanged.connect(finalize) + else finalize() + } +} diff --git a/shell/shell.qml b/shell/shell.qml index b71be97b..cf01d683 100644 --- a/shell/shell.qml +++ b/shell/shell.qml @@ -17,6 +17,9 @@ ShellRoot { // own empty copies. property PluginRegistry pluginRegistry: PluginRegistry { } property BarWidgetRegistry barWidgetRegistry: BarWidgetRegistry { } + property FirstPartyWidgets firstPartyWidgets: FirstPartyWidgets { + barWidgetRegistry: shell.barWidgetRegistry + } property string home: Quickshell.env("HOME")