I want to make a program that has two parts. A listener (a server, if you will) and a sender (the client). I did some research and learned that this is done via a method program
There's not a lot of documentation for dbus in python3, but I managed to figure it out so I'll document it here: The major difference from all the python2 examples is replacing import gobject
with import gi.repository.GLib
.
You can find more examples (which use more features than I needed) in the dbus-python examples directory.
I didn't implement self-backgrounding in the server because that style of daemon has gone out of style recently.
common.py:
# well-known name for our program
ECHO_BUS_NAME = 'com.stackoverflow.question_21793826.EchoService'
# interfaces implemented by some objects in our program
ECHO_INTERFACE = 'com.stackoverflow.question_21793826.EchoInterface'
QUIT_INTERFACE = 'com.stackoverflow.question_21793826.QuitInterface'
# paths to some objects in our program
ECHO_OBJECT_PATH = '/EchoServerObject'
server.py:
#!/usr/bin/env python3
# standard includes
import sys
# dbus includes
import gi.repository.GLib
import dbus
import dbus.service
import dbus.mainloop.glib
# project includes
import common
class EchoServerObject(dbus.service.Object):
# TODO it would be nice to make a better decorator using annotations:
# def foo(self, a: 's', b: 's') -> '': pass
# but the existing dbus decorator does its own reflection which
# fails if there are any annotations (or keyword-only arguments)
@dbus.service.method(common.ECHO_INTERFACE,
in_signature='s', out_signature='')
def echo(self, message):
message = str(message) # get rid of subclass for repr
print('server: a client said %r' % message)
@dbus.service.method(common.QUIT_INTERFACE,
in_signature='', out_signature='')
def quit(self):
# this should be a separate object, but I'm
# showing how one object can have multiple interfaces
self.mainloop.quit()
def stop():
bus = dbus.SessionBus()
proxy = bus.get_object(common.ECHO_BUS_NAME, common.ECHO_OBJECT_PATH)
iface = dbus.Interface(proxy, common.QUIT_INTERFACE)
iface.quit()
def server():
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
try:
name = dbus.service.BusName(common.ECHO_BUS_NAME, bus, do_not_queue=True)
except dbus.NameExistsException:
sys.exit('Server is already running.')
else:
print('Server is not running yet. Putting on listening ears.')
echo = EchoServerObject(bus, common.ECHO_OBJECT_PATH)
mainloop = gi.repository.GLib.MainLoop()
echo.mainloop = mainloop
mainloop.run()
def main(exe, args):
if args == ['stop']:
stop()
elif not args:
server()
else:
sys.exit('Usage: %s [stop]' % exe)
if __name__ == '__main__':
main(sys.argv[0], sys.argv[1:])
client.py:
#!/usr/bin/env python3
# standard includes
import sys
# dbus includes
import dbus
# project includes
import common
def client(mes):
bus = dbus.SessionBus()
try:
proxy = bus.get_object(common.ECHO_BUS_NAME, common.ECHO_OBJECT_PATH)
except dbus.DBusException as e:
# There are actually two exceptions thrown:
# 1: org.freedesktop.DBus.Error.NameHasNoOwner
# (when the name is not registered by any running process)
# 2: org.freedesktop.DBus.Error.ServiceUnknown
# (during auto-activation since there is no .service file)
# TODO figure out how to suppress the activation attempt
# also, there *has* to be a better way of managing exceptions
if e._dbus_error_name != 'org.freedesktop.DBus.Error.ServiceUnknown':
raise
if e.__context__._dbus_error_name != 'org.freedesktop.DBus.Error.NameHasNoOwner':
raise
print('client: No one can hear me!!')
else:
iface = dbus.Interface(proxy, common.ECHO_INTERFACE)
iface.echo(mes)
def main(exe, args):
if args:
client(' '.join(args))
else:
sys.exit('Usage: %s message...' % exe)
if __name__ == '__main__':
main(sys.argv[0], sys.argv[1:])