Minimal example of GDBus in Python

I’m sorry, the error was on line number 22:

File "service.py", line 22, in on_name_acquired
    Printer.connect("handle-configure", on_handle_configure, None)
TypeError: descriptor 'connect' requires a 'gi._gi.GObject' object but received a 'str'

Updated the code.

Try replacing Printer with printer on line 22.

Printer refers to the class, while printer refers to the instance of the class you created.

Thanks for that, a typo :joy: :man_facepalming:
Now I’m getting an unknown signal “handle-configure” error? Is it a valid signal for DBus-Skeleton?

Now I’m getting an unknown signal “handle-configure” error? Is it a valid signal for DBus-Skeleton?

No, I don’t see it in the documentation. You can use the .add_interface() method to implement the actual D-Bus interface that you want.

Ahhh its confusing. There are two things, Gio.DBusInterfaceSkeleton, which has export method but not add_interface and Gio.DBusObjectSkeleton which has your add_interface method but not export. Which one to use? How to use? Can you give an example?

I believe you should create an InterfaceSkeleton, add the methods you want to publish there, then add the interface to an ObjectSkeleton and export that.

Confusing… I’m confused how to export an interface. It’s already 12:33 AM here, let me continue fresh tomorrow…

Hello. How do I do that?

There is a lot to get your head around the first time! My advice is to experiment, and also to look at other similar projects even if they are written in a different languages :slight_smile:

What happens if you remove the line:

Printer.connect("handle-configure", on_handle_configure, None) 

is anything exported?

1 Like

Nope, Segmentation Fault.

I’m not going to give up, I can read C, so I’m looking at the examples included in the official API reference of DBus objects. Now I’m here : https://gitlab.gnome.org/GNOME/glib/-/blob/master/gio/tests/gdbus-example-server.c and looking for how to implement a Gio.DBusInterfaceInfo in Python.

If there’s a segmentation fault, you can use gdb to find out what happened. Use gdb --args python3 ... to run your program, type run to start execution, then backtrace to see where it crashed.

It’s reasonably common that PyGI code can trigger segfaults because it can call directly into the C code without checking that parameters you passed make sense, so if you pass an integer when the C code expects a string for example it’ll lead to a crash.

1 Like

Ok I will do it and report you asap.

@sthursfield I created the following scripts:

service.py
import gi
from gi.repository import Gio, GLib

node_xml = """
<node>
    <interface name='org.gtk.GDBus.TestInterface'>
      <annotation name='org.gtk.GDBus.Annotation' value='OnInterface'/>
      <annotation name='org.gtk.GDBus.Annotation' value='AlsoOnInterface'/>
      <method name='HelloWorld'>
        <annotation name='org.gtk.GDBus.Annotation' value='OnMethod'/>
        <arg type='s' name='greeting' direction='in'/>
        <arg type='s' name='response' direction='out'/>
      </method>
      <method name='EmitSignal'>
        <arg type='d' name='speed_in_mph' direction='in'>
          <annotation name='org.gtk.GDBus.Annotation' value='OnArg'/>
        </arg>
      </method>
      <method name='GimmeStdout'/>
      <signal name='VelocityChanged'>
        <annotation name='org.gtk.GDBus.Annotation' value='Onsignal'/>
        <arg type='d' name='speed_in_mph'/>
        <arg type='s' name='speed_as_string'>
          <annotation name='org.gtk.GDBus.Annotation' value='OnArg_NonFirst'/>
        </arg>
      </signal>
      <property type='s' name='FluxCapicitorName' access='read'>
        <annotation name='org.gtk.GDBus.Annotation' value='OnProperty'>
          <annotation name='org.gtk.GDBus.Annotation' value='OnAnnotation_YesThisIsCrazy'/>
        </annotation>
      </property>
      <property type='s' name='Title' access='readwrite'/>
      <property type='s' name='ReadingAlwaysThrowsError' access='read'/>
      <property type='s' name='WritingAlwaysThrowsError' access='readwrite'/>
      <property type='s' name='OnlyWritable' access='write'/>
      <property type='s' name='Foo' access='read'/>
      <property type='s' name='Bar' access='read'/>
    </interface>
</node>"""

NodeInfo = Gio.DBusNodeInfo.new_for_xml(node_xml)

def on_bus_acquired(conn, name):
    conn.register_object(conn,
                         "/org/Gtk/GDBus/TestInterface",
                         NodeInfo,
                         print,
                         print,
                         print)

def on_name_acquired(conn, name):
    print("Name acquired")

def on_name_lost(conn, name):
    print("Name lost")

Gio.bus_own_name(Gio.BusType.SESSION,
                 "org.gtk.GDBus.TestInterface",
                 Gio.BusNameOwnerFlags.NONE,
                 None,
                 on_name_acquired,
                 on_name_lost)
GLib.MainLoop().run()

Running it, I got Name acquired.

client.py

import sys
from gi.repository import GLib, Gio

if len(sys.argv) < 2:
sys.stderr.write(“Tell something\n”)
sys.exit(1)

if len(sys.argv) > 2:
sys.stderr.write(“Too many arguments\n”)
sys.exit(1)

connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
proxy = Gio.DBusProxy.new_sync(connection,
Gio.DBusProxyFlags.NONE,
None,
“org.gtk.GDBus.TestInterface”,
“/org/Gtk/GDBus/TestInterface”,
“org.gtk.GDBus.TestInterface”,
None)
try:
proxy.call_sync(“Configure”,
GLib.Variant("(u)", (int(sys.argv[1]),)),
Gio.DBusCallFlags.NONE,
-1,
None)
except Exception as e:
sys.stderr.write(“Error: %s\n” % str(e))
else:
print(“Done”)

Running it with service.py already running, I got this:

$ python3 client.py 13
Error: g-dbus-error-quark: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such interface “org.gtk.GDBus.TestInterface” on object at path /org/Gtk/GDBus/TestInterface (19)

What’s next?

I find the tool d-feet very useful. Try using that to test your D-Bus service.

Sorry forgot to include the screenshot

Have you been able to make any progress? I’m really interested in this and I hope that you succeed and maybe even contribute some examples somewhere or to documentation because It was really confusing when I looked into it.

1 Like

I’m surely doing something, don’t worry I will soon give you all a good result.

1 Like

Success!! After centuries of battle with many things, I was able to make a working example ! Yeah! I have included the source code below.

server.py
from gi.repository import Gio, GLib


# Thanks to  Gio test files at GNOME repo: glib/blob/master/gio/tests/gdbus-example-server.c
# Generating interface from XML is easy. Look at the above file on how it is done.
# A better resource : http://maemo.org/maemo_training_material/maemo4.x/html/
#                     maemo_Platform_Development_Chinook/
#                     Chapter_03_Using_the_GLib_wrappers_for_DBus.html
#                     #DBusinterfacedefinitionusingXML


xml = (
    "<node>"
    "  <interface name='org.examples.gdbus'>"
    "    <method name='PrintMsg'>"
    "      <arg type='s' name='msg' direction='in'>"
    "      </arg>"
    "    </method>"
    "    <method name='SayHello'>"
    "      <arg type='s' name='name' direction='in'/>"
    "      <arg type='s' name='greeting' direction='out'/>"
    "    </method>"
    "    <method name='Quit'/>"
    "  </interface>"
    "</node>"
)

node = Gio.DBusNodeInfo.new_for_xml(xml)  # We make a node for the xml
loop = GLib.MainLoop() # A loop to handle API

def handle_method_call(
    connection, sender, object_path, interface_name, method_name, params, invocation
):
    """
    This is the top-level function that handles all the method calls to our server.
    The first four parameters are self-explanatory.
    `method_name` is a string that describes our method name.
    `params` is a GLib.Variant that are inputs/parameters to the method.
    `invocation` is a Gio.DBusMethodInvocation, something like a messenger that transports
    our reply back to sender.
    """

    print("CALLED")
    # We need to unpack GLib.Variant to a Python object. The unpacked one is always a
    # tuple.
    if method_name == "PrintMsg":
        msg = params.unpack()[0]  # First argument is what we need
        print(f"FROM {sender} : {msg}")
        invocation.return_value(None)  # Nothing to say, so just return None.

    elif method_name == "SayHello":
        name = params.unpack()[0]
        print(f"FROM {sender} : GREET {name}")
        greeting = GLib.Variant(
            "(s)", (f"Long live {name}",)
        )  # All form of return should be a
        # variant.
        invocation.return_value(greeting)

    elif method_name == "Quit":
        loop.quit()
        invocation.return_value(None)

    # No need of an else part to handle methods which we didn't mention in XML.
    # When a client does something like that, they get an error.
    # The same holds for parameters.
    # Always return something (actually return value specified in XML) via invocation,
    # otherwise client get a response-timeout error.


def on_bus_acquired(connection, name):

    """
    The function that introduces our server to the world. It is called automatically
    when we get the bus we asked.
    """

    # Remember the node we made earlier? That has a list as interfaces attribute.
    # From that get our interface. We made only one interface, so we get the first
    # interface.

    print("Bus acquired for name, ", name)
    reg_id = connection.register_object(
        "/org/examples/gdbus", node.interfaces[0], handle_method_call, None, None
    )


def on_name_acquired(connection, name):

    """
    What to do after name acquired?
    """

    print("Name acquired :", name)


def on_name_lost(connection, name):

    """
    What to do after our name is lost? May be just exit.
    """

    print("Name lost :", name)
    exit(0)


if __name__ == "__main__":

    # Now we request the name and run the server.

    owner_id = Gio.bus_own_name(
        Gio.BusType.SESSION,  # What kinda bus is it?
        "org.examples.gdbus",  # It's name ?
        Gio.BusNameOwnerFlags.NONE,  # If other has same name, what to do?
        on_bus_acquired,
        on_name_acquired,
        on_name_lost,
    )

    loop.run()  # Like a web server, ours has to be active for client access
    Gio.bus_unown_name(owner_id)
    print("Exiting...")

client.py
from gi.repository import Gio, GLib

# Client is pretty straight-forward
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)  # What is the bus type?

# Below we are doing something like, 'Hey Gio, heard there is a DBus server
# with name org.examples.gdbus and type SESSION, get me a connection to it'
proxy = Gio.DBusProxy.new_sync(
    bus,
    Gio.DBusProxyFlags.NONE,
    None,
    "org.examples.gdbus",
    "/org/examples/gdbus",
    "org.examples.gdbus",
    None,
)

# proxy.call_sync is the way to call the method names of server.


def print_msg(msg):

    var = GLib.Variant("(s)", (msg,))  # Parameters should be variant.
    proxy.call_sync(
        "PrintMsg",  # Method name
        var,  # Parameters for method
        Gio.DBusCallFlags.NO_AUTO_START,  # Flags for call APIs
        500,  # How long to wait for reply? (in milliseconds)
        None,  # Cancellable, to cancel the call if you changed mind in middle)
    )


def say_hello(name):

    var = GLib.Variant("(s)", (name,))
    ret_var = proxy.call_sync(
        "SayHello", var, Gio.DBusCallFlags.NO_AUTO_START, 500, None
    )
    greeting = ret_var.unpack()[0]
    print(greeting)

def quit_server():

    ret_var = proxy.call_sync(
        "Quit", None, Gio.DBusCallFlags.NO_AUTO_START, 500, None
    )
    print("Server quit")

Fire up terminal and run server.py like python3 server.py. Client is an interactive one, so run it like this : python3 -i client.py. Then in interactive prompt, do whatever you want with defined methods.

>>> say_hello("GTK")
Long live GTK
>>> print_msg("It's fun") # Go to server.py console to see message
>>> quit_server() # Quits server
Server quit

Never thought this would be so easy ! This bare-bone example covers only method calls. So in my future research, I will learn and cover about signals, authentication etc.

Also we need this example (or a better one) to be available for Python GDBus in docs and other places. So I will also take a look at that.

Thanks @ebassi @lb90 @sthursfield @curioussavage and other wonderful contributors of GNOME :smile:

4 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.