namespace zavai {
namespace input {
-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);
+ public signal bool event(LinuxInput.Event* ev);
protected IOChannel fd = null;
protected uint fd_watch = 0;
+ public DevInput(string name, string device)
+ {
+ Object(name: "input.power_button", device: "/dev/input/event0");
+ }
+
protected void close_fd()
{
if (fd != null)
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());
char[] buf = new char[sizeof(LinuxInput.Event)];
- 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;
- }
+ 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;
+ 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);
/*
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);
+ zavai.log.info("Opening device " + device);
fd = new IOChannel.file(device, "r");
- try {
- fd.set_encoding(null);
- } catch (Error e) {
- zavai.log.error("Setting encoding to null on " + device + ": " + e.message);
- }
- 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)
{
close_fd();
}
- base.stop();
- }
+ base.stop();
+ }
+}
+
+public class HotKeys : zavai.Service
+{
+ 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 : DevInput
+public class PowerButton : zavai.Service
{
- public signal void power_button(Posix.timeval* time, bool pressed);
+ protected DevInput devinput;
+
+ public signal void power_button(Posix.timeval* time, bool pressed);
public PowerButton()
{
// FIXME: change to event0 for the power button
// FIXME: change to event4 for the aux button and headset button
- //device = "/dev/input/event1";
- Object(
- name: "input.power_button",
- device: "/dev/input/event0"
- );
+ 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");
+ }
+ }
- event += on_event;
+ 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_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
*/
+public HotKeys hotkeys = null;
public PowerButton power_button = null;
public void init()
{
- power_button = new PowerButton();
-
- zavai.registry.register_service(power_button);
+ hotkeys = new HotKeys();
+ power_button = new PowerButton();
}
}