From 24f5e9a7e686ba4d730d53591652f941816b30e8 Mon Sep 17 00:00:00 2001 From: bbedward Date: Wed, 15 Oct 2025 16:08:24 -0400 Subject: [PATCH] plugins: add PluginGlobalVar - Allow syncing vars to all widget instances, like a singleton --- PLUGINS/README.md | 120 ++++++++++++++++++++++++++++++++++++ Services/PluginService.qml | 20 ++++++ Widgets/PluginGlobalVar.qml | 30 +++++++++ 3 files changed, 170 insertions(+) create mode 100644 Widgets/PluginGlobalVar.qml diff --git a/PLUGINS/README.md b/PLUGINS/README.md index f0f77adc..c3f9e575 100644 --- a/PLUGINS/README.md +++ b/PLUGINS/README.md @@ -561,6 +561,10 @@ PluginService.getWidgetComponents(): object // Data Persistence PluginService.savePluginData(pluginId: string, key: string, value: any): bool PluginService.loadPluginData(pluginId: string, key: string, defaultValue: any): any + +// Global Variables - Shared state across all plugin instances +PluginService.getGlobalVar(pluginId: string, varName: string, defaultValue: any): any +PluginService.setGlobalVar(pluginId: string, varName: string, value: any): void ``` ### Signals @@ -569,8 +573,124 @@ PluginService.loadPluginData(pluginId: string, key: string, defaultValue: any): PluginService.pluginLoaded(pluginId: string) PluginService.pluginUnloaded(pluginId: string) PluginService.pluginLoadFailed(pluginId: string, error: string) +PluginService.globalVarChanged(pluginId: string, varName: string) ``` +## Plugin Global Variables + +Plugins can share state across multiple instances using global variables. This is useful when you have the same widget displayed on multiple monitors or multiple instances of the same widget on different bars. + +### Why Use Global Variables? + +Unlike regular properties which are scoped to each component instance, global variables are synchronized across all instances of your plugin. This enables: + +- **Multi-monitor consistency**: Same data displayed across all monitors +- **Multi-instance widgets**: Multiple instances of the same widget sharing state +- **Cross-component communication**: Share data between widget and settings components + +### Using PluginGlobalVar + +The `PluginGlobalVar` helper component provides reactive global variable access: + +```qml +import QtQuick +import qs.Widgets +import qs.Modules.Plugins + +PluginComponent { + PluginGlobalVar { + id: globalCounter + varName: "counter" + defaultValue: 0 + } + + horizontalBarPill: Component { + StyledRect { + width: content.implicitWidth + Theme.spacingM * 2 + height: parent.widgetThickness + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + + StyledText { + id: content + anchors.centerIn: parent + text: "Count: " + globalCounter.value + color: Theme.surfaceText + } + + MouseArea { + anchors.fill: parent + onClicked: globalCounter.set(globalCounter.value + 1) + } + } + } +} +``` + +**PluginGlobalVar Properties:** +- `varName` (required): Name of the global variable +- `defaultValue` (optional): Default value if not set +- `value` (readonly): Current value of the global variable + +**PluginGlobalVar Methods:** +- `set(newValue)`: Update the global variable (triggers reactivity across all instances) + +### Using PluginService API Directly + +For more control, use the PluginService API directly: + +```qml +import QtQuick +import qs.Services +import qs.Modules.Plugins + +PluginComponent { + property int counter: PluginService.getGlobalVar("myPlugin", "counter", 0) + + Connections { + target: PluginService + function onGlobalVarChanged(pluginId, varName) { + if (pluginId === "myPlugin" && varName === "counter") { + counter = PluginService.getGlobalVar("myPlugin", "counter", 0) + } + } + } + + horizontalBarPill: Component { + StyledRect { + MouseArea { + anchors.fill: parent + onClicked: { + const current = PluginService.getGlobalVar("myPlugin", "counter", 0) + PluginService.setGlobalVar("myPlugin", "counter", current + 1) + } + } + } + } +} +``` + +### Global Variables vs Settings + +**Global Variables** (`getGlobalVar`/`setGlobalVar`): +- Runtime state only (not persisted to disk) +- Synchronized across all plugin instances +- Changes trigger `globalVarChanged` signal for reactivity +- Use for: counters, current selection, temporary UI state + +**Settings** (`savePluginData`/`loadPluginData`): +- Persisted to `settings.json` across sessions +- Loaded once per plugin instance +- Use for: user preferences, API keys, configuration + +### Important Notes + +1. **Reactivity**: Global variables are reactive - all instances update when a value changes +2. **Namespacing**: Variables are namespaced by plugin ID to avoid conflicts +3. **Type Safety**: Values can be any QML/JavaScript type (numbers, strings, objects, arrays) +4. **Not Persistent**: Global variables are cleared when the shell restarts (use settings for persistence) +5. **Performance**: Efficient for frequent updates - changes only trigger updates for the specific variable + ## Creating a Plugin ### Step 1: Create Plugin Directory diff --git a/Services/PluginService.qml b/Services/PluginService.qml index 56e11bc5..596bf145 100644 --- a/Services/PluginService.qml +++ b/Services/PluginService.qml @@ -30,12 +30,14 @@ Singleton { property var knownManifests: ({}) property var pathToPluginId: ({}) property var pluginInstances: ({}) + property var globalVars: ({}) signal pluginLoaded(string pluginId) signal pluginUnloaded(string pluginId) signal pluginLoadFailed(string pluginId, string error) signal pluginDataChanged(string pluginId) signal pluginListUpdated() + signal globalVarChanged(string pluginId, string varName) Timer { id: resyncDebounce @@ -589,4 +591,22 @@ Singleton { } return plugins } + + function getGlobalVar(pluginId, varName, defaultValue) { + if (globalVars[pluginId] && varName in globalVars[pluginId]) { + return globalVars[pluginId][varName] + } + return defaultValue + } + + function setGlobalVar(pluginId, varName, value) { + const newGlobals = Object.assign({}, globalVars) + if (!newGlobals[pluginId]) { + newGlobals[pluginId] = {} + } + newGlobals[pluginId] = Object.assign({}, newGlobals[pluginId]) + newGlobals[pluginId][varName] = value + globalVars = newGlobals + globalVarChanged(pluginId, varName) + } } diff --git a/Widgets/PluginGlobalVar.qml b/Widgets/PluginGlobalVar.qml new file mode 100644 index 00000000..6a95a976 --- /dev/null +++ b/Widgets/PluginGlobalVar.qml @@ -0,0 +1,30 @@ +import QtQuick +import qs.Services + +Item { + id: root + + required property string varName + property var defaultValue: undefined + + readonly property var value: { + const pid = parent?.pluginId ?? "" + if (!pid || !PluginService.globalVars[pid]) { + return defaultValue + } + return PluginService.globalVars[pid][varName] ?? defaultValue + } + + function set(newValue) { + const pid = parent?.pluginId ?? "" + if (pid) { + PluginService.setGlobalVar(pid, varName, newValue) + } else { + console.warn("PluginGlobalVar: Cannot set", varName, "- no pluginId from parent") + } + } + + visible: false + width: 0 + height: 0 +}