From 8af8bf2e2e7876df9f933b6e4821276e162dbe3c Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 17 Sep 2025 10:19:55 -0400 Subject: [PATCH 1/2] BarService: to keep tracks of bar widgets and improve IPC behavior. --- Modules/Bar/Bar.qml | 13 +- .../Bar/Extras/BarWidgetLoader.qml | 26 ++- Modules/Bar/Widgets/Battery.qml | 2 +- Modules/Bar/Widgets/Brightness.qml | 2 +- Modules/Bar/Widgets/CustomButton.qml | 2 +- Modules/Bar/Widgets/KeyboardLayout.qml | 2 +- Modules/Bar/Widgets/Microphone.qml | 2 +- Modules/Bar/Widgets/Volume.qml | 2 +- Modules/IPC/IPCManager.qml | 6 +- Services/BarService.qml | 172 ++++++++++++++++++ Services/BarWidgetRegistry.qml | 20 -- 11 files changed, 209 insertions(+), 40 deletions(-) rename Widgets/NWidgetLoader.qml => Modules/Bar/Extras/BarWidgetLoader.qml (55%) create mode 100644 Services/BarService.qml diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index c98798f6..04429d90 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -8,6 +8,7 @@ import qs.Commons import qs.Services import qs.Widgets import qs.Modules.Notification +import qs.Modules.Bar.Extras Variants { model: Quickshell.screens @@ -88,7 +89,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.left - delegate: NWidgetLoader { + delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, @@ -111,7 +112,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.center - delegate: NWidgetLoader { + delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, @@ -135,7 +136,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.right - delegate: NWidgetLoader { + delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, @@ -169,7 +170,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.left - delegate: NWidgetLoader { + delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, @@ -194,7 +195,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.center - delegate: NWidgetLoader { + delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, @@ -220,7 +221,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.right - delegate: NWidgetLoader { + delegate: BarWidgetLoader { widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, diff --git a/Widgets/NWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml similarity index 55% rename from Widgets/NWidgetLoader.qml rename to Modules/Bar/Extras/BarWidgetLoader.qml index ac4c5420..3aa5911f 100644 --- a/Widgets/NWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -8,12 +8,14 @@ Item { property string widgetId: "" property var widgetProps: ({}) - property bool enabled: true + property string screenName: widgetProps.screen ? widgetProps.screen.name : "" + property string section: widgetProps.section || "" + property int sectionIndex: widgetProps.sectionWidgetIndex || 0 Connections { target: ScalingService - function onScaleChanged(screenName, scale) { - if (loader.item && loader.item.screen && screenName === loader.item.screen.name) { + function onScaleChanged(aScreenName, scale) { + if (loader.item && loader.item.screen && aScreenName === screenName) { loader.item['scaling'] = scale } } @@ -27,7 +29,7 @@ Item { id: loader anchors.fill: parent - active: Settings.isLoaded && enabled && widgetId !== "" + active: Settings.isLoaded && widgetId !== "" sourceComponent: { if (!active) { return null @@ -45,18 +47,30 @@ Item { } } + // Register this widget instance with BarService + if (screenName && section) { + BarService.registerWidget(screenName, section, widgetId, sectionIndex, item) + } + if (item.hasOwnProperty("onLoaded")) { item.onLoaded() } - Logger.log("NWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name) + //Logger.log("BarWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name) + } + + Component.onDestruction: { + // Unregister when destroyed + if (screenName && section) { + BarService.unregisterWidget(screenName, section, widgetId, sectionIndex) + } } } // Error handling onWidgetIdChanged: { if (widgetId && !BarWidgetRegistry.hasWidget(widgetId)) { - Logger.warn("WidgetLoader", "Widget not found in registry:", widgetId) + Logger.warn("BarWidgetLoader", "Widget not found in bar registry:", widgetId) } } } diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index 2b5ec67d..1d2d3f7e 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -86,7 +86,7 @@ Item { id: pill compact: (Settings.data.bar.density === "compact") - rightOpen: BarWidgetRegistry.getPillDirection(root) + rightOpen: BarService.getPillDirection(root) icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, charging, isReady) text: (isReady || testMode) ? Math.round(percent) : "-" suffix: "%" diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index c588b1db..e81ef351 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -78,7 +78,7 @@ Item { id: pill compact: (Settings.data.bar.density === "compact") - rightOpen: BarWidgetRegistry.getPillDirection(root) + rightOpen: BarService.getPillDirection(root) icon: getIcon() autoHide: false // Important to be false so we can hover as long as we want text: { diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 496c6707..4dc5a8a0 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -47,7 +47,7 @@ Item { BarPill { id: pill - rightOpen: BarWidgetRegistry.getPillDirection(root) + rightOpen: BarService.getPillDirection(root) icon: customIcon text: _dynamicText compact: (Settings.data.bar.density === "compact") diff --git a/Modules/Bar/Widgets/KeyboardLayout.qml b/Modules/Bar/Widgets/KeyboardLayout.qml index 13b4dac4..4ea1400a 100644 --- a/Modules/Bar/Widgets/KeyboardLayout.qml +++ b/Modules/Bar/Widgets/KeyboardLayout.qml @@ -44,7 +44,7 @@ Item { anchors.verticalCenter: parent.verticalCenter compact: (Settings.data.bar.density === "compact") - rightOpen: BarWidgetRegistry.getPillDirection(root) + rightOpen: BarService.getPillDirection(root) icon: "keyboard" autoHide: false // Important to be false so we can hover as long as we want text: currentLayout.toUpperCase() diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index 75044141..a11371f7 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -89,7 +89,7 @@ Item { BarPill { id: pill - rightOpen: BarWidgetRegistry.getPillDirection(root) + rightOpen: BarService.getPillDirection(root) icon: getIcon() compact: (Settings.data.bar.density === "compact") autoHide: false // Important to be false so we can hover as long as we want diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index bcacd7ff..c473d203 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -76,7 +76,7 @@ Item { id: pill compact: (Settings.data.bar.density === "compact") - rightOpen: BarWidgetRegistry.getPillDirection(root) + rightOpen: BarService.getPillDirection(root) icon: getIcon() autoHide: false // Important to be false so we can hover as long as we want text: Math.floor(AudioService.volume * 100) diff --git a/Modules/IPC/IPCManager.qml b/Modules/IPC/IPCManager.qml index e9bfac58..be88ff0c 100644 --- a/Modules/IPC/IPCManager.qml +++ b/Modules/IPC/IPCManager.qml @@ -27,7 +27,8 @@ Item { IpcHandler { target: "notifications" function toggleHistory() { - notificationHistoryPanel.toggle() + // Will attempt to open the panel next to the bar button if any. + notificationHistoryPanel.toggle(BarService.lookupWidget("NotificationHistory")) } function toggleDND() { Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb @@ -118,7 +119,8 @@ Item { IpcHandler { target: "sidePanel" function toggle() { - sidePanel.toggle() + // Will attempt to open the panel next to the bar button if any. + sidePanel.toggle(BarService.lookupWidget("SidePanelToggle")) } } diff --git a/Services/BarService.qml b/Services/BarService.qml new file mode 100644 index 00000000..6444d1bb --- /dev/null +++ b/Services/BarService.qml @@ -0,0 +1,172 @@ +pragma Singleton + +import Quickshell +import qs.Commons + +Singleton { + id: root + + // Registry to store actual widget instances + // Key format: "screenName|section|widgetId|index" + property var widgetInstances: ({}) + + // Register a widget instance + function registerWidget(screenName, section, widgetId, index, instance) { + const key = [screenName, section, widgetId, index].join("|") + widgetInstances[key] = { + "key": key, + "screenName": screenName, + "section": section, + "widgetId": widgetId, + "index": index, + "instance": instance + } + Logger.log("BarService", "Registered widget:", key) + } + + // Unregister a widget instance + function unregisterWidget(screenName, section, widgetId, index) { + const key = [screenName, section, widgetId, index].join("|") + delete widgetInstances[key] + Logger.log("BarService", "Unregistered widget:", key) + } + + // Lookup a specific widget instance (returns the actual QML instance) + function lookupWidget(widgetId, screenName = null, section = null) { + // If looking for a specific instance + if (screenName && section !== null) { + for (var key in widgetInstances) { + var widget = widgetInstances[key] + if (widget.widgetId === widgetId && widget.screenName === screenName && widget.section === section) { + return widget.instance + } + } + } + + // Return first match if no specific screen/section specified + for (var key in widgetInstances) { + var widget = widgetInstances[key] + if (widget.widgetId === widgetId) { + if (!screenName || widget.screenName === screenName) { + if (section === null || widget.section === section) { + return widget.instance + } + } + } + } + + return undefined + } + + // Get all instances of a widget type + function getAllWidgetInstances(widgetId = null, screenName = null, section = null) { + var instances = [] + + for (var key in widgetInstances) { + var widget = widgetInstances[key] + + var matches = true + if (widgetId && widget.widgetId !== widgetId) + matches = false + if (screenName && widget.screenName !== screenName) + matches = false + if (section !== null && widget.section !== section) + matches = false + + if (matches) { + instances.push(widget.instance) + } + } + + return instances + } + + // Get widget with full metadata + function getWidgetWithMetadata(widgetId, screenName = null, section = null) { + for (var key in widgetInstances) { + var widget = widgetInstances[key] + if (widget.widgetId === widgetId) { + if (!screenName || widget.screenName === screenName) { + if (section === null || widget.section === section) { + return widget + } + } + } + } + return undefined + } + + // Get all widgets in a specific section + function getWidgetsBySection(section, screenName = null) { + var widgets = [] + + for (var key in widgetInstances) { + var widget = widgetInstances[key] + if (widget.section === section) { + if (!screenName || widget.screenName === screenName) { + widgets.push(widget.instance) + } + } + } + + // Sort by index to maintain order + widgets.sort(function (a, b) { + var aWidget = getWidgetWithMetadata(a.widgetId, a.screen?.name, a.section) + var bWidget = getWidgetWithMetadata(b.widgetId, b.screen?.name, b.section) + return (aWidget?.index || 0) - (bWidget?.index || 0) + }) + + return widgets + } + + // Get all registered widgets (for debugging) + function getAllRegisteredWidgets() { + var result = [] + for (var key in widgetInstances) { + result.push({ + "key": key, + "widgetId": widgetInstances[key].widgetId, + "section": widgetInstances[key].section, + "screenName": widgetInstances[key].screenName, + "index": widgetInstances[key].index + }) + } + return result + } + + // Check if a widget type exists in a section + function hasWidget(widgetId, section = null, screenName = null) { + for (var key in widgetInstances) { + var widget = widgetInstances[key] + if (widget.widgetId === widgetId) { + if (section === null || widget.section === section) { + if (!screenName || widget.screenName === screenName) { + return true + } + } + } + } + return false + } + + // Get pill direction for a widget instance + function getPillDirection(widgetInstance) { + try { + if (widgetInstance.section === "left") { + return true + } else if (widgetInstance.section === "right") { + return false + } else { + // middle section + if (widgetInstance.sectionWidgetIndex < widgetInstance.sectionWidgetsCount / 2) { + return false + } else { + return true + } + } + } catch (e) { + Logger.error(e) + } + return false + } +} diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index f12a94ce..4b1ea12b 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -206,24 +206,4 @@ Singleton { function widgetHasUserSettings(id) { return (widgetMetadata[id] !== undefined) && (widgetMetadata[id].allowUserSettings === true) } - - function getPillDirection(widget) { - try { - if (widget.section === "left") { - return true - } else if (widget.section === "right") { - return false - } else { - // middle section - if (widget.sectionWidgetIndex < widget.sectionWidgetsCount / 2) { - return false - } else { - return true - } - } - } catch (e) { - Logger.error(e) - } - return false - } } From 1305efec244e6998fd852cbc2828cb40d6c51a9a Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 17 Sep 2025 15:38:25 -0400 Subject: [PATCH 2/2] Settings/Notification: fixed typo --- Modules/SettingsPanel/Tabs/NotificationTab.qml | 2 +- Services/BarService.qml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/SettingsPanel/Tabs/NotificationTab.qml b/Modules/SettingsPanel/Tabs/NotificationTab.qml index 7d7a7f27..363f5350 100644 --- a/Modules/SettingsPanel/Tabs/NotificationTab.qml +++ b/Modules/SettingsPanel/Tabs/NotificationTab.qml @@ -133,7 +133,7 @@ ColumnLayout { NHeader { label: "Monitors Configuration" - description: "Show bar on specific monitors. Defaults to all if none are chosen." + description: "Show notification on specific monitors. Defaults to all if none are chosen." } Repeater { diff --git a/Services/BarService.qml b/Services/BarService.qml index 6444d1bb..c88dcdd1 100644 --- a/Services/BarService.qml +++ b/Services/BarService.qml @@ -9,7 +9,6 @@ Singleton { // Registry to store actual widget instances // Key format: "screenName|section|widgetId|index" property var widgetInstances: ({}) - // Register a widget instance function registerWidget(screenName, section, widgetId, index, instance) { const key = [screenName, section, widgetId, index].join("|")