From 9694ec75ab1464b3699ef367f3800e60bef0cc7a Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 15 Aug 2014 20:49:23 +0200 Subject: initial import --- x11.c | 257 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 x11.c (limited to 'x11.c') 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 + +#include +#include +#include +#include +#include +#include + +#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); +} -- cgit v1.2.3