From 2b8f9b63cccfe510458e123f6236144fdf86ecb1 Mon Sep 17 00:00:00 2001 From: Luis Bocanegra Date: Sat, 11 Jan 2025 03:49:10 -0600 Subject: [PATCH] feat: register a D-Bus service per widget to apply presets Should be enabled first from the widget settings General tab example usage: qdbus6 luisbocanegra.panel.colorizer.c337.w2346 /preset luisbocanegra.panel.colorizer.c337.w2346.preset /path/to/preset/dir refs: https://github.com/luisbocanegra/plasma-panel-colorizer/issues/126 --- package/contents/config/main.xml | 13 ++++ package/contents/ui/DBusServiceModel.qml | 73 +++++++++++++++++++ package/contents/ui/code/globals.js | 4 +- package/contents/ui/configGeneral.qml | 84 ++++++++++++++++++++++ package/contents/ui/main.qml | 32 +++++---- package/contents/ui/tools/service.py | 89 ++++++++++++++++++++++++ 6 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 package/contents/ui/DBusServiceModel.qml create mode 100644 package/contents/ui/tools/service.py diff --git a/package/contents/config/main.xml b/package/contents/config/main.xml index f4e5b42..0c75a36 100644 --- a/package/contents/config/main.xml +++ b/package/contents/config/main.xml @@ -69,5 +69,18 @@ type="Int"> 0 + + python3 + + + false + + + 250 + diff --git a/package/contents/ui/DBusServiceModel.qml b/package/contents/ui/DBusServiceModel.qml new file mode 100644 index 0000000..6b9dfd5 --- /dev/null +++ b/package/contents/ui/DBusServiceModel.qml @@ -0,0 +1,73 @@ +import QtQuick +import org.kde.plasma.plasmoid + + +Item { + + id: root + property bool enabled: false + property string preset: "" + property bool switchIsPending: false + property int poolingRate: 250 + + property string toolsDir: Qt.resolvedUrl("./tools").toString().substring(7) + "/" + property string serviceUtil: toolsDir+"service.py" + property string pythonExecutable: plasmoid.configuration.pythonExecutable + property string serviceCmd: pythonExecutable + " '" + serviceUtil + "' " + Plasmoid.containment.id + " " + Plasmoid.id + property string dbusName: Plasmoid.metaData.pluginId + ".c" + Plasmoid.containment.id + ".w" + Plasmoid.id + property string gdbusPartial: "gdbus call --session --dest "+dbusName+" --object-path /preset --method "+dbusName + property string pendingSwitchCmd: gdbusPartial +".pending_switch" + property string switchDoneCmd: gdbusPartial +".switch_done" + property string getPresetCmd: gdbusPartial +".preset" + property string quitServiceCmd: gdbusPartial +".quit" + + RunCommand { + id: runCommand + onExited: (cmd, exitCode, exitStatus, stdout, stderr) => { + // console.error(cmd, exitCode, exitStatus, stdout, stderr) + if (exitCode!==0) return + stdout = stdout.trim().replace(/[()',]/g, "") + // console.log("stdout parsed:", stdout) + if(cmd === pendingSwitchCmd) { + switchIsPending = stdout === "true" + } + if (cmd === getPresetCmd) { + preset = stdout + switchIsPending = false + } + } + } + + Component.onCompleted: { + toggleService() + } + + function toggleService() { + if (enabled) { + runCommand.run(serviceCmd) + } else ( + runCommand.run(quitServiceCmd) + ) + } + + onEnabledChanged: toggleService() + + onSwitchIsPendingChanged: { + if (switchIsPending) { + runCommand.run(switchDoneCmd) + runCommand.run(getPresetCmd) + } + } + + Timer { + id: updateTimer + interval: poolingRate + running: enabled + repeat: true + onTriggered: { + if (switchIsPending) return + runCommand.run(pendingSwitchCmd) + } + } +} + diff --git a/package/contents/ui/code/globals.js b/package/contents/ui/code/globals.js index 5f56225..b99a939 100644 --- a/package/contents/ui/code/globals.js +++ b/package/contents/ui/code/globals.js @@ -260,5 +260,7 @@ const ignoredConfigs = [ "configurationOverrides", "widgetClickMode", "switchPresets", - "switchPresetsIndex" + "switchPresetsIndex", + "enableDBusService", + "dBusPollingRate" ] diff --git a/package/contents/ui/configGeneral.qml b/package/contents/ui/configGeneral.qml index d67bd95..4d2c9d2 100644 --- a/package/contents/ui/configGeneral.qml +++ b/package/contents/ui/configGeneral.qml @@ -16,6 +16,15 @@ KCM.SimpleKCM { property bool cfg_hideWidget: hideWidget.checked property alias cfg_isEnabled: headerComponent.isEnabled property alias cfg_enableDebug: enableDebug.checked + property alias cfg_enableDBusService: enableDBusService.checked + property alias cfg_pythonExecutable: pythonExecutable.text + property alias cfg_dBusPollingRate: dBusPollingRate.value + + property string presetsDir: StandardPaths.writableLocation( + StandardPaths.HomeLocation).toString().substring(7) + "/.config/panel-colorizer/presets" + property string presetsBuiltinDir: Qt.resolvedUrl("./presets").toString().substring(7) + "/" + + property string dbusName: Plasmoid.metaData.pluginId + ".c" + Plasmoid.containment.id + ".w" + Plasmoid.id header: ColumnLayout { Components.Header { @@ -27,6 +36,7 @@ KCM.SimpleKCM { ColumnLayout { Kirigami.FormLayout { + id: form CheckBox { Kirigami.FormData.label: i18n("Hide widget:") id: hideWidget @@ -41,6 +51,80 @@ KCM.SimpleKCM { onCheckedChanged: cfg_enableDebug = checked text: i18n("Show debugging information") } + + Kirigami.Separator { + Kirigami.FormData.isSection: true + Kirigami.FormData.label: i18n("D-Bus Service") + Layout.fillWidth: true + } + + CheckBox { + Kirigami.FormData.label: i18n("Enabled:") + id: enableDBusService + checked: cfg_enableDBusService + onCheckedChanged: cfg_enableDBusService = checked + text: i18n("D-Bus name:") + " " + dbusName + } + + Label { + text: i18n("Each Panel Colorizer instance has its D-Bus name.") + wrapMode: Text.WordWrap + Layout.preferredWidth: 400 + opacity: 0.6 + } + + TextField { + Kirigami.FormData.label: i18n("Python 3 executable:") + id: pythonExecutable + placeholderText: qsTr("Python executable e.g. python, python3") + enabled: enableDBusService.checked + } + + Label { + text: i18n("Required to run the D-Bus service in the background") + wrapMode: Text.WordWrap + Layout.preferredWidth: 400 + opacity: 0.6 + } + + SpinBox { + Kirigami.FormData.label: i18n("Polling rate:") + from: 10 + to: 9999 + stepSize: 100 + id: dBusPollingRate + } + + Label { + text: i18n("How fast the widget reacts to D-Bus changes") + wrapMode: Text.WordWrap + Layout.preferredWidth: 400 + opacity: 0.6 + } + + + Label { + Kirigami.FormData.label: i18n("Usage:") + text: i18n("Apply a preset:") + } + + TextArea { + text: "qdbus6 " + dbusName + " /preset preset /path/to/preset/dir/" + readOnly: true + wrapMode: Text.WordWrap + Layout.preferredWidth: 400 + } + + Label { + text: i18n("Preview and switch presets using fzf + qdbus6 + jq:") + } + + TextArea { + text: "find " + presetsBuiltinDir + " "+ presetsDir +" -mindepth 1 -prune -type d | fzf --preview 'qdbus6 " + dbusName + " /preset preset {} && jq --color-output . {}/settings.json'" + readOnly: true + wrapMode: Text.WordWrap + Layout.preferredWidth: 400 + } } } } diff --git a/package/contents/ui/main.qml b/package/contents/ui/main.qml index 101e080..c3f6ed5 100644 --- a/package/contents/ui/main.qml +++ b/package/contents/ui/main.qml @@ -150,7 +150,7 @@ PlasmoidItem { onStockPanelSettingsChanged: { Qt.callLater(function() { - console.error(JSON.stringify(stockPanelSettings)) + // console.error(JSON.stringify(stockPanelSettings)) let script = Utils.setPanelModeScript(panelPosition, stockPanelSettings) Utils.evaluateScript(script) }) @@ -1333,7 +1333,7 @@ PlasmoidItem { if (!child.applet?.plasmoid?.pluginName) continue // if (Utils.getBgManaged(child)) continue // console.error(child.applet?.plasmoid?.pluginName) - if (child.applet.plasmoid.pluginName !== "luisbocanegra.panel.colorizer") { + if (child.applet.plasmoid.pluginName !== Plasmoid.metaData.pluginId) { child.applet.plasmoid.contextualActions.push(configureAction) } const isTray = child.applet.plasmoid.pluginName === "org.kde.plasma.systemtray" @@ -1433,20 +1433,20 @@ PlasmoidItem { } } + // toolTipMainText: onDesktop ? "" : toolTipSubText: { + let text = "" if (onDesktop) { - return "Panel not found, this widget must be child of a panel" - } - if (!plasmoid.configuration.isEnabled) { - return "" - } - const name = plasmoid.configuration.lastPreset.split("/") - if (name.length) { - return i18n("Last preset loaded:") + " " + name[name.length-1] + text = "Panel not found, this widget must be child of a panel" + } else if (plasmoid.configuration.isEnabled) { + const name = plasmoid.configuration.lastPreset.split("/") + if (name.length) { + text = i18n("Last preset loaded:") + " " + name[name.length-1] + } } - return "" + return text } - toolTipTextFormat: Text.RichText + toolTipTextFormat: Text.PlainText Plasmoid.status: (editMode || !hideWidget) ? PlasmaCore.Types.ActiveStatus : @@ -1588,4 +1588,12 @@ PlasmoidItem { } fullRepresentation: onDesktop ? desktopView : popupView + + DBusServiceModel { + enabled: plasmoid.configuration.enableDBusService + poolingRate: plasmoid.configuration.dBusPollingRate + onPresetChanged: { + applyPreset(preset) + } + } } diff --git a/package/contents/ui/tools/service.py b/package/contents/ui/tools/service.py new file mode 100644 index 0000000..7971744 --- /dev/null +++ b/package/contents/ui/tools/service.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +""" +D-Bus service to interact with the current panel +""" + +import sys +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop +from gi.repository import GLib + +DBusGMainLoop(set_as_default=True) +bus = dbus.SessionBus() + +CONTAINMENT_ID = sys.argv[1] +PANEL_ID = sys.argv[2] +SERVICE_NAME = "luisbocanegra.panel.colorizer.c" + CONTAINMENT_ID + ".w" + PANEL_ID +PATH = "/preset" + + +class Service(dbus.service.Object): + """D-Bus service + + Args: + dbus (dbus.service.Object): D-Bus object + """ + + def __init__(self): + self._loop = GLib.MainLoop() + self._last_preset = "" + self._pending_witch = False + super().__init__() + + def run(self): + """run""" + DBusGMainLoop(set_as_default=True) + bus_name = dbus.service.BusName(SERVICE_NAME, dbus.SessionBus()) + dbus.service.Object.__init__(self, bus_name, PATH) + + print("Service running...") + self._loop.run() + print("Service stopped") + + @dbus.service.method(SERVICE_NAME, in_signature="s", out_signature="s") + def preset(self, m="") -> str: + """Set and get the last applied preset + + Args: + m (str, optional): Preset. Defaults to "". + + Returns: + str: "saved" or current Preset + """ + if m: + if m != self._last_preset: + print(f"last_last_preset: '{m}'") + self._last_preset = m + self._pending_witch = True + return "saved" + return self._last_preset + + @dbus.service.method(SERVICE_NAME, in_signature="", out_signature="b") + def pending_switch(self) -> bool: + """Wether there is a pending preset switch + + Returns: + bool: Pending + """ + return self._pending_witch + + @dbus.service.method(SERVICE_NAME, in_signature="", out_signature="") + def switch_done(self): + """Void the pending switch""" + self._pending_witch = False + + @dbus.service.method(SERVICE_NAME, in_signature="", out_signature="") + def quit(self): + """Stop the service""" + print("Shutting down") + self._loop.quit() + + +if __name__ == "__main__": + # Keep a single instance of the service + try: + bus.get_object(SERVICE_NAME, PATH) + print("Service is already running") + except dbus.exceptions.DBusException: + Service().run()