add a sispmctl_device
[~helmut/onoff.git] / onoff / dbusutils.py
1 import argparse
2 import logging
3 import socket
4
5 import dbus
6 import dbus.service
7 from gi.repository import GObject
8
9 logger = logging.getLogger("onoff.dbusutils")
10
11 object_prefix = "/de/subdivi/onoff0"
12 default_busname = "de.subdivi.onoff0"
13
14 dbus_options = argparse.ArgumentParser(add_help=False)
15 dbus_options.add_argument("--bus", default="session",
16                           choices=("system", "session"),
17                           help="which bus to use (default: %(default)s)")
18 dbus_options.add_argument("--busname", type=str, default=default_busname,
19                           help="which busname (i.e. client) to use " +
20                                "(default: %(default)s)")
21 dbus_options.add_argument("--device", type=str,
22                           help="which device to control")
23
24 def get_dbus(namespace):
25     """
26     @param namespace: a namespace returned from a dbus_options argument parser
27     @rtype: dbus.Bus
28     @returns: the requested bus
29     """
30     if namespace.bus == "session":
31         return dbus.SessionBus()
32     elif namespace.bus == "system":
33         return dbus.SystemBus()
34     else:
35         raise AssertionError("namespace.bus %r is neither session nor system",
36                              namespace.bus)
37
38 def get_dbus_proxy(namespace):
39     """
40     @param namespace: a namespace returned from a dbus_options argument parser
41     @returns: a dbus object proxy
42     """
43     bus = get_dbus(namespace)
44     if not namespace.device:
45         raise ValueError("no --device given")
46     objname = "%s/%s" % (object_prefix, namespace.device)
47     return bus.get_object(namespace.busname, objname)
48
49 def socketpair():
50     """Create a socket pair where the latter end is already wrapped for
51     transmission over dbus.
52
53     @rtype: (socket, dbus.types.UnixFd)
54     """
55     s1, s2 = socket.socketpair()
56     s3 = dbus.types.UnixFd(s2)
57     s2.close()
58     return s1, s3
59
60 class OnoffControl(dbus.service.Object):
61     domain = default_busname
62     path = object_prefix
63
64     def __init__(self, bus, name, device):
65         """
66         @type bus: dbus.Bus
67         @type name: str
68         @type device: OnoffDevice
69         """
70         busname = dbus.service.BusName(self.domain, bus=bus)
71         dbus.service.Object.__init__(self, busname, "%s/%s" % (self.path, name))
72         self.device = device
73         self.usecount = 0
74         device.notify.add(self.changestate)
75
76     @dbus.service.signal(domain, signature="q")
77     def changestate(self, state):
78         logger.debug("emitting state %d", state)
79
80     @dbus.service.method(domain, out_signature="q")
81     def state(self):
82         return self.device.state
83
84     @dbus.service.method(domain, in_signature="q", out_signature="q")
85     def activatetime(self, duration):
86         """Activate the device for a given number of seconds."""
87         logger.info("activatetime %d", duration)
88         GObject.timeout_add(duration * 1000, self.unuse)
89         return self.use()
90
91     @dbus.service.method(domain, in_signature="", out_signature="qh")
92     def activatefd(self):
93         """Activate a device until the returned file descriptor is closed."""
94         logger.info("activatefd")
95         notifyfd, retfd = socketpair()
96         def callback(fd, _):
97             logger.info("fd %d completed", fd.fileno())
98             fd.close()
99             self.unuse()
100             return False
101         GObject.io_add_watch(notifyfd, GObject.IO_HUP|GObject.IO_ERR, callback)
102         return (self.use(), retfd)
103
104     def use(self):
105         self.usecount += 1
106         if self.usecount <= 1:
107             self.device.activate()
108         return self.device.state
109
110     def unuse(self):
111         self.usecount -= 1
112         if not self.usecount:
113             self.device.deactivate()
114         else:
115             logger.debug("%d users left", self.usecount)
116         return False