diff --git a/README.md b/README.md index d082a17..7564fa5 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,13 @@ Fully-featured widget to bring Latte-Dock and WM status bar customization featur * [Built-in presets](https://github.com/luisbocanegra/plasma-panel-colorizer/tree/main/package/contents/ui/presets) * Create your own presets * Preset auto-loading - * Floating panel - * Maximized window shown - * Window touching panel + * Fullscreen window + * Maximized window + * Window touching the panel + * At least one window is shown on screen + * Panel is floating + * Normal (fall-back when none of the above are meet) +* [Switch presets from the command-line using D-Bus](#switching-presets-using-the-commandline-with-d-bus) ### Panel/Widget/System Tray elements @@ -96,8 +100,14 @@ Overrides let you give a completely different configuration to one or more widge ### KDE Store -1. **Right click on the Panel** > **Add or manage widgets** > **Add new...** > **Download new...** -2. **Search** for "**Panel Colorizer**", install and add it to a Panel. +1. Install these runtime dependencies or the equivalents for your distribution + + ```txt + python python-dbus python-gobject + ``` + +2. **Right click on the Panel** > **Add or manage widgets** > **Add new...** > **Download new...** +3. **Search** for "**Panel Colorizer**", install and add it to a Panel. * ~~[Plasma 5](https://store.kde.org/p/2131149) version v0.2.0~~ **[No longer maintained](https://github.com/luisbocanegra/plasma-panel-colorizer/issues/10)** @@ -110,13 +120,13 @@ Overrides let you give a completely different configuration to one or more widge * Arch ```txt - git gcc cmake extra-cmake-modules libplasma spectacle + git gcc cmake extra-cmake-modules libplasma spectacle python python-dbus python-gobject ``` * Fedora ```txt - git gcc-c++ cmake extra-cmake-modules libplasma-devel spectacle + git gcc-c++ cmake extra-cmake-modules libplasma-devel spectacle python3 python3-dbus python3-gobject ``` Spectacle is optional, will be used to create preset previews @@ -187,6 +197,18 @@ To install the widget use one of these methods: 2. Go to the widget settings to change the current panel appearance (right click > Configure...) 3. Widget can set to only show in panel **Edit Mode** (right click > Hide widget or from the widget settings) +### Switching presets using the commandline with D-Bus + +1. Go to the widget settings +2. In General tab enable the D-Bus service +3. To apply a preset can use qdbus6 and pass the absolute path of a preset: + + ```sh + qdbus6 luisbocanegra.panel.colorizer.c337.w2346 /preset preset /path/to/preset/dir/ + ``` + +Each widget instance has its own D-bus name, you can get it from the same widget settings General tab. + ## Adding or improving the built-in presets Instructions to add new presets or improve the existing ones are [here](https://github.com/luisbocanegra/plasma-panel-colorizer/blob/main/package/contents/ui/presets/README.md) @@ -243,7 +265,7 @@ Backgrounds are drawn by creating rectangle areas bellow widgets/panel, text and **Performance** -I tried to optimize it so CPU usage only increases around 0.5-1% on my computer, but usage could vary depending on your System or how many widgets are in your panels. +I tried to optimize it so CPU usage only increases around 0.5-1% on my computer, usage may vary depending on your System or how many widgets are in your panels. ### Can this widget change the appearance of other parts of Plasma (e.g Desktop view, widget popups/tooltips, overview) 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..3c3d640 100644 --- a/package/contents/ui/code/globals.js +++ b/package/contents/ui/code/globals.js @@ -260,5 +260,8 @@ const ignoredConfigs = [ "configurationOverrides", "widgetClickMode", "switchPresets", - "switchPresetsIndex" + "switchPresetsIndex", + "enableDBusService", + "dBusPollingRate", + "pythonExecutable" ] 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()