implement a redshift device
authorHelmut Grohne <helmut@subdivi.de>
Sun, 16 Jun 2013 22:04:17 +0000 (00:04 +0200)
committerHelmut Grohne <helmut@subdivi.de>
Sun, 16 Jun 2013 22:09:25 +0000 (00:09 +0200)
dbus_client.py
dbus_service.py
onoff/process.py [new file with mode: 0644]

index 2af2a77..8316a7c 100755 (executable)
@@ -24,7 +24,7 @@ def main():
     s1, s2 = socket.socketpair()
     DBusGMainLoop(set_as_default=True)
     bus = dbus.SessionBus()
-    proxy = bus.get_object("de.subdivi.onoff", "/de/subdivi/onoff/5")
+    proxy = bus.get_object("de.subdivi.onoff", "/de/subdivi/onoff/redshift")
     if len(sys.argv) > 1:
         st = proxy.activatefd(dbus.types.UnixFd(s1), 10)
         s1.close()
index 41a6546..bbc652c 100755 (executable)
@@ -8,7 +8,8 @@ import dbus.service
 from dbus.mainloop.glib import DBusGMainLoop
 from gi.repository import GObject
 
-from onoff.common import ST_ACTIVE, ST_TRANSITION
+import onoff.common
+import onoff.process
 
 logger = logging.getLogger("dbus_service")
 
@@ -16,20 +17,20 @@ class OnoffControl(dbus.service.Object):
     domain = "de.subdivi.onoff"
     path = "/de/subdivi/onoff"
 
-    def __init__(self, bus, num):
+    def __init__(self, bus, name, device):
         busname = dbus.service.BusName(self.domain, bus=bus)
-        dbus.service.Object.__init__(self, busname, "%s/%d" % (self.path, num))
+        dbus.service.Object.__init__(self, busname, "%s/%s" % (self.path, name))
+        self.device = device
+        device.notify.add(self.changestate)
         self.usecount = 0
-        self.curstate = 0 # empty bitmask
 
     @dbus.service.signal(domain, signature="q")
     def changestate(self, st):
         logger.debug("emitting state %d", st)
-        self.curstate = st
 
     @dbus.service.method(domain, out_signature="q")
     def state(self):
-        return self.curstate
+        return self.device.state
 
     @dbus.service.method(domain, in_signature="q", out_signature="q")
     def activatetime(self, duration):
@@ -52,16 +53,13 @@ class OnoffControl(dbus.service.Object):
     def use(self):
         self.usecount += 1
         if self.usecount <= 1:
-            self.changestate(ST_ACTIVE|ST_TRANSITION)
-            def finish():
-                self.changestate(ST_ACTIVE)
-            GObject.timeout_add(200, finish)
-        return self.curstate
+            self.device.activate()
+        return self.device.state
 
     def unuse(self):
         self.usecount -= 1
         if not self.usecount:
-            self.changestate(0)
+            self.device.deactivate()
         else:
             logger.debug("%d users left", self.usecount)
         return False
@@ -72,7 +70,9 @@ def main():
     logging.getLogger().setLevel(logging.DEBUG)
     DBusGMainLoop(set_as_default=True)
     bus = dbus.SessionBus()
-    OnoffControl(bus, 5)
+    dev = onoff.process.OnoffProcess(["redshift"], 3)
+    dev = onoff.common.InvertedDevice(dev)
+    OnoffControl(bus, "redshift", dev)
     GObject.MainLoop().run()
 
 if __name__ == "__main__":
diff --git a/onoff/process.py b/onoff/process.py
new file mode 100644 (file)
index 0000000..5dd39c0
--- /dev/null
@@ -0,0 +1,80 @@
+import logging
+import os
+import signal
+
+from gi.repository import GObject
+
+from .common import ST_ACTIVE, ST_TRANSITION, OnoffDevice
+
+logger = logging.getLogger("onoff.process")
+
+class OnoffProcess(OnoffDevice):
+    def __init__(self, command, start_wait=0, termsig=signal.SIGTERM):
+        OnoffDevice.__init__(self)
+        self.command = command
+        self.start_wait = start_wait
+        self.termsig = termsig
+        self.state = 0
+        self.pid = None
+        self.starting = None # timeout event during start
+        self.watch = None # watch event
+
+    def start_process(self):
+        assert self.state == 0
+        assert self.pid is None
+        assert self.starting is None
+        logger.info("starting command %s", " ".join(self.command))
+        self.state = ST_ACTIVE|ST_TRANSITION
+        ret = GObject.spawn_async(self.command, flags=GObject.SPAWN_SEARCH_PATH | GObject.SPAWN_DO_NOT_REAP_CHILD)
+        self.pid = ret[0]
+        logger.debug("started as pid %d", self.pid)
+        self.watch = GObject.child_watch_add(self.pid, self.process_died)
+        self.starting = GObject.timeout_add(1000 * self.start_wait,
+                                            self.process_started)
+        self.changestate(self.state)
+
+    def process_died(self, pid, condition):
+        assert self.state != 0
+        assert self.pid == pid
+        assert self.watch is not None
+        self.state = 0
+        self.watch = None
+        self.pid = None
+        logger.info("process %d died", pid)
+        self.changestate(self.state)
+
+    def process_started(self):
+        assert self.state == ST_ACTIVE|ST_TRANSITION
+        assert self.starting is not None
+        self.state = ST_ACTIVE
+        self.starting = None
+        logger.debug("process started")
+        self.changestate(self.state)
+
+    def stop_process(self):
+        assert self.state & ST_ACTIVE
+        if self.state & ST_TRANSITION:
+            logger.debug("cancelling start notification")
+            assert self.starting is not None
+            ret = GObject.source_remove(self.starting)
+            assert ret
+            self.starting = None
+        self.state = ST_TRANSITION
+        logger.info("killing process %d", self.pid)
+        os.kill(self.pid, self.termsig)
+        self.changestate(self.state)
+
+    def activate(self):
+        if self.state == 0:
+            self.start_process()
+        else:
+            logger.debug("not activating from state %d", self.state)
+
+    def deactivate(self):
+        if self.state & ST_ACTIVE:
+            self.stop_process()
+        else:
+            logger.debug("not deactivating from state %d", self.state)
+
+    def close(self):
+        self.deactivate()