From 0d5c1bb3df07a7da527223e494c8efc41eb0f452 Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 3 Oct 2025 22:55:07 -0400 Subject: [PATCH] Add "daemon" type of plugins --- CLAUDE.md | 68 ++++++++++++++++++- DMSShell.qml | 19 ++++++ Modules/Plugins/PluginPopout.qml | 2 +- Modules/Plugins/StringSetting.qml | 25 +++++-- .../WallpaperWatcherDaemon.qml | 65 ++++++++++++++++++ .../WallpaperWatcherSettings.qml | 24 +++++++ PLUGINS/WallpaperWatcherDaemon/plugin.json | 15 ++++ Services/PluginService.qml | 49 ++++++++++--- 8 files changed, 250 insertions(+), 17 deletions(-) create mode 100644 PLUGINS/WallpaperWatcherDaemon/WallpaperWatcherDaemon.qml create mode 100644 PLUGINS/WallpaperWatcherDaemon/WallpaperWatcherSettings.qml create mode 100644 PLUGINS/WallpaperWatcherDaemon/plugin.json diff --git a/CLAUDE.md b/CLAUDE.md index 7c15550e..6c3889f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -448,7 +448,13 @@ When modifying the shell: ### Creating Plugins -Plugins are external, dynamically-loaded components that extend DankBar functionality. Plugins are stored in `~/.config/DankMaterialShell/plugins/` and have their settings isolated from core DMS settings. +Plugins are external, dynamically-loaded components that extend DankMaterialShell functionality. Plugins are stored in `~/.config/DankMaterialShell/plugins/` and have their settings isolated from core DMS settings. + +**Plugin Types:** +- **Widget plugins** (`"type": "widget"` or omit type field): Display UI components in DankBar +- **Daemon plugins** (`"type": "daemon"`): Run invisibly in the background without UI + +#### Widget Plugins 1. **Create plugin directory**: ```bash @@ -464,6 +470,7 @@ Plugins are external, dynamically-loaded components that extend DankBar function "version": "1.0.0", "author": "Your Name", "icon": "extension", + "type": "widget", "component": "./YourWidget.qml", "settings": "./YourSettings.qml", "permissions": ["settings_read", "settings_write"] @@ -545,6 +552,65 @@ Plugins are external, dynamically-loaded components that extend DankBar function - Toggle plugin to enable - Add plugin ID to DankBar widget list +#### Daemon Plugins + +Daemon plugins run invisibly in the background without any UI components. They're useful for monitoring system events, background tasks, or data synchronization. + +1. **Create plugin directory**: + ```bash + mkdir -p ~/.config/DankMaterialShell/plugins/YourDaemon + ``` + +2. **Create manifest** (`plugin.json`): + ```json + { + "id": "yourDaemon", + "name": "Your Daemon", + "description": "Background daemon description", + "version": "1.0.0", + "author": "Your Name", + "icon": "settings_applications", + "type": "daemon", + "component": "./YourDaemon.qml", + "permissions": ["settings_read", "settings_write"] + } + ``` + +3. **Create daemon component** (`YourDaemon.qml`): + ```qml + import QtQuick + import qs.Common + import qs.Services + + Item { + id: root + + property var pluginService: null + + Connections { + target: SessionData + function onWallpaperPathChanged() { + console.log("Wallpaper changed:", SessionData.wallpaperPath) + if (pluginService) { + pluginService.savePluginData("yourDaemon", "lastEvent", Date.now()) + } + } + } + + Component.onCompleted: { + console.log("Daemon started") + } + } + ``` + +4. **Enable daemon**: + - Open Settings → Plugins + - Click "Scan for Plugins" + - Toggle daemon to enable + - Daemon runs automatically in background + +**Example**: See `PLUGINS/WallpaperWatcherDaemon/` for a complete daemon plugin that monitors wallpaper changes + **Plugin Directory Structure:** ``` ~/.config/DankMaterialShell/ diff --git a/DMSShell.qml b/DMSShell.qml index 87d82eeb..4d68eec8 100644 --- a/DMSShell.qml +++ b/DMSShell.qml @@ -42,6 +42,25 @@ ShellRoot { PluginService.pluginDirectory } + Instantiator { + id: daemonPluginInstantiator + model: Object.keys(PluginService.pluginDaemonComponents) + + delegate: Loader { + id: daemonLoader + property string pluginId: modelData + sourceComponent: PluginService.pluginDaemonComponents[pluginId] + + onLoaded: { + if (item) { + item.pluginService = PluginService + item.pluginId = pluginId + console.log("Daemon plugin loaded:", pluginId) + } + } + } + } + WallpaperBackground {} Lock { diff --git a/Modules/Plugins/PluginPopout.qml b/Modules/Plugins/PluginPopout.qml index e531b917..05ad1517 100644 --- a/Modules/Plugins/PluginPopout.qml +++ b/Modules/Plugins/PluginPopout.qml @@ -19,7 +19,7 @@ DankPopout { } popupWidth: contentWidth - popupHeight: popoutContent.item ? popoutContent.item.implicitHeight : contentHeight + popupHeight: contentHeight screen: triggerScreen shouldBeVisible: false visible: shouldBeVisible diff --git a/Modules/Plugins/StringSetting.qml b/Modules/Plugins/StringSetting.qml index 795a2270..e16f1ed6 100644 --- a/Modules/Plugins/StringSetting.qml +++ b/Modules/Plugins/StringSetting.qml @@ -15,14 +15,25 @@ Column { width: parent.width spacing: Theme.spacingS - Component.onCompleted: { + function loadValue() { const settings = findSettings() - if (settings) { + if (settings && settings.pluginService) { value = settings.loadValue(settingKey, defaultValue) textField.text = value } } + Component.onCompleted: { + loadValue() + } + + onValueChanged: { + const settings = findSettings() + if (settings) { + settings.saveValue(settingKey, value) + } + } + function findSettings() { let item = parent while (item) { @@ -54,11 +65,15 @@ Column { id: textField width: parent.width placeholderText: root.placeholder + onTextEdited: { + root.value = text + } onEditingFinished: { root.value = text - const settings = findSettings() - if (settings) { - settings.saveValue(settingKey, text) + } + onActiveFocusChanged: { + if (!activeFocus) { + root.value = text } } } diff --git a/PLUGINS/WallpaperWatcherDaemon/WallpaperWatcherDaemon.qml b/PLUGINS/WallpaperWatcherDaemon/WallpaperWatcherDaemon.qml new file mode 100644 index 00000000..daa9d97b --- /dev/null +++ b/PLUGINS/WallpaperWatcherDaemon/WallpaperWatcherDaemon.qml @@ -0,0 +1,65 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Common +import qs.Services +import qs.Modules.Plugins + +PluginComponent { + id: root + + property string scriptPath: pluginData.scriptPath || "" + + Connections { + target: SessionData + function onWallpaperPathChanged() { + if (scriptPath) { + var scriptProcess = scriptProcessComponent.createObject(root, { + wallpaperPath: SessionData.wallpaperPath + }) + scriptProcess.running = true + } + } + } + + Component { + id: scriptProcessComponent + + Process { + property string wallpaperPath: "" + + command: [scriptPath, wallpaperPath] + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + console.log("WallpaperWatcherDaemon script output:", text.trim()) + } + } + } + + stderr: StdioCollector { + onStreamFinished: { + if (text.trim()) { + ToastService.showError("Wallpaper Change Script Error", text.trim()) + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + ToastService.showError("Wallpaper Change Script Error", "Script exited with code: " + exitCode) + } + destroy() + } + } + } + + Component.onCompleted: { + console.log("WallpaperWatcherDaemon: Started monitoring wallpaper changes") + } + + Component.onDestruction: { + console.log("WallpaperWatcherDaemon: Stopped monitoring wallpaper changes") + } +} diff --git a/PLUGINS/WallpaperWatcherDaemon/WallpaperWatcherSettings.qml b/PLUGINS/WallpaperWatcherDaemon/WallpaperWatcherSettings.qml new file mode 100644 index 00000000..5f8c2c05 --- /dev/null +++ b/PLUGINS/WallpaperWatcherDaemon/WallpaperWatcherSettings.qml @@ -0,0 +1,24 @@ +import QtQuick +import qs.Common +import qs.Widgets +import qs.Modules.Plugins + +PluginSettings { + id: root + pluginId: "wallpaperWatcherDaemon" + + StyledText { + text: "Wallpaper Change Hook" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + } + + StringSetting { + settingKey: "scriptPath" + label: "Script Path" + description: "Path to a script that will be executed when the wallpaper changes. The new wallpaper path will be passed as the first argument." + placeholder: "/path/to/your/script.sh" + defaultValue: "" + } +} diff --git a/PLUGINS/WallpaperWatcherDaemon/plugin.json b/PLUGINS/WallpaperWatcherDaemon/plugin.json new file mode 100644 index 00000000..0a46e76b --- /dev/null +++ b/PLUGINS/WallpaperWatcherDaemon/plugin.json @@ -0,0 +1,15 @@ +{ + "id": "wallpaperWatcherDaemon", + "name": "Wallpaper Watcher Daemon", + "description": "Background daemon that monitors wallpaper changes and logs them", + "version": "1.0.0", + "author": "DankMaterialShell", + "icon": "wallpaper", + "type": "daemon", + "component": "./WallpaperWatcherDaemon.qml", + "settings": "./WallpaperWatcherSettings.qml", + "permissions": [ + "settings_read", + "settings_write" + ] +} diff --git a/Services/PluginService.qml b/Services/PluginService.qml index d36c9fa1..a36f206d 100644 --- a/Services/PluginService.qml +++ b/Services/PluginService.qml @@ -14,6 +14,7 @@ Singleton { property var availablePlugins: ({}) property var loadedPlugins: ({}) property var pluginWidgetComponents: ({}) + property var pluginDaemonComponents: ({}) property string pluginDirectory: { var configDir = StandardPaths.writableLocation(StandardPaths.ConfigLocation) var configDirStr = configDir.toString() @@ -153,6 +154,7 @@ Singleton { pluginInfo.componentPath = pluginDir + '/' + componentFile pluginInfo.settingsPath = settingsFile ? pluginDir + '/' + settingsFile : null pluginInfo.loaded = false + pluginInfo.type = manifest.type || "widget" availablePlugins[manifest.id] = pluginInfo } @@ -178,12 +180,19 @@ Singleton { return true } - if (pluginWidgetComponents[pluginId]) { - var oldComponent = pluginWidgetComponents[pluginId] + var isDaemon = plugin.type === "daemon" + var componentMap = isDaemon ? pluginDaemonComponents : pluginWidgetComponents + + if (componentMap[pluginId]) { + var oldComponent = componentMap[pluginId] if (oldComponent) { oldComponent.destroy() } - delete pluginWidgetComponents[pluginId] + if (isDaemon) { + delete pluginDaemonComponents[pluginId] + } else { + delete pluginWidgetComponents[pluginId] + } } try { @@ -207,9 +216,15 @@ Singleton { return false } - var newComponents = Object.assign({}, pluginWidgetComponents) - newComponents[pluginId] = component - pluginWidgetComponents = newComponents + if (isDaemon) { + var newDaemons = Object.assign({}, pluginDaemonComponents) + newDaemons[pluginId] = component + pluginDaemonComponents = newDaemons + } else { + var newComponents = Object.assign({}, pluginWidgetComponents) + newComponents[pluginId] = component + pluginWidgetComponents = newComponents + } plugin.loaded = true loadedPlugins[pluginId] = plugin @@ -232,15 +247,25 @@ Singleton { } try { - if (pluginWidgetComponents[pluginId]) { + var isDaemon = plugin.type === "daemon" + + if (isDaemon && pluginDaemonComponents[pluginId]) { + var daemonComponent = pluginDaemonComponents[pluginId] + if (daemonComponent) { + daemonComponent.destroy() + } + var newDaemons = Object.assign({}, pluginDaemonComponents) + delete newDaemons[pluginId] + pluginDaemonComponents = newDaemons + } else if (pluginWidgetComponents[pluginId]) { var component = pluginWidgetComponents[pluginId] if (component) { component.destroy() } + var newComponents = Object.assign({}, pluginWidgetComponents) + delete newComponents[pluginId] + pluginWidgetComponents = newComponents } - var newComponents = Object.assign({}, pluginWidgetComponents) - delete newComponents[pluginId] - pluginWidgetComponents = newComponents plugin.loaded = false delete loadedPlugins[pluginId] @@ -258,6 +283,10 @@ Singleton { return pluginWidgetComponents } + function getDaemonComponents() { + return pluginDaemonComponents + } + function getAvailablePlugins() { var result = [] for (var key in availablePlugins) {