]> ToastFreeware Gitweb - gregoa/zavai.git/blobdiff - src/input.vala
Merge branch 'master' into gregoa
[gregoa/zavai.git] / src / input.vala
index 7b99f1a1b688001698ee61c6fbaa355fb1170e50..ffa0add9fad3fffc5f9bce4216c0956e6417f0fd 100644 (file)
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-/*
-import sys
-import os
-import os.path
-import time
-import struct
-import signal
-import optparse
-import dbus
-import dbus.mainloop.glib
-import gobject
-import subprocess
-*/
-
 namespace zavai {
 namespace input {
 
-// For a list of dbus services, look in /etc/dbus-1/system.d/
-public abstract class DevInput : zavai.Service
+public class DevInput : zavai.Service
 {
-       public string device { get; construct; }
+    public string device { get; construct; }
+
+    public signal bool event(LinuxInput.Event* ev);
 
     protected IOChannel fd = null;
     protected uint fd_watch = 0;
 
-       public DevInput()
-       {
-               //usage.ResourceChanged += on_resourcechanged;
-       }
+    public DevInput(string name, string device)
+    {
+        Object(name: "input.power_button", device: "/dev/input/event0");
+    }
 
     protected void close_fd()
     {
@@ -62,25 +49,23 @@ public abstract class DevInput : zavai.Service
         }
     }
 
-    /*
-       public void on_resourcechanged(dynamic DBus.Object pos, string name, bool state, HashTable<string, Value?> attributes)
-       {
-               zavai.log.info("RESOURCE CHANGED " + name);
-       }
-    */
-
     protected bool on_input_data(IOChannel source, IOCondition condition)
     {
-               if (condition != IOCondition.IN) return true;
+        if (condition != IOCondition.IN) return true;
 
-        stderr.printf("GOT INPUT ON %s %d\n", device, source.unix_get_fd());
+        //stderr.printf("GOT INPUT ON %s %d\n", device, source.unix_get_fd());
         char[] buf = new char[sizeof(LinuxInput.Event)];
-               size_t count_read;
-               source.read_chars(buf, out count_read);
-        stderr.printf("READ %zu chars\n", count_read);
+        size_t count_read;
+        try {
+            source.read_chars(buf, out count_read);
+        } catch (Error e) {
+            zavai.log.error("Reading from " + device + ": " + e.message);
+            return true;
+        }
+        //stderr.printf("READ %zu chars\n", count_read);
 
-               LinuxInput.Event* ie = (LinuxInput.Event*)buf;
-        stderr.printf("INPUT EVENT time %lu.%lu type %hu code %hu val %d\n", (ulong)ie->time.tv_sec, ie->time.tv_usec, ie->type, ie->code, ie->val);
+        LinuxInput.Event* ie = (LinuxInput.Event*)buf;
+        //stderr.printf("INPUT EVENT time %lu.%lu type %hu code %hu val %d\n", (ulong)ie->time.tv_sec, ie->time.tv_usec, ie->type, ie->code, ie->val);
 
         /*
         ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
@@ -103,30 +88,35 @@ public abstract class DevInput : zavai.Service
                 info("Headset plugged out")
                 self.mixer_for_handset(self)
         */
-        return true;
+        return event(ie);
     }
 
-       /// Start reading from the device
-       public override void start()
-       {
-               if (started) return;
+    /// Start reading from the device
+    public override void start()
+    {
+        if (started) return;
 
         if (fd != null)
             close_fd();
 
         // Open the device and listed to it using the GObject main loop
+        zavai.log.info("Opening device " + device);
         fd = new IOChannel.file(device, "r");
-               fd.set_encoding(null);
-               fd.set_buffered(false);
+        try {
+            fd.set_encoding(null);
+        } catch (Error e) {
+            zavai.log.error("Setting encoding to null on " + device + ": " + e.message);
+        }
+        fd.set_buffered(false);
         fd_watch = fd.add_watch(IOCondition.IN, on_input_data);
 
-               base.start();
-       }
+        base.start();
+    }
 
-       // Stop reading from the device
-       public override void stop()
-       {
-               if (!started) return;
+    // Stop reading from the device
+    public override void stop()
+    {
+        if (!started) return;
 
         if (fd != null)
         {
@@ -134,21 +124,152 @@ public abstract class DevInput : zavai.Service
             close_fd();
         }
 
-               base.stop();
-       }
+        base.stop();
+    }
 }
 
-public class Buttons : DevInput
+public class HotKeys : zavai.Service
 {
-    public Buttons()
+    protected List<int> grabbed;
+    public signal bool hotkey(uint keycode, ulong time, bool pressed);
+
+    public HotKeys()
+    {
+        Object(name: "input.hotkeys");
+
+        grabbed = new List<int>();
+    }
+
+    // Hotkey handlink takes inspiration from
+    // http://old.nabble.com/-PATCH--Initial-non-working-implementation-of-wxWindow::%28Un%29RegisterHotKey-on-wxGTK-td14557263.html
+    private static Gdk.FilterReturn on_hotkey(Gdk.XEvent* xevent, Gdk.Event? event, void* data)
+    {
+        // Global events don't get their data translated to gdk, as there is no
+        // GdkWindow to work with, therefore we need to use the xevent, because
+        // GdkEvent* is always GDK_NOTHING
+        X.Event* xev = (X.Event*)xevent;
+
+        switch (xev->type)
+        {
+            case X.EventType.KeyPress:
+                return zavai.input.hotkeys.my_on_hotkey(xev, true);
+            case X.EventType.KeyRelease:
+                return zavai.input.hotkeys.my_on_hotkey(xev, false);
+            default:
+                return Gdk.FilterReturn.CONTINUE;
+        }
+    }
+
+    //public Gdk.FilterReturn on_hotkey(Gdk.XEvent xevent, Gdk.Event? event)
+    private Gdk.FilterReturn my_on_hotkey(X.Event* xev, bool pressed)
+    {
+        // From http://tronche.com/gui/x/xlib/input/pointer-grabbing.html:
+        //
+        // A timestamp is a time value, expressed in milliseconds. It typically is the
+        // time since the last server reset. Timestamp values wrap around (after about
+        // 49.7 days). The server, given its current time is represented by timestamp
+        // T, always interprets timestamps from clients by treating half of the
+        // timestamp space as being later in time than T. One timestamp value, named
+        // CurrentTime, is never generated by the server. This value is reserved for
+        // use in requests to represent the current server time. 
+
+        if (grabbed.index((int)xev->xkey.keycode) == -1)
+            return Gdk.FilterReturn.CONTINUE;
+
+        if (hotkey(xev->xkey.keycode, xev->xkey.time, pressed))
+            return Gdk.FilterReturn.REMOVE;
+        return Gdk.FilterReturn.CONTINUE;
+    }
+
+    public void grab(int keycode, int modifiers, bool owner_events)
+    {
+        // We need to grab the keys we want to listen to
+        int res = Gdk.x11_get_default_xdisplay().grab_key(keycode, modifiers, Gdk.x11_get_default_root_xwindow(), owner_events, X.GrabMode.Async, X.GrabMode.Async);
+        if (res != 0)
+            stderr.printf("Grab result: %d\n", res); // We get BadRequest and don't know why
+        grabbed.append(keycode);
+    }
+
+    /// Start reading from the device
+    public override void start()
+    {
+        if (started) return;
+
+        //gdk_window_add_filter (NULL, _wxgtk_global_hotkey_callback, this);
+        ((Gdk.Window*)null)->add_filter((Gdk.FilterFunc)on_hotkey);
+
+        grab(160, 0, false);
+
+        base.start();
+    }
+
+    // Stop reading from the device
+    public override void stop()
+    {
+        if (!started) return;
+
+        //gdk_window_remove_filter(NULL, _wxgtk_global_hotkey_callback, this); 
+        ((Gdk.Window*)null)->remove_filter((Gdk.FilterFunc)on_hotkey);
+
+        base.stop();
+    }
+}
+
+public class PowerButton : zavai.Service
+{
+    protected DevInput devinput;
+
+    public signal void power_button(Posix.timeval* time, bool pressed);
+
+    public PowerButton()
     {
-        name = "input.buttons";
         // FIXME: change to event0 for the power button
         // FIXME: change to event4 for the aux button and headset button
-        device = "/dev/input/event1";
+        string inputdev = "/dev/input/event0";
+        if (Posix.access(inputdev, Posix.R_OK) == 0)
+        {
+            zavai.log.info("Handle power button via " + inputdev);
+            // Listen via input device
+            devinput = new DevInput("input.power_button", "/dev/input/event0");
+            devinput.event += on_event;
+            devinput.request("powerbutton");
+        } else {
+            zavai.log.info("Handle power button via XGrabKey on keycode " + zavai.config.power_button_keycode.to_string());
+            // Listen via X
+            hotkeys.hotkey += on_hotkey;
+            hotkeys.grab(zavai.config.power_button_keycode, 0, false);
+            hotkeys.request("powerbutton");
+        }
+    }
+
+    protected bool on_event(LinuxInput.Event* ev)
+    {
+        if (ev->type == LinuxInput.Type.KEY && 
+            ev->code == LinuxInput.Key.POWER)
+        {
+            power_button(&(ev->time), ev->val == 0 ? false : true);
+        }
+        return true;
+    }
+
+    protected bool on_hotkey(uint keycode, ulong time, bool pressed)
+    {
+        if (keycode == zavai.config.power_button_keycode)
+        {
+            // Convert X time to a fake timeval
+            // TODO: handle wraparound
+            Posix.timeval tv = {
+                (time_t)(time / 1000),
+                (long)((time % 1000) * 1000)
+            };
+            power_button(&tv, pressed);
+            return true;
+        }
+        return false;
     }
 }
 
+
 /*
 # TODO:
 #  - hook into the headset plugged/unplugged event
@@ -288,348 +409,16 @@ class Audio:
         res = proc.wait()
         if res != 0:
             raise RuntimeError("Setting mixer failed")
-
-
-# For a list of dbus services, look in /etc/dbus-1/system.d/
-class GPS():
-    def __init__(self, bus = None):
-        if bus is None:
-            self.bus = dbus.SystemBus()
-        else:
-            self.bus = bus
-
-        # see mdbus -s org.freesmartphone.ousaged /org/freesmartphone/Usage
-        self.usage = self.bus.get_object('org.freesmartphone.ousaged', '/org/freesmartphone/Usage')
-        self.usage = dbus.Interface(self.usage, "org.freesmartphone.Usage")
-
-        # see mdbus -s org.freesmartphone.ogpsd /org/freedesktop/Gypsy
-        gps = self.bus.get_object('org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy') 
-        self.gps = dbus.Interface(gps, "org.freedesktop.Gypsy.Device")
-        self.gps_time = dbus.Interface(gps, "org.freedesktop.Gypsy.Time")
-        self.gps_position = dbus.Interface(gps, 'org.freedesktop.Gypsy.Position')
-        self.gps_ubx = dbus.Interface(gps, 'org.freesmartphone.GPS.UBX')
-        self.gps_debug = GPSDebug(self.gps_ubx)
-
-        # Request GPS resource
-        self.usage.RequestResource('GPS')
-        info("Acquired GPS")
-
-        self.waiting_for_fix = None
-
-    def close(self):
-        self.usage.ReleaseResource('GPS')
-        info("Released GPS")
-
-    def wait_for_fix(self, callback):
-        status = self.gps.GetFixStatus()
-        if status in [2, 3]:
-            info("We already have a fix, good.")
-            callback()
-            return True
-        else:
-            info("Waiting for a fix...")
-            self.waiting_for_fix = callback
-            self.bus.add_signal_receiver(
-                self.on_fix_status_changed, 'FixStatusChanged', 'org.freedesktop.Gypsy.Device',
-                'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy')
-            return False
-
-    def on_fix_status_changed(self, status):
-        if status not in [2, 3]: return
-
-        info("Got GPS fix")
-        self.bus.remove_signal_receiver(
-            self.on_fix_status_changed, 'FixStatusChanged', 'org.freedesktop.Gypsy.Device',
-            'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy')
-
-        if self.waiting_for_fix:
-            self.waiting_for_fix()
-            self.waiting_for_fix = None
-
-    def track_position(self, callback):
-        self.bus.add_signal_receiver(
-            callback, 'PositionChanged', 'org.freedesktop.Gypsy.Position',
-            'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy')
-
-# This is taken from Zhone
-class GPSDebug():
-    def __init__(self, gps):
-        self.gps_ubx = gps.gps_ubx
-        self.busy = None
-        self.want = set( ["NAV-STATUS", "NAV-SVINFO"] )
-        self.have = set()
-        self.error = set()
-
-    def _update( self ):
-        if self.busy is None:
-            pending = self.want - self.have - self.error
-            if pending:
-                self.busy = pending.pop()
-                self.gps_ubx.SetDebugFilter(
-                    self.busy,
-                    True,
-                    reply_handler=self.on_debug_reply,
-                    error_handler=self.on_debug_error,
-                )
-
-    def request(self):
-        self.have = set()
-        self._update()
-
-    def on_debug_reply(self):
-        self.have.add(self.busy)
-        self.busy = None
-        self._update()
-
-    def on_debug_error(self, e):
-        info(e, "error while requesting debug packet %s" % self.busy)
-        self.error.add(self.busy)
-        self.busy = None
-        self._update()
-
-class GPSMonitor():
-    def __init__(self, gps):
-        self.gps = gps
-        self.gps_debug = GPSDebug(gps)
-
-    def start(self):
-        # TODO: find out how come sometimes these events are not sent
-        self.gps.bus.add_signal_receiver(
-            self.on_satellites_changed, 'SatellitesChanged', 'org.freedesktop.Gypsy.Satellite',
-            'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy')
-        self.gps.bus.add_signal_receiver(
-            self.on_ubxdebug_packet, 'DebugPacket', 'org.freesmartphone.GPS.UBX',
-            'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy')
-        self.gps_debug.request()
-
-    def stop(self):
-        self.gps.bus.remove_signal_receiver(
-            self.on_satellites_changed, 'SatellitesChanged', 'org.freedesktop.Gypsy.Satellite',
-            'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy')
-        self.gps.bus.remove_signal_receiver(
-            self.on_ubxdebug_packet, 'DebugPacket', 'org.freesmartphone.GPS.UBX',
-            'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy')
-
-    def on_satellites_changed(self, satellites):
-        #if not satellites:
-        #    info("Satellite status: none")
-        #    return
-        self.gps_debug.request()
-        #info("Satellite status:")
-        #for sat in satellites:
-        #    if sat[0] not in self.sat_data:
-        #        self.sat_data[sat[0]] = [sat, None]
-        #    else:
-        #        self.sat_data[sat[0]][0] = sat
-        #self.print_sat_data()
-
-    def on_ubxdebug_packet(self, clid, length, data):
-        # In zhone it is cbUBXDebugPacket
-        #if clid == "NAV-STATUS" and data:
-        #    i = ["%s: %d" % (k, data[0][k]) for k in sorted(data[0].keys())]
-        #    info("Status:", " ".join(i))
-        ##    if data[0]['TTFF']:
-        ##        info("TTFF: %f", data[0]['TTFF']/1000.0)
-        if clid == "NAV-SVINFO":
-            self.print_ubx_sat_data(data[1:])
-        #else:
-        #    info("gps got ubxdebug packet", clid)
-        #    info("DATA:", data)
-
-    def print_ubx_sat_data(self, ubxinfo):
-        info("CH ID SN ELE AZI Used Diff Alm Eph Bad Status")
-        for sv in ubxinfo:
-            if sv["CNO"] == 0: continue
-            svid = sv["SVID"]
-            used = sv["Flags"] & 0x01
-            diff = sv["Flags"] & 0x02
-            almoreph = sv["Flags"] & 0x04
-            eph = sv["Flags"] & 0x08
-            bad = sv["Flags"] & 0x10
-            qi = ("%i: " % sv["QI"]) + {
-                0: "idle",
-                1: "searching",
-                2: "signal acquired",
-                3: "signal unusable",
-                4: "code lock",
-                5: "code&carrier lock",
-                6: "code&carrier lock",
-                7: "receiving data"
-            }[sv["QI"]]
-            info("%2d %2d %2d %3d %3d %s %s %s %s %s %s" % (
-                sv["chn"], sv["SVID"],
-                sv["CNO"], sv["Elev"], sv["Azim"],
-                used and "used" or "----",
-                diff and "diff" or "----",
-                almoreph and "alm" or "---",
-                eph and "eph" or "---",
-                bad and "bad" or "---",
-                qi))
-
-    def print_sat_data(self, satellites):
-        for sat in satellites:
-            if sat[4] == 0: continue
-            info("PRN %u" % sat[0],
-                 sat[1] and "used" or "unused",
-                 "el %u" % sat[2],
-                 "az %u" % sat[3],
-                 "snr %u" % sat[4])
-
-
-class Hub:
-    """Hub that manages all the various resources that we use, and initiates
-    operations."""
-    def __init__(self, bus = None):
-        self.bus = bus
-        self.waiting_for_fix = False
-        self.basename = None
-        self.recorder = None
-        self.gpx = None
-        self.gps = None
-        self.gps_monitor = None
-        self.audio = None
-        self.last_pos = None
-
-    def shutdown(self):
-        # Stop recording
-        if self.audio is not None:
-            self.audio.close()
-
-        # Close waypoints file
-        if self.gpx is not None:
-            self.gpx.close()
-
-        # Stop the GPS monitor
-        if self.gps_monitor:
-            self.gps_monitor.stop()
-
-        # Release the GPS
-        if self.gps is not None:
-            self.gps.close()
-
-    def levels(self):
-        self.audio = Audio()
-        self.audio.start_levels()
-
-    def record(self):
-        self.audio = Audio(self.make_waypoint)
-        self.gps = GPS()
-        # Get a fix and start recording
-        if not self.gps.wait_for_fix(self.start_recording):
-            self.gps_monitor = GPSMonitor(self.gps)
-            self.gps_monitor.start()
-
-    def monitor(self):
-        self.audio = None
-        self.gps = GPS()
-        self.gps_monitor = GPSMonitor(self.gps)
-        self.gps_monitor.start()
-
-    def start_recording(self):
-        if self.gps_monitor:
-            self.gps_monitor.stop()
-            self.gps_monitor = None
-
-        if not self.audio:
-            return
-
-        # Sync system time
-        gpstime = self.gps.gps_time.GetTime()
-        subprocess.call(["date", "-s", "@%d" % gpstime])
-        subprocess.call(["hwclock", "--systohc"])
-
-        # Compute basename for output files
-        self.basename = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(gpstime))
-        self.basename = os.path.join(AUDIODIR, self.basename)
-
-        # Start recording the GPX track
-        self.gpx = GPX(self.basename)
-        self.gps.track_position(self.on_position_changed)
-
-        # Start recording in background forking arecord
-        self.audio.set_basename(self.basename)
-        self.audio.start_recording()
-
-    def on_position_changed(self, fields, tstamp, lat, lon, alt):
-        self.last_pos = (fields, tstamp, lat, lon, alt)
-        if self.gpx:
-            self.gpx.trackpoint(tstamp, lat, lon, alt)
-
-    def make_waypoint(self):
-        if self.gpx is None:
-            return
-        if self.last_pos is None:
-            self.last_pos = self.gps.gps_position.GetPosition()
-        (fields, tstamp, lat, lon, alt) = self.last_pos
-        self.gpx.waypoint(tstamp, lat, lon, alt)
-        info("Making waypoint at %s: %f, %f, %f" % (
-            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(tstamp)), lat, lon, alt))
-
-class Parser(optparse.OptionParser):
-    def __init__(self, *args, **kwargs):
-        # Yes, in 2009 optparse from the *standard library* still uses old
-        # style classes
-        optparse.OptionParser.__init__(self, *args, **kwargs)
-
-    def error(self, msg):
-        sys.stderr.write("%s: error: %s\n\n" % (self.get_prog_name(), msg))
-        self.print_help(sys.stderr)
-        sys.exit(2)
-
-parser = Parser(usage="usage: %prog [options]",
-                version="%prog "+ VERSION,
-                description="Create a GPX and audio trackFind the times in the wav file when there is clear voice among the noise")
-parser.add_option("-v", "--verbose", action="store_true", help="verbose mode")
-parser.add_option("-m", "--monitor", action="store_true", help="only keep the GPS on and monitor satellite status")
-parser.add_option("-l", "--levels", action="store_true", help="only show input levels")
-
-(opts, args) = parser.parse_args()
-
-if not opts.monitor and not opts.verbose:
-    def info(*args):
-        pass
-
-# Set up dbus
-dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-mainloop = gobject.MainLoop()
-
-def on_sigint(signum, frame):
-    mainloop.quit()
-
-signal.signal(signal.SIGINT, on_sigint)
-
-hub = Hub()
-
-if opts.monitor:
-    hub.monitor()
-elif opts.levels:
-    hub.levels()
-else:
-    hub.record()
-
-mainloop.run()
-
-hub.shutdown()
-
-# Create waypoint at button press
-
-# At button press, raise window
-# Keep window until after 5 seconds it gets input, or until dismissed
-# Allow to choose "do not show"
-# Request input method when window is raised (or start a keyboard and kill it when lowered)
-
-# Release GPS
 */
 
-public Buttons buttons = null;
+
+public HotKeys hotkeys = null;
+public PowerButton power_button = null;
 
 public void init()
 {
-       buttons = new Buttons();
-
-       zavai.registry.register_service(buttons);
-
-       stderr.printf("ANTANI %d\n", LinuxInput.Key.POWER);
+    hotkeys = new HotKeys();
+    power_button = new PowerButton();
 }
 
 }