aboutsummaryrefslogtreecommitdiff
path: root/x11.c
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2014-08-15 20:49:23 +0200
committerJavier <dev.git@javispedro.com>2014-08-15 20:49:23 +0200
commit9694ec75ab1464b3699ef367f3800e60bef0cc7a (patch)
tree645c4d0fc9b8c71162cdf9f4ac572904b0bc4079 /x11.c
downloadcoveraudiod-9694ec75ab1464b3699ef367f3800e60bef0cc7a.tar.gz
coveraudiod-9694ec75ab1464b3699ef367f3800e60bef0cc7a.zip
initial import
Diffstat (limited to 'x11.c')
-rw-r--r--x11.c257
1 files changed, 257 insertions, 0 deletions
diff --git a/x11.c b/x11.c
new file mode 100644
index 0000000..3b8bcdc
--- /dev/null
+++ b/x11.c
@@ -0,0 +1,257 @@
+#include <ctype.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+#include <glib-unix.h>
+
+#include "x11.h"
+#include "key.h"
+
+#define ONLY_SURFACE_COVER 1
+
+static Display *display;
+
+static int xi_opcode;
+static int xi_event;
+static int xi_error;
+
+static Atom product_id;
+static Atom button_horiz_wheel_left;
+
+static bool is_keyboard(const XIDeviceInfo *info)
+{
+ if (!info->enabled)
+ return false;
+
+ // Surface keyboard appears as a SlavePointer sometimes
+ if (info->use != XISlaveKeyboard && info->use != XISlavePointer)
+ return false;
+
+ for (int i = 0; i < info->num_classes; i++) {
+ if (info->classes[i]->type != KeyClass)
+ continue;
+
+ const XIKeyClassInfo *key_info = (XIKeyClassInfo *)info->classes[i];
+ if (key_info->num_keycodes > 40) {
+ // If it has more than 40 keys, it is a keyboard
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool is_surface_touch_cover(const XIDeviceInfo *info)
+{
+ Atom act_type;
+ int act_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *data;
+ Status status;
+
+ // First test: product_id
+ status = XIGetProperty(display, info->deviceid, product_id, 0, 2, False, XA_INTEGER,
+ &act_type, &act_format, &nitems, &bytes_after, &data);
+
+ if (status != Success) {
+ return false;
+ }
+
+ if (act_type != XA_INTEGER || act_format != 32) {
+ XFree(data);
+ return false;
+ }
+
+ int32_t *int_data = (int32_t*)data;
+ int vendor_id = int_data[0];
+ int product_id = int_data[1];
+ XFree(data);
+
+ if (vendor_id == MICROSOFT_VENDOR_ID) {
+ if (product_id != MICROSOFT_TOUCH_COVER_1_PRODUCT_ID &&
+ product_id != MICROSOFT_TOUCH_COVER_2_PRODUCT_ID) {
+ return false;
+ }
+ // TODO: Touch cover 2
+ } else {
+ return false;
+ }
+
+ // Second test: check if it has a horizontal wheel (all Surface covers 'have' one)
+ // This way we ignore the device with the volume buttons.
+ for (int i = 0; i < info->num_classes; i++) {
+ if (info->classes[i]->type != ButtonClass)
+ continue;
+
+ const XIButtonClassInfo *btn_info = (XIButtonClassInfo *)info->classes[i];
+ for (int j = 0; j < btn_info->num_buttons; j++) {
+ if (btn_info->labels[j] == button_horiz_wheel_left) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+static bool find_and_select_devices()
+{
+ int ndevices;
+ XIDeviceInfo *info = XIQueryDevice(display, XIAllDevices, &ndevices);
+
+ if (!info) return false;
+
+ for (int i = 0; i < ndevices; i++) {
+ // Looking for keyboards only currently
+ if (!is_keyboard(&info[i])) continue;
+#if ONLY_SURFACE_COVER
+ if (!is_surface_touch_cover(&info[i])) continue;
+#endif
+
+ g_debug("Capturing events from '%s'", info[i].name);
+
+ // Subscribe to this device's events
+ XIEventMask mask;
+ mask.deviceid = info[i].deviceid;
+ mask.mask_len = XIMaskLen(XI_LASTEVENT);
+ mask.mask = calloc(mask.mask_len, 1);
+ XISetMask(mask.mask, XI_KeyPress);
+ XISelectEvents(display, DefaultRootWindow(display), &mask, 1);
+ free(mask.mask);
+ }
+
+ XIFreeDeviceInfo(info);
+ return true;
+}
+
+static void handle_key_press(const XIDeviceEvent *event)
+{
+ // Convert into an XKeyEvent
+ XKeyEvent kevent;
+ kevent.serial = event->serial;
+ kevent.send_event = event->send_event;
+ kevent.display = event->display;
+ kevent.same_screen = True;
+ kevent.window = event->event;
+ kevent.root = event->root;
+ kevent.subwindow = event->child;
+ kevent.time = event->time;
+ kevent.x = event->event_x;
+ kevent.y = event->event_y;
+ kevent.x_root = event->root_x;
+ kevent.y_root = event->root_y;
+ kevent.state = event->mods.effective;
+ kevent.keycode = event->detail;
+
+ char buf[4];
+ KeySym sym;
+
+ int len = XLookupString(&kevent, buf, sizeof(buf), &sym, NULL);
+
+ bool is_dead_key;
+ switch (len) {
+ case 0:
+ is_dead_key = true;
+ break;
+ case 1:
+ is_dead_key = iscntrl(buf[0]) || isblank(buf[0]);
+ break;
+ default:
+ is_dead_key = false;
+ break;
+ }
+
+ handle_keypress(is_dead_key);
+}
+
+static void handle_xi_event(const XGenericEventCookie *event)
+{
+ switch (event->evtype) {
+ case XI_HierarchyChanged:
+ g_debug("Hierarchy changed event");
+ find_and_select_devices();
+ break;
+ case XI_KeyPress:
+ handle_key_press(event->data);
+ break;
+ case XI_RawKeyPress:
+ g_debug("Raw key press");
+ break;
+ }
+}
+
+static gboolean handle_x11_fd(gint fd, GIOCondition condition, gpointer data)
+{
+ while (XPending(display)) {
+ XEvent event;
+ XNextEvent(display, &event);
+
+ switch (event.type) {
+ case GenericEvent:
+ if (XGetEventData(display, &event.xcookie)) {
+ if (event.xcookie.extension == xi_opcode) {
+ handle_xi_event(&event.xcookie);
+ }
+ XFreeEventData(display, &event.xcookie);
+ }
+ break;
+ }
+
+ // Ignoring events
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+bool x11_init()
+{
+ display = XOpenDisplay(NULL);
+ if (!display) {
+ g_warning("Cannot connect to X display");
+ return false;
+ }
+
+ if (!XQueryExtension(display, INAME, &xi_opcode, &xi_event, &xi_error)) {
+ g_warning("X Input extension not available");
+ return false;
+ }
+
+ int major = 2;
+ int minor = 0;
+ if (XIQueryVersion(display, &major, &minor) != Success) {
+ g_warning("X Input 2.0 not available");
+ return false;
+ }
+
+ product_id = XInternAtom(display, "Device Product ID", False);
+ button_horiz_wheel_left = XInternAtom(display, "Button Horiz Wheel Left", False);
+
+ // Setup a mask for hierarchy changed events
+ XIEventMask mask;
+ mask.deviceid = XIAllDevices;
+ mask.mask_len = XIMaskLen(XI_LASTEVENT);
+ mask.mask = calloc(mask.mask_len, 1);
+ XISetMask(mask.mask, XI_HierarchyChanged);
+ XISelectEvents(display, DefaultRootWindow(display), &mask, 1);
+ free(mask.mask);
+
+ if (!find_and_select_devices()) {
+ return false;
+ }
+
+ // Prepare integration with glib event loop
+ g_unix_fd_add(ConnectionNumber(display), G_IO_IN,
+ handle_x11_fd, NULL);
+ XFlush(display);
+
+ return true;
+}
+
+void x11_close()
+{
+ XCloseDisplay(display);
+}