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