/* coveraudiod -- plays sounds while using surface touch covers Copyright (C) 2014 Javier S. Pedro This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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); }