Skip to content

Commit

Permalink
feat: register a D-Bus service per widget to apply presets
Browse files Browse the repository at this point in the history
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: #126
  • Loading branch information
luisbocanegra committed Jan 11, 2025
1 parent 8028e59 commit 2b8f9b6
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 13 deletions.
13 changes: 13 additions & 0 deletions package/contents/config/main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,18 @@
type="Int">
<default>0</default>
</entry>
<entry name="pythonExecutable" type="String">
<default>python3</default>
</entry>
<entry
name="enableDBusService"
type="Bool">
<default>false</default>
</entry>
<entry
name="dBusPollingRate"
type="Int">
<default>250</default>
</entry>
</group>
</kcfg>
73 changes: 73 additions & 0 deletions package/contents/ui/DBusServiceModel.qml
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

4 changes: 3 additions & 1 deletion package/contents/ui/code/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,5 +260,7 @@ const ignoredConfigs = [
"configurationOverrides",
"widgetClickMode",
"switchPresets",
"switchPresetsIndex"
"switchPresetsIndex",
"enableDBusService",
"dBusPollingRate"
]
84 changes: 84 additions & 0 deletions package/contents/ui/configGeneral.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -27,6 +36,7 @@ KCM.SimpleKCM {

ColumnLayout {
Kirigami.FormLayout {
id: form
CheckBox {
Kirigami.FormData.label: i18n("Hide widget:")
id: hideWidget
Expand All @@ -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
}
}
}
}
32 changes: 20 additions & 12 deletions package/contents/ui/main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -1433,20 +1433,20 @@ PlasmoidItem {
}
}

// toolTipMainText: onDesktop ? "" :
toolTipSubText: {
let text = ""
if (onDesktop) {
return "<font color='"+Kirigami.Theme.neutralTextColor+"'>Panel not found, this widget must be child of a panel</font>"
}
if (!plasmoid.configuration.isEnabled) {
return ""
}
const name = plasmoid.configuration.lastPreset.split("/")
if (name.length) {
return i18n("Last preset loaded:") + " " + name[name.length-1]
text = "<font color='"+Kirigami.Theme.neutralTextColor+"'>Panel not found, this widget must be child of a panel</font>"
} 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 :
Expand Down Expand Up @@ -1588,4 +1588,12 @@ PlasmoidItem {
}

fullRepresentation: onDesktop ? desktopView : popupView

DBusServiceModel {
enabled: plasmoid.configuration.enableDBusService
poolingRate: plasmoid.configuration.dBusPollingRate
onPresetChanged: {
applyPreset(preset)
}
}
}
89 changes: 89 additions & 0 deletions package/contents/ui/tools/service.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 2b8f9b6

Please sign in to comment.