4 import xml.parsers.expat
8 from gi.repository import GObject
10 from .gobject import ScheduledFunction
12 logger = logging.getLogger("onoff.dbusutils")
14 object_prefix = "/de/subdivi/onoff0"
15 default_busname = "de.subdivi.onoff0"
17 dbus_options = argparse.ArgumentParser(add_help=False)
18 dbus_options.add_argument("--bus", default="session",
19 choices=("system", "session"),
20 help="which bus to use (default: %(default)s)")
21 dbus_options.add_argument("--busname", type=str, default=default_busname,
22 help="which busname (i.e. client) to use " +
23 "(default: %(default)s)")
24 dbus_options.add_argument("--device", type=str,
25 help="which device to control")
27 def get_dbus(namespace):
29 @param namespace: a namespace returned from a dbus_options argument parser
31 @returns: the requested bus
33 if namespace.bus == "session":
34 return dbus.SessionBus()
35 elif namespace.bus == "system":
36 return dbus.SystemBus()
38 raise AssertionError("namespace.bus %r is neither session nor system",
41 def get_dbus_proxy(namespace):
43 @param namespace: a namespace returned from a dbus_options argument parser
44 @returns: a dbus object proxy
46 bus = get_dbus(namespace)
47 if not namespace.device:
48 raise ValueError("no --device given")
49 objname = "%s/%s" % (object_prefix, namespace.device)
50 return bus.get_object(namespace.busname, objname)
53 """Create a socket pair where the latter end is already wrapped for
54 transmission over dbus.
56 @rtype: (socket, dbus.types.UnixFd)
58 s1, s2 = socket.socketpair()
59 s3 = dbus.types.UnixFd(s2)
63 def list_objects(bus, busname, path=None):
64 """List objects on the given bus and busname starting with path. Only the
65 trailing components after the slash are returned.
69 @type path: None or str
70 @param path: prefix for searching objects. Defaults to
71 dbusutils.object_prefix.
73 @returns: the trailing components of the objects found
77 xmlstring = bus.get_object(busname, path).Introspect()
78 parser = xml.parsers.expat.ParserCreate()
80 def start_element(name, attrs):
88 parser.StartElementHandler = start_element
89 parser.Parse(xmlstring)
92 class OnoffControl(dbus.service.Object):
93 domain = default_busname
96 def __init__(self, bus, name, device):
100 @type device: OnoffDevice
102 busname = dbus.service.BusName(self.domain, bus=bus)
103 dbus.service.Object.__init__(self, busname, "%s/%s" % (self.path, name))
106 device.notify.add(self.changestate)
108 @dbus.service.signal(domain, signature="q")
109 def changestate(self, state):
110 logger.debug("emitting state %d", state)
112 @dbus.service.method(domain, out_signature="q")
114 return self.device.state
116 @dbus.service.method(domain, in_signature="q", out_signature="q")
117 def activatetime(self, duration):
118 """Activate the device for a given number of seconds."""
119 logger.info("activatetime %d", duration)
120 ScheduledFunction(duration, self.unuse)
123 @dbus.service.method(domain, in_signature="", out_signature="qh")
124 def activatefd(self):
125 """Activate a device until the returned file descriptor is closed."""
126 notifyfd, retfd = socketpair()
127 logger.info("activatefd returning fd %d", notifyfd.fileno())
129 logger.info("fd %d completed", fd.fileno())
133 GObject.io_add_watch(notifyfd, GObject.IO_HUP|GObject.IO_ERR, callback)
134 return (self.use(), retfd)
138 if self.usecount <= 1:
139 self.device.activate()
140 return self.device.state
144 if not self.usecount:
145 self.device.deactivate()
147 logger.debug("%d users left", self.usecount)