Communicate with plasmoid

Hi,

For my very personnal use, I created a very simple plasmoid (for plasma 6).
When I clic, it launches a toggleState() function.
All works nice, but now, I would like to start this function from elsewhere, like a script.
What path should I follow?

Here is my main.qml:

import QtQuick
import org.kde.plasma.plasmoid
import org.kde.kirigami as Kirigami
import org.kde.plasma.plasma5support as Plasma5Support

PlasmoidItem {
    id: root

    property string iconOff: Qt.resolvedUrl("../images/cercle_vert_plein.svg")
    property string iconOn: Qt.resolvedUrl("../images/cercle_rouge_plein.svg")
    Plasmoid.icon: iconOff

    preferredRepresentation: fullRepresentation

    MouseArea {
        anchors.fill: parent
        onClicked: toggleState()
    }
    Kirigami.Icon {
        id: icon
        anchors.fill: parent
        height: Math.min(parent.height, parent.width)
        width: valid ? height : 0
        source: Plasmoid.icon
    }
    Plasma5Support.DataSource {
        id: executable
        engine: "executable"
        onNewData: function(source, data) { disconnectSource(source) }
        function exec(cmd) { executable.connectSource(cmd) }
    }
    function toggleState() {
        Plasmoid.icon = Plasmoid.icon === iconOff ? iconOn : iconOff
        executable.exec("/usr/local/perso/sws")
    }
}

There’s no direct way to dot this as far as I am aware.

I don’t kow if there’s a better way, but one option would be to create a Python Script or a standalone executable which you can execute from the Plasmoid using a DataSource. Then you can make the script register a DBus Srvice with a method which terminate the script with a message like ‘Triggered’. You can make the plasmoid trigger the related function when it recieve the Triggered text.

Add this on QML side

Plasma5Support.DataSource {
    id: service
    engine: "executable"
    onNewData: function(source, data) {
        disconnectSource(source)
        if (data.stdout.includes("Triggered"))
            toggleState()
    }
    function start() { executable.connectSource("python service.py") }
}

and in the python script service.py:

#!/usr/bin/env python

import dbus
import dbus.service
import dbus.mainloop.glib

from gi.repository import GLib

class Service(dbus.service.Object):
   def run(self):
      dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
      bus_name = dbus.service.BusName("com.example.service", dbus.SessionBus())
      dbus.service.Object.__init__(self, bus_name, "/com/example/service")

      self._loop = GLib.MainLoop()
      self._loop.run()

   @dbus.service.method("com.example.service.trigger", in_signature='', out_signature='')
   def trigger(self):
      print ("Triggered")
      self._loop.quit()

if __name__ == "__main__":
   Service().run()

Then you can call this DBus service from your script to trigger the plasmoid

1 Like

Thank you very much for your help.

Should this make the python script run when the plasmoid is loaded?
If so, this doesn’t work.

And, if it worked, what bash command to execute this “Triggered”?

After many hours trying to make it work, I realize that it is impossible to retrieve the output of a script that loops in this way.
I have indeed removed the self._loop.quit() from the python script, because it closes immediately, how could a bash script send it a command?
I am not sure that @Jack_White understood my need, or I expressed myself very badly.
I need to be able to send a command to my plasmoid through a bash command, such as a dbus-send here. So here the python script launched by my plasmoid would have to run permanently to wait for this command, and my plasmoid would have to receive the notification, and that, as presented, is not possible.

Does anyone have another idea for a solution?

The way I implemented is similar to what @Jack_White suggests, though I have been using it to run commands and retrieve the output when they exit and haven’t tried reading from the constant stdout stream.

So if you only want to run a simple command you simply call it and read what it returns if you want to display it, then you could use a timer and have it run at your desired interval, for example have a common file that your widget reads and you script writes.

To interact with the widget I register a D-Bus service with a python script but exposed dbus methods to get-set the data I needed. Then have the plasmoid polling from it using the get method at a fixed interval.

The code is in a widget of mine ( Commit b549534)

Thanks @luisbocanegra for trying to help me.

Excuse me but either I’m becoming an idiot or what you’re talking about doesn’t correspond at all to what I’m trying to do.
Because, if the python script stops as soon as the service has been queried with its :
print (“Triggered”)
self._loop.quit()
I don’t see at all how it will be able to answer the next time it is queried again. It’s a one-time use and I don’t understand its purpose or interest.
And if it’s the plasmoid that launches the python script, this python script must remain alive all the time the plasmoid is loaded to be able to send it commands.

In @Jack_White QML I don’t even know what launches the start().

So I explain once again what I am trying to do:

  1. I load my plasmoid
  2. I trigger an event by any means like a dbus-send command, or a write to a file, or a notification, nevermind
  3. the plasmoid detects this event and launches the toggleState() function

In no case do I want the plasmoid to launch an event retrieved by something else. It’s the opposite!

Sorry @luisbocanegra but being a beginner, I don’t understand much of what you suggested to me.

Edit:
I think I finally get it:

    Component.onCompleted: {
        console.log("Plasmoid loaded")
        executable.connectSource(dbusScript)
    }
    Plasma5Support.DataSource {
        id: executable
        engine: "executable"
        connectedSources: []
        onNewData: function(source, data) {
            console.log("Data received:",data.stdout.trim())
            if ( source === suspendScript ) {
                if ( data.stdout.trim() === msgOn ) {
                    Plasmoid.icon = iconOn
                    tooltipLabel.text = "suspend"
                } else {
                    Plasmoid.icon = iconOff
                    tooltipLabel.text = ""
                }
                disconnectSource(source)
            } else {
                disconnectSource(source)
                if (data.stdout.includes("Triggered"))
                    connectSource(suspendScript)
            }
        }
        onSourceRemoved: function(source) {
            console.log("removed:", source)
            if (source === dbusScript) 
                restartTimer.start()
        }
    }
    
    Timer {
        id: restartTimer
        interval: 1
        repeat: false
        onTriggered: {
            console.error("Restarting command:", dbusScript);
            executable.connectSource(dbusScript);
        }
    }

There are definitely much better ones, but this seems to work, and does what I want.
Thanks!

Ah, Sorry. I was in a hurry and left a few key parts from the code. You have figured it out anyway, so I guess all’s well that ends well

PS: Wouldn’t the code look a bit cleaner if the DBus service is started from it’s own DataSource? Dont know about possible performance issues though

Ideally there would be a “native” way of doing D-Bus calls from QML of a Plasmoid.

KWin’s scripting API has support for that.

There are also some people on GitHub who have at some point worked on QML API for doing D-Bus.

1 Like

There is actually a module in plasma to do that: components/dbus ¡ master ¡ Plasma / Plasma Workspace ¡ GitLab

2 Likes

Ah, I was already wondering if I was simply missing something :slight_smile:

I guess this would need a bit more documentation from a QML point of view so that it doesn’t require understanding on how the C++/QML type system works.

2 Likes