/* * Copyright © 2011 Intel Corp. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Author: Emmanuele Bassi */ #include "config.h" #include #include #include "backends/x11/meta-backend-x11.h" #include "backends/x11/meta-device-manager-x11.h" #include "backends/x11/meta-event-x11.h" #include "backends/x11/meta-input-device-x11.h" #include "backends/x11/meta-input-device-tool-x11.h" #include "backends/x11/meta-keymap-x11.h" #include "backends/x11/meta-stage-x11.h" #include "backends/x11/meta-virtual-input-device-x11.h" #include "backends/x11/meta-xkb-a11y-x11.h" #include "clutter/clutter-mutter.h" #include "clutter/x11/clutter-x11.h" #include "core/display-private.h" #include "meta/meta-x11-errors.h" enum { PROP_0, PROP_OPCODE, PROP_LAST }; static GParamSpec *obj_props[PROP_LAST] = { NULL, }; static const char *clutter_input_axis_atom_names[] = { "Abs X", /* CLUTTER_INPUT_AXIS_X */ "Abs Y", /* CLUTTER_INPUT_AXIS_Y */ "Abs Pressure", /* CLUTTER_INPUT_AXIS_PRESSURE */ "Abs Tilt X", /* CLUTTER_INPUT_AXIS_XTILT */ "Abs Tilt Y", /* CLUTTER_INPUT_AXIS_YTILT */ "Abs Wheel", /* CLUTTER_INPUT_AXIS_WHEEL */ "Abs Distance", /* CLUTTER_INPUT_AXIS_DISTANCE */ }; #define N_AXIS_ATOMS G_N_ELEMENTS (clutter_input_axis_atom_names) static const char *wacom_type_atoms[] = { "STYLUS", "CURSOR", "ERASER", "PAD", "TOUCH" }; #define N_WACOM_TYPE_ATOMS G_N_ELEMENTS (wacom_type_atoms) enum { WACOM_TYPE_STYLUS, WACOM_TYPE_CURSOR, WACOM_TYPE_ERASER, WACOM_TYPE_PAD, WACOM_TYPE_TOUCH, }; enum { PAD_AXIS_FIRST = 3, /* First axes are always x/y/pressure, ignored in pads */ PAD_AXIS_STRIP1 = PAD_AXIS_FIRST, PAD_AXIS_STRIP2, PAD_AXIS_RING1, PAD_AXIS_RING2, }; static Atom clutter_input_axis_atoms[N_AXIS_ATOMS] = { 0, }; G_DEFINE_TYPE (MetaDeviceManagerX11, meta_device_manager_x11, CLUTTER_TYPE_DEVICE_MANAGER) static void meta_device_manager_x11_copy_event_data (ClutterDeviceManager *device_manager, const ClutterEvent *src, ClutterEvent *dest) { gpointer event_x11; event_x11 = _clutter_event_get_platform_data (src); if (event_x11 != NULL) _clutter_event_set_platform_data (dest, meta_event_x11_copy (event_x11)); } static void meta_device_manager_x11_free_event_data (ClutterDeviceManager *device_manager, ClutterEvent *event) { gpointer event_x11; event_x11 = _clutter_event_get_platform_data (event); if (event_x11 != NULL) meta_event_x11_free (event_x11); } static void translate_valuator_class (Display *xdisplay, ClutterInputDevice *device, XIValuatorClassInfo *class) { static gboolean atoms_initialized = FALSE; ClutterInputAxis i, axis = CLUTTER_INPUT_AXIS_IGNORE; if (G_UNLIKELY (!atoms_initialized)) { XInternAtoms (xdisplay, (char **) clutter_input_axis_atom_names, N_AXIS_ATOMS, False, clutter_input_axis_atoms); atoms_initialized = TRUE; } for (i = 0; i < N_AXIS_ATOMS; i += 1) { if (clutter_input_axis_atoms[i] == class->label) { axis = i + 1; break; } } _clutter_input_device_add_axis (device, axis, class->min, class->max, class->resolution); g_debug ("Added axis '%s' (min:%.2f, max:%.2fd, res:%d) of device %d", clutter_input_axis_atom_names[axis], class->min, class->max, class->resolution, device->id); } static void translate_device_classes (Display *xdisplay, ClutterInputDevice *device, XIAnyClassInfo **classes, int n_classes) { int i; for (i = 0; i < n_classes; i++) { XIAnyClassInfo *class_info = classes[i]; switch (class_info->type) { case XIKeyClass: { XIKeyClassInfo *key_info = (XIKeyClassInfo *) class_info; int j; _clutter_input_device_set_n_keys (device, key_info->num_keycodes); for (j = 0; j < key_info->num_keycodes; j++) { clutter_input_device_set_key (device, j, key_info->keycodes[i], 0); } } break; case XIValuatorClass: translate_valuator_class (xdisplay, device, (XIValuatorClassInfo *) class_info); break; case XIScrollClass: { XIScrollClassInfo *scroll_info = (XIScrollClassInfo *) class_info; ClutterScrollDirection direction; if (scroll_info->scroll_type == XIScrollTypeVertical) direction = CLUTTER_SCROLL_DOWN; else direction = CLUTTER_SCROLL_RIGHT; g_debug ("Scroll valuator %d: %s, increment: %f", scroll_info->number, scroll_info->scroll_type == XIScrollTypeVertical ? "vertical" : "horizontal", scroll_info->increment); _clutter_input_device_add_scroll_info (device, scroll_info->number, direction, scroll_info->increment); } break; default: break; } } } static gboolean is_touch_device (XIAnyClassInfo **classes, int n_classes, ClutterInputDeviceType *device_type, uint32_t *n_touch_points) { int i; for (i = 0; i < n_classes; i++) { XITouchClassInfo *class = (XITouchClassInfo *) classes[i]; if (class->type != XITouchClass) continue; if (class->num_touches > 0) { if (class->mode == XIDirectTouch) *device_type = CLUTTER_TOUCHSCREEN_DEVICE; else if (class->mode == XIDependentTouch) *device_type = CLUTTER_TOUCHPAD_DEVICE; else continue; *n_touch_points = class->num_touches; return TRUE; } } return FALSE; } static gboolean is_touchpad_device (XIDeviceInfo *info) { gulong nitems, bytes_after; uint32_t *data = NULL; int rc, format; Atom type; Atom prop; prop = XInternAtom (clutter_x11_get_default_display (), "libinput Tapping Enabled", True); if (prop == None) return FALSE; clutter_x11_trap_x_errors (); rc = XIGetProperty (clutter_x11_get_default_display (), info->deviceid, prop, 0, 1, False, XA_INTEGER, &type, &format, &nitems, &bytes_after, (guchar **) &data); clutter_x11_untrap_x_errors (); /* We don't care about the data */ XFree (data); if (rc != Success || type != XA_INTEGER || format != 8 || nitems != 1) return FALSE; return TRUE; } static gboolean get_device_ids (XIDeviceInfo *info, char **vendor_id, char **product_id) { gulong nitems, bytes_after; uint32_t *data = NULL; int rc, format; Atom type; clutter_x11_trap_x_errors (); rc = XIGetProperty (clutter_x11_get_default_display (), info->deviceid, XInternAtom (clutter_x11_get_default_display (), "Device Product ID", False), 0, 2, False, XA_INTEGER, &type, &format, &nitems, &bytes_after, (guchar **) &data); clutter_x11_untrap_x_errors (); if (rc != Success || type != XA_INTEGER || format != 32 || nitems != 2) { XFree (data); return FALSE; } if (vendor_id) *vendor_id = g_strdup_printf ("%.4x", data[0]); if (product_id) *product_id = g_strdup_printf ("%.4x", data[1]); XFree (data); return TRUE; } static char * get_device_node_path (XIDeviceInfo *info) { gulong nitems, bytes_after; guchar *data; int rc, format; Atom prop, type; char *node_path; prop = XInternAtom (clutter_x11_get_default_display (), "Device Node", False); if (prop == None) return NULL; clutter_x11_trap_x_errors (); rc = XIGetProperty (clutter_x11_get_default_display (), info->deviceid, prop, 0, 1024, False, XA_STRING, &type, &format, &nitems, &bytes_after, (guchar **) &data); if (clutter_x11_untrap_x_errors ()) return NULL; if (rc != Success || type != XA_STRING || format != 8) { XFree (data); return FALSE; } node_path = g_strdup ((char *) data); XFree (data); return node_path; } static void get_pad_features (XIDeviceInfo *info, uint32_t *n_rings, uint32_t *n_strips) { int i, rings = 0, strips = 0; for (i = PAD_AXIS_FIRST; i < info->num_classes; i++) { XIValuatorClassInfo *valuator = (XIValuatorClassInfo*) info->classes[i]; int axis = valuator->number; if (valuator->type != XIValuatorClass) continue; if (valuator->max <= 1) continue; /* Ring/strip axes are fixed in pad devices as handled by the * wacom driver. Match those to detect pad features. */ if (axis == PAD_AXIS_STRIP1 || axis == PAD_AXIS_STRIP2) strips++; else if (axis == PAD_AXIS_RING1 || axis == PAD_AXIS_RING2) rings++; } *n_rings = rings; *n_strips = strips; } /* The Wacom driver exports the tool type as property. Use that over guessing based on the device name */ static gboolean guess_source_from_wacom_type (XIDeviceInfo *info, ClutterInputDeviceType *source_out) { gulong nitems, bytes_after; uint32_t *data = NULL; int rc, format; Atom type; Atom prop; Atom device_type; Atom types[N_WACOM_TYPE_ATOMS]; prop = XInternAtom (clutter_x11_get_default_display (), "Wacom Tool Type", True); if (prop == None) return FALSE; clutter_x11_trap_x_errors (); rc = XIGetProperty (clutter_x11_get_default_display (), info->deviceid, prop, 0, 1, False, XA_ATOM, &type, &format, &nitems, &bytes_after, (guchar **) &data); clutter_x11_untrap_x_errors (); if (rc != Success || type != XA_ATOM || format != 32 || nitems != 1) { XFree (data); return FALSE; } device_type = *data; XFree (data); if (device_type == 0) return FALSE; rc = XInternAtoms (clutter_x11_get_default_display (), (char **)wacom_type_atoms, N_WACOM_TYPE_ATOMS, False, types); if (rc == 0) return FALSE; if (device_type == types[WACOM_TYPE_STYLUS]) { *source_out = CLUTTER_PEN_DEVICE; } else if (device_type == types[WACOM_TYPE_CURSOR]) { *source_out = CLUTTER_CURSOR_DEVICE; } else if (device_type == types[WACOM_TYPE_ERASER]) { *source_out = CLUTTER_ERASER_DEVICE; } else if (device_type == types[WACOM_TYPE_PAD]) { *source_out = CLUTTER_PAD_DEVICE; } else if (device_type == types[WACOM_TYPE_TOUCH]) { uint32_t num_touches = 0; if (!is_touch_device (info->classes, info->num_classes, source_out, &num_touches)) *source_out = CLUTTER_TOUCHSCREEN_DEVICE; } else { return FALSE; } return TRUE; } static ClutterInputDevice * create_device (MetaDeviceManagerX11 *manager_xi2, ClutterBackend *backend, XIDeviceInfo *info) { ClutterInputDeviceType source, touch_source; ClutterInputDevice *retval; ClutterInputMode mode; gboolean is_enabled; uint32_t num_touches = 0, num_rings = 0, num_strips = 0; char *vendor_id = NULL, *product_id = NULL, *node_path = NULL; if (info->use == XIMasterKeyboard || info->use == XISlaveKeyboard) { source = CLUTTER_KEYBOARD_DEVICE; } else if (is_touchpad_device (info)) { source = CLUTTER_TOUCHPAD_DEVICE; } else if (info->use == XISlavePointer && is_touch_device (info->classes, info->num_classes, &touch_source, &num_touches)) { source = touch_source; } else if (!guess_source_from_wacom_type (info, &source)) { char *name; name = g_ascii_strdown (info->name, -1); if (strstr (name, "eraser") != NULL) source = CLUTTER_ERASER_DEVICE; else if (strstr (name, "cursor") != NULL) source = CLUTTER_CURSOR_DEVICE; else if (strstr (name, " pad") != NULL) source = CLUTTER_PAD_DEVICE; else if (strstr (name, "wacom") != NULL || strstr (name, "pen") != NULL) source = CLUTTER_PEN_DEVICE; else if (strstr (name, "touchpad") != NULL) source = CLUTTER_TOUCHPAD_DEVICE; else source = CLUTTER_POINTER_DEVICE; g_free (name); } switch (info->use) { case XIMasterKeyboard: case XIMasterPointer: mode = CLUTTER_INPUT_MODE_MASTER; is_enabled = TRUE; break; case XISlaveKeyboard: case XISlavePointer: mode = CLUTTER_INPUT_MODE_SLAVE; is_enabled = FALSE; break; case XIFloatingSlave: default: mode = CLUTTER_INPUT_MODE_FLOATING; is_enabled = FALSE; break; } if (info->use != XIMasterKeyboard && info->use != XIMasterPointer) { get_device_ids (info, &vendor_id, &product_id); node_path = get_device_node_path (info); } if (source == CLUTTER_PAD_DEVICE) { is_enabled = TRUE; get_pad_features (info, &num_rings, &num_strips); } retval = g_object_new (META_TYPE_INPUT_DEVICE_X11, "name", info->name, "id", info->deviceid, "has-cursor", (info->use == XIMasterPointer), "device-manager", manager_xi2, "device-type", source, "device-mode", mode, "backend", backend, "enabled", is_enabled, "vendor-id", vendor_id, "product-id", product_id, "device-node", node_path, "n-rings", num_rings, "n-strips", num_strips, "n-mode-groups", MAX (num_rings, num_strips), NULL); translate_device_classes (clutter_x11_get_default_display (), retval, info->classes, info->num_classes); #ifdef HAVE_LIBWACOM if (source == CLUTTER_PAD_DEVICE) meta_input_device_x11_ensure_wacom_info (retval, manager_xi2->wacom_db); #endif g_free (vendor_id); g_free (product_id); g_free (node_path); g_debug ("Created device '%s' (id: %d, has-cursor: %s)", info->name, info->deviceid, info->use == XIMasterPointer ? "yes" : "no"); return retval; } static void pad_passive_button_grab (ClutterInputDevice *device) { XIGrabModifiers xi_grab_mods = { XIAnyModifier, }; XIEventMask xi_event_mask; int device_id, rc; device_id = clutter_input_device_get_device_id (device); xi_event_mask.deviceid = device_id; xi_event_mask.mask_len = XIMaskLen (XI_LASTEVENT); xi_event_mask.mask = g_new0 (unsigned char, xi_event_mask.mask_len); XISetMask (xi_event_mask.mask, XI_Motion); XISetMask (xi_event_mask.mask, XI_ButtonPress); XISetMask (xi_event_mask.mask, XI_ButtonRelease); clutter_x11_trap_x_errors (); rc = XIGrabButton (clutter_x11_get_default_display (), device_id, XIAnyButton, clutter_x11_get_root_window (), None, XIGrabModeSync, XIGrabModeSync, True, &xi_event_mask, 1, &xi_grab_mods); if (rc != 0) { g_warning ("Could not passively grab pad device: %s", clutter_input_device_get_device_name (device)); } else { XIAllowEvents (clutter_x11_get_default_display (), device_id, XIAsyncDevice, CLUTTER_CURRENT_TIME); } clutter_x11_untrap_x_errors (); g_free (xi_event_mask.mask); } static ClutterInputDevice * add_device (MetaDeviceManagerX11 *manager_xi2, ClutterBackend *backend, XIDeviceInfo *info, gboolean in_construction) { ClutterInputDevice *device; device = create_device (manager_xi2, backend, info); /* we don't go through the DeviceManager::add_device() vfunc because * that emits the signal, and we only do it conditionally */ g_hash_table_replace (manager_xi2->devices_by_id, GINT_TO_POINTER (info->deviceid), device); if (info->use == XIMasterPointer || info->use == XIMasterKeyboard) { manager_xi2->master_devices = g_list_prepend (manager_xi2->master_devices, device); } else if (info->use == XISlavePointer || info->use == XISlaveKeyboard || info->use == XIFloatingSlave) { manager_xi2->slave_devices = g_list_prepend (manager_xi2->slave_devices, device); } else g_warning ("Unhandled device: %s", clutter_input_device_get_device_name (device)); if (clutter_input_device_get_device_type (device) == CLUTTER_PAD_DEVICE) pad_passive_button_grab (device); /* relationships between devices and signal emissions are not * necessary while we're constructing the device manager instance */ if (!in_construction) { if (info->use == XISlavePointer || info->use == XISlaveKeyboard) { ClutterInputDevice *master; master = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (info->attachment)); _clutter_input_device_set_associated_device (device, master); _clutter_input_device_add_slave (master, device); } /* blow the cache */ g_slist_free (manager_xi2->all_devices); manager_xi2->all_devices = NULL; g_signal_emit_by_name (manager_xi2, "device-added", device); } return device; } static void remove_device (MetaDeviceManagerX11 *manager_xi2, int device_id) { ClutterInputDevice *device; device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (device_id)); if (device != NULL) { manager_xi2->master_devices = g_list_remove (manager_xi2->master_devices, device); manager_xi2->slave_devices = g_list_remove (manager_xi2->slave_devices, device); /* blow the cache */ g_slist_free (manager_xi2->all_devices); manager_xi2->all_devices = NULL; g_signal_emit_by_name (manager_xi2, "device-removed", device); g_object_run_dispose (G_OBJECT (device)); g_hash_table_remove (manager_xi2->devices_by_id, GINT_TO_POINTER (device_id)); } } static void translate_hierarchy_event (ClutterBackend *backend, MetaDeviceManagerX11 *manager_xi2, XIHierarchyEvent *ev) { int i; for (i = 0; i < ev->num_info; i++) { if (ev->info[i].flags & XIDeviceEnabled && !g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (ev->info[i].deviceid))) { XIDeviceInfo *info; int n_devices; g_debug ("Hierarchy event: device enabled"); clutter_x11_trap_x_errors (); info = XIQueryDevice (clutter_x11_get_default_display (), ev->info[i].deviceid, &n_devices); clutter_x11_untrap_x_errors (); if (info != NULL) { add_device (manager_xi2, backend, &info[0], FALSE); XIFreeDeviceInfo (info); } } else if (ev->info[i].flags & XIDeviceDisabled) { g_debug ("Hierarchy event: device disabled"); remove_device (manager_xi2, ev->info[i].deviceid); } else if ((ev->info[i].flags & XISlaveAttached) || (ev->info[i].flags & XISlaveDetached)) { ClutterInputDevice *master, *slave; XIDeviceInfo *info; int n_devices; gboolean send_changed = FALSE; g_debug ("Hierarchy event: slave %s", (ev->info[i].flags & XISlaveAttached) ? "attached" : "detached"); slave = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (ev->info[i].deviceid)); master = clutter_input_device_get_associated_device (slave); /* detach the slave in both cases */ if (master != NULL) { _clutter_input_device_remove_slave (master, slave); _clutter_input_device_set_associated_device (slave, NULL); send_changed = TRUE; } /* and attach the slave to the new master if needed */ if (ev->info[i].flags & XISlaveAttached) { clutter_x11_trap_x_errors (); info = XIQueryDevice (clutter_x11_get_default_display (), ev->info[i].deviceid, &n_devices); clutter_x11_untrap_x_errors (); if (info != NULL) { master = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (info->attachment)); if (master != NULL) { _clutter_input_device_set_associated_device (slave, master); _clutter_input_device_add_slave (master, slave); send_changed = TRUE; } XIFreeDeviceInfo (info); } } if (send_changed) { ClutterStage *stage = _clutter_input_device_get_stage (master); if (stage != NULL) { meta_stage_x11_events_device_changed (META_STAGE_X11 (_clutter_stage_get_window (stage)), master, CLUTTER_DEVICE_MANAGER (manager_xi2)); } } } } } static void meta_device_manager_x11_select_events (ClutterDeviceManager *manager, Window xwindow, XIEventMask *event_mask) { Display *xdisplay; xdisplay = clutter_x11_get_default_display (); XISelectEvents (xdisplay, xwindow, event_mask, 1); } static ClutterStage * get_event_stage (MetaDeviceManagerX11 *manager_xi2, XIEvent *xi_event) { Window xwindow = None; switch (xi_event->evtype) { case XI_KeyPress: case XI_KeyRelease: case XI_ButtonPress: case XI_ButtonRelease: case XI_Motion: case XI_TouchBegin: case XI_TouchUpdate: case XI_TouchEnd: { XIDeviceEvent *xev = (XIDeviceEvent *) xi_event; xwindow = xev->event; } break; case XI_Enter: case XI_Leave: case XI_FocusIn: case XI_FocusOut: { XIEnterEvent *xev = (XIEnterEvent *) xi_event; xwindow = xev->event; } break; default: break; } if (xwindow == None) return NULL; return meta_x11_get_stage_from_window (xwindow); } /* * print_key_sym: Translate a symbol to its printable form if any * @symbol: the symbol to translate * @buffer: the buffer where to put the translated string * @len: size of the buffer * * Translates @symbol into a printable representation in @buffer, if possible. * * Return value: The number of bytes of the translated string, 0 if the * symbol can't be printed * * Note: The code is derived from libX11's src/KeyBind.c * Copyright 1985, 1987, 1998 The Open Group * * Note: This code works for Latin-1 symbols. clutter_keysym_to_unicode() * does the work for the other keysyms. */ static int print_keysym (uint32_t symbol, char *buffer, int len) { unsigned long high_bytes; unsigned char c; high_bytes = symbol >> 8; if (!(len && ((high_bytes == 0) || ((high_bytes == 0xFF) && (((symbol >= CLUTTER_KEY_BackSpace) && (symbol <= CLUTTER_KEY_Clear)) || (symbol == CLUTTER_KEY_Return) || (symbol == CLUTTER_KEY_Escape) || (symbol == CLUTTER_KEY_KP_Space) || (symbol == CLUTTER_KEY_KP_Tab) || (symbol == CLUTTER_KEY_KP_Enter) || ((symbol >= CLUTTER_KEY_KP_Multiply) && (symbol <= CLUTTER_KEY_KP_9)) || (symbol == CLUTTER_KEY_KP_Equal) || (symbol == CLUTTER_KEY_Delete)))))) return 0; /* if X keysym, convert to ascii by grabbing low 7 bits */ if (symbol == CLUTTER_KEY_KP_Space) c = CLUTTER_KEY_space & 0x7F; /* patch encoding botch */ else if (high_bytes == 0xFF) c = symbol & 0x7F; else c = symbol & 0xFF; buffer[0] = c; return 1; } static double * translate_axes (ClutterInputDevice *device, double x, double y, XIValuatorState *valuators) { uint32_t n_axes = clutter_input_device_get_n_axes (device); uint32_t i; double *retval; double *values; retval = g_new0 (double, n_axes); values = valuators->values; for (i = 0; i < valuators->mask_len * 8; i++) { ClutterInputAxis axis; double val; if (!XIMaskIsSet (valuators->mask, i)) continue; axis = clutter_input_device_get_axis (device, i); val = *values++; switch (axis) { case CLUTTER_INPUT_AXIS_X: retval[i] = x; break; case CLUTTER_INPUT_AXIS_Y: retval[i] = y; break; default: _clutter_input_device_translate_axis (device, i, val, &retval[i]); break; } } return retval; } static gboolean translate_pad_axis (ClutterInputDevice *device, XIValuatorState *valuators, ClutterEventType *evtype, uint32_t *number, double *value) { double *values; int i; values = valuators->values; for (i = PAD_AXIS_FIRST; i < valuators->mask_len * 8; i++) { double val; uint32_t axis_number = 0; if (!XIMaskIsSet (valuators->mask, i)) continue; val = *values++; if (val <= 0) continue; _clutter_input_device_translate_axis (device, i, val, value); if (i == PAD_AXIS_RING1 || i == PAD_AXIS_RING2) { *evtype = CLUTTER_PAD_RING; (*value) *= 360.0; } else if (i == PAD_AXIS_STRIP1 || i == PAD_AXIS_STRIP2) { *evtype = CLUTTER_PAD_STRIP; } else continue; if (i == PAD_AXIS_STRIP2 || i == PAD_AXIS_RING2) axis_number++; *number = axis_number; return TRUE; } return FALSE; } static void translate_coords (MetaStageX11 *stage_x11, double event_x, double event_y, float *x_out, float *y_out) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11); ClutterActor *stage = CLUTTER_ACTOR (stage_cogl->wrapper); float stage_width; float stage_height; clutter_actor_get_size (stage, &stage_width, &stage_height); *x_out = CLAMP (event_x, 0, stage_width); *y_out = CLAMP (event_y, 0, stage_height); } static double scroll_valuators_changed (ClutterInputDevice *device, XIValuatorState *valuators, double *dx_p, double *dy_p) { gboolean retval = FALSE; uint32_t n_axes, n_val, i; double *values; n_axes = clutter_input_device_get_n_axes (device); values = valuators->values; *dx_p = *dy_p = 0.0; n_val = 0; for (i = 0; i < MIN (valuators->mask_len * 8, n_axes); i++) { ClutterScrollDirection direction; double delta; if (!XIMaskIsSet (valuators->mask, i)) continue; if (_clutter_input_device_get_scroll_delta (device, i, values[n_val], &direction, &delta)) { retval = TRUE; if (direction == CLUTTER_SCROLL_UP || direction == CLUTTER_SCROLL_DOWN) *dy_p = delta; else *dx_p = delta; } n_val += 1; } return retval; } static void meta_device_manager_x11_select_stage_events (ClutterDeviceManager *manager, ClutterStage *stage) { MetaStageX11 *stage_x11; XIEventMask xi_event_mask; unsigned char *mask; int len; stage_x11 = META_STAGE_X11 (_clutter_stage_get_window (stage)); len = XIMaskLen (XI_LASTEVENT); mask = g_new0 (unsigned char, len); XISetMask (mask, XI_Motion); XISetMask (mask, XI_ButtonPress); XISetMask (mask, XI_ButtonRelease); XISetMask (mask, XI_KeyPress); XISetMask (mask, XI_KeyRelease); XISetMask (mask, XI_Enter); XISetMask (mask, XI_Leave); XISetMask (mask, XI_TouchBegin); XISetMask (mask, XI_TouchUpdate); XISetMask (mask, XI_TouchEnd); xi_event_mask.deviceid = XIAllMasterDevices; xi_event_mask.mask = mask; xi_event_mask.mask_len = len; XISelectEvents (clutter_x11_get_default_display (), stage_x11->xwin, &xi_event_mask, 1); g_free (mask); } static uint device_get_tool_serial (ClutterInputDevice *device) { gulong nitems, bytes_after; uint32_t *data = NULL; int serial_id = 0; int rc, format; Atom type; Atom prop; prop = XInternAtom (clutter_x11_get_default_display (), "Wacom Serial IDs", True); if (prop == None) return 0; clutter_x11_trap_x_errors (); rc = XIGetProperty (clutter_x11_get_default_display (), clutter_input_device_get_device_id (device), prop, 0, 4, FALSE, XA_INTEGER, &type, &format, &nitems, &bytes_after, (guchar **) &data); clutter_x11_untrap_x_errors (); if (rc == Success && type == XA_INTEGER && format == 32 && nitems >= 4) serial_id = data[3]; XFree (data); return serial_id; } static void handle_property_event (MetaDeviceManagerX11 *manager_xi2, XIEvent *event) { XIPropertyEvent *xev = (XIPropertyEvent *) event; Atom serial_ids_prop = XInternAtom (clutter_x11_get_default_display (), "Wacom Serial IDs", True); ClutterInputDevice *device; device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); if (!device) return; if (xev->property == serial_ids_prop) { ClutterInputDeviceTool *tool = NULL; ClutterInputDeviceToolType type; int serial_id; serial_id = device_get_tool_serial (device); if (serial_id != 0) { tool = g_hash_table_lookup (manager_xi2->tools_by_serial, GUINT_TO_POINTER (serial_id)); if (!tool) { type = clutter_input_device_get_device_type (device) == CLUTTER_ERASER_DEVICE ? CLUTTER_INPUT_DEVICE_TOOL_ERASER : CLUTTER_INPUT_DEVICE_TOOL_PEN; tool = meta_input_device_tool_x11_new (serial_id, type); g_hash_table_insert (manager_xi2->tools_by_serial, GUINT_TO_POINTER (serial_id), tool); } } meta_input_device_x11_update_tool (device, tool); g_signal_emit_by_name (manager_xi2, "tool-changed", device, tool); } } static gboolean translate_pad_event (ClutterEvent *event, XIDeviceEvent *xev, ClutterInputDevice *device) { double value; uint32_t number, mode = 0; if (!translate_pad_axis (device, &xev->valuators, &event->any.type, &number, &value)) return FALSE; /* When touching a ring/strip a first XI_Motion event * is generated. Use it to reset the pad state, so * later events actually have a directionality. */ if (xev->evtype == XI_Motion) value = -1; #ifdef HAVE_LIBWACOM mode = meta_input_device_x11_get_pad_group_mode (device, number); #endif if (event->any.type == CLUTTER_PAD_RING) { event->pad_ring.ring_number = number; event->pad_ring.angle = value; event->pad_ring.mode = mode; } else { event->pad_strip.strip_number = number; event->pad_strip.value = value; event->pad_strip.mode = mode; } event->any.time = xev->time; clutter_event_set_device (event, device); clutter_event_set_source_device (event, device); g_debug ("%s: win:0x%x, device:%d '%s', time:%d " "(value:%f)", event->any.type == CLUTTER_PAD_RING ? "pad ring " : "pad strip", (unsigned int) xev->event, device->id, device->device_name, event->any.time, value); return TRUE; } static void handle_raw_event (MetaDeviceManagerX11 *manager_xi2, XEvent *xevent) { ClutterInputDevice *device; XGenericEventCookie *cookie; XIEvent *xi_event; XIRawEvent *xev; float x,y; cookie = &xevent->xcookie; xi_event = (XIEvent *) cookie->data; xev = (XIRawEvent *) xi_event; device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); if (device == NULL) return; if (!_clutter_is_input_pointer_a11y_enabled (device)) return; switch (cookie->evtype) { case XI_RawMotion: g_debug ("raw motion: device:%d '%s'", device->id, device->device_name); /* We don't get actual pointer location with raw events, and we cannot * rely on `clutter_input_device_get_coords()` either because of * unreparented toplevels (like all client-side decoration windows), * so we need to explicitely query the pointer here... */ if (meta_input_device_x11_get_pointer_location (device, &x, &y)) _clutter_input_pointer_a11y_on_motion_event (device, x, y); break; case XI_RawButtonPress: case XI_RawButtonRelease: g_debug ("raw button %s: device:%d '%s' button %i", cookie->evtype == XI_RawButtonPress ? "press " : "release", device->id, device->device_name, xev->detail); _clutter_input_pointer_a11y_on_button_event (device, xev->detail, (cookie->evtype == XI_RawButtonPress)); break; } } gboolean meta_device_manager_x11_translate_event (MetaDeviceManagerX11 *manager_xi2, XEvent *xevent, ClutterEvent *event) { gboolean retval = FALSE; ClutterBackend *backend = clutter_get_default_backend (); MetaStageX11 *stage_x11 = NULL; ClutterStage *stage = NULL; ClutterInputDevice *device, *source_device; XGenericEventCookie *cookie; XIEvent *xi_event; cookie = &xevent->xcookie; if (cookie->type != GenericEvent || cookie->extension != manager_xi2->opcode) return FALSE; xi_event = (XIEvent *) cookie->data; if (!xi_event) return FALSE; if (cookie->evtype == XI_RawMotion || cookie->evtype == XI_RawButtonPress || cookie->evtype == XI_RawButtonRelease) { handle_raw_event (manager_xi2, xevent); return FALSE; } if (!(xi_event->evtype == XI_HierarchyChanged || xi_event->evtype == XI_DeviceChanged || xi_event->evtype == XI_PropertyEvent)) { stage = get_event_stage (manager_xi2, xi_event); if (stage == NULL || CLUTTER_ACTOR_IN_DESTRUCTION (stage)) return FALSE; else stage_x11 = META_STAGE_X11 (_clutter_stage_get_window (stage)); } event->any.stage = stage; switch (xi_event->evtype) { case XI_HierarchyChanged: { XIHierarchyEvent *xev = (XIHierarchyEvent *) xi_event; translate_hierarchy_event (backend, manager_xi2, xev); } retval = FALSE; break; case XI_DeviceChanged: { XIDeviceChangedEvent *xev = (XIDeviceChangedEvent *) xi_event; device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); source_device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->sourceid)); if (device) { _clutter_input_device_reset_axes (device); translate_device_classes (clutter_x11_get_default_display (), device, xev->classes, xev->num_classes); } if (source_device) _clutter_input_device_reset_scroll_info (source_device); } retval = FALSE; break; case XI_KeyPress: case XI_KeyRelease: { XIDeviceEvent *xev = (XIDeviceEvent *) xi_event; MetaKeymapX11 *keymap_x11 = META_KEYMAP_X11 (clutter_backend_get_keymap (backend)); MetaEventX11 *event_x11; char buffer[7] = { 0, }; gunichar n; event->key.type = event->type = (xev->evtype == XI_KeyPress) ? CLUTTER_KEY_PRESS : CLUTTER_KEY_RELEASE; if (xev->evtype == XI_KeyPress && xev->flags & XIKeyRepeat) clutter_event_set_flags (event, CLUTTER_EVENT_FLAG_REPEATED); event->key.time = xev->time; event->key.stage = stage; meta_input_device_x11_translate_state (event, &xev->mods, &xev->buttons, &xev->group); event->key.hardware_keycode = xev->detail; /* keyval is the key ignoring all modifiers ('1' vs. '!') */ event->key.keyval = meta_keymap_x11_translate_key_state (keymap_x11, event->key.hardware_keycode, &event->key.modifier_state, NULL); /* KeyEvents have platform specific data associated to them */ event_x11 = meta_event_x11_new (); _clutter_event_set_platform_data (event, event_x11); event_x11->key_group = meta_keymap_x11_get_key_group (keymap_x11, event->key.modifier_state); event_x11->key_is_modifier = meta_keymap_x11_get_is_modifier (keymap_x11, event->key.hardware_keycode); event_x11->num_lock_set = clutter_keymap_get_num_lock_state (CLUTTER_KEYMAP (keymap_x11)); event_x11->caps_lock_set = clutter_keymap_get_caps_lock_state (CLUTTER_KEYMAP (keymap_x11)); source_device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->sourceid)); clutter_event_set_source_device (event, source_device); device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); clutter_event_set_device (event, device); /* XXX keep this in sync with the evdev device manager */ n = print_keysym (event->key.keyval, buffer, sizeof (buffer)); if (n == 0) { /* not printable */ event->key.unicode_value = (gunichar) '\0'; } else { event->key.unicode_value = g_utf8_get_char_validated (buffer, n); if (event->key.unicode_value == -1 || event->key.unicode_value == -2) event->key.unicode_value = (gunichar) '\0'; } g_debug ("%s: win:0x%x device:%d source:%d, key: %12s (%d)", event->any.type == CLUTTER_KEY_PRESS ? "key press " : "key release", (unsigned int) stage_x11->xwin, xev->deviceid, xev->sourceid, event->key.keyval ? buffer : "(none)", event->key.keyval); if (xi_event->evtype == XI_KeyPress) meta_stage_x11_set_user_time (stage_x11, event->key.time); retval = TRUE; } break; case XI_ButtonPress: case XI_ButtonRelease: { XIDeviceEvent *xev = (XIDeviceEvent *) xi_event; source_device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->sourceid)); device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); /* Set the stage for core events coming out of nowhere (see bug #684509) */ if (clutter_input_device_get_device_mode (device) == CLUTTER_INPUT_MODE_MASTER && clutter_input_device_get_pointer_stage (device) == NULL && stage != NULL) _clutter_input_device_set_stage (device, stage); if (clutter_input_device_get_device_type (source_device) == CLUTTER_PAD_DEVICE) { /* We got these events because of the passive button grab */ XIAllowEvents (clutter_x11_get_default_display (), xev->sourceid, XIAsyncDevice, xev->time); event->any.stage = stage; if (xev->detail >= 4 && xev->detail <= 7) { retval = FALSE; if (xi_event->evtype == XI_ButtonPress && translate_pad_event (event, xev, source_device)) retval = TRUE; break; } event->any.type = (xi_event->evtype == XI_ButtonPress) ? CLUTTER_PAD_BUTTON_PRESS : CLUTTER_PAD_BUTTON_RELEASE; event->any.time = xev->time; /* The 4-7 button range is taken as non-existent on pad devices, * let the buttons above that take over this range. */ if (xev->detail > 7) xev->detail -= 4; /* Pad buttons are 0-indexed */ event->pad_button.button = xev->detail - 1; #ifdef HAVE_LIBWACOM meta_input_device_x11_update_pad_state (device, event->pad_button.button, (xi_event->evtype == XI_ButtonPress), &event->pad_button.group, &event->pad_button.mode); #endif clutter_event_set_device (event, device); clutter_event_set_source_device (event, source_device); g_debug ("%s: win:0x%x, device:%d '%s', time:%d " "(button:%d)", event->any.type == CLUTTER_BUTTON_PRESS ? "pad button press " : "pad button release", (unsigned int) stage_x11->xwin, device->id, device->device_name, event->any.time, event->pad_button.button); retval = TRUE; break; } switch (xev->detail) { case 4: case 5: case 6: case 7: /* we only generate Scroll events on ButtonPress */ if (xi_event->evtype == XI_ButtonRelease) return FALSE; event->scroll.type = event->type = CLUTTER_SCROLL; if (xev->detail == 4) event->scroll.direction = CLUTTER_SCROLL_UP; else if (xev->detail == 5) event->scroll.direction = CLUTTER_SCROLL_DOWN; else if (xev->detail == 6) event->scroll.direction = CLUTTER_SCROLL_LEFT; else event->scroll.direction = CLUTTER_SCROLL_RIGHT; event->scroll.stage = stage; event->scroll.time = xev->time; translate_coords (stage_x11, xev->event_x, xev->event_y, &event->scroll.x, &event->scroll.y); meta_input_device_x11_translate_state (event, &xev->mods, &xev->buttons, &xev->group); clutter_event_set_source_device (event, source_device); clutter_event_set_device (event, device); event->scroll.axes = translate_axes (event->scroll.device, event->scroll.x, event->scroll.y, &xev->valuators); g_debug ("scroll: win:0x%x, device:%d '%s', time:%d " "(direction:%s, " "x:%.2f, y:%.2f, " "emulated:%s)", (unsigned int) stage_x11->xwin, device->id, device->device_name, event->any.time, event->scroll.direction == CLUTTER_SCROLL_UP ? "up" : event->scroll.direction == CLUTTER_SCROLL_DOWN ? "down" : event->scroll.direction == CLUTTER_SCROLL_LEFT ? "left" : event->scroll.direction == CLUTTER_SCROLL_RIGHT ? "right" : "invalid", event->scroll.x, event->scroll.y, (xev->flags & XIPointerEmulated) ? "yes" : "no"); break; default: event->button.type = event->type = (xi_event->evtype == XI_ButtonPress) ? CLUTTER_BUTTON_PRESS : CLUTTER_BUTTON_RELEASE; event->button.stage = stage; event->button.time = xev->time; translate_coords (stage_x11, xev->event_x, xev->event_y, &event->button.x, &event->button.y); event->button.button = xev->detail; meta_input_device_x11_translate_state (event, &xev->mods, &xev->buttons, &xev->group); clutter_event_set_source_device (event, source_device); clutter_event_set_device (event, device); clutter_event_set_device_tool (event, meta_input_device_x11_get_current_tool (source_device)); event->button.axes = translate_axes (event->button.device, event->button.x, event->button.y, &xev->valuators); g_debug ("%s: win:0x%x, device:%d '%s', time:%d " "(button:%d, " "x:%.2f, y:%.2f, " "axes:%s, " "emulated:%s)", event->any.type == CLUTTER_BUTTON_PRESS ? "button press " : "button release", (unsigned int) stage_x11->xwin, device->id, device->device_name, event->any.time, event->button.button, event->button.x, event->button.y, event->button.axes != NULL ? "yes" : "no", (xev->flags & XIPointerEmulated) ? "yes" : "no"); break; } if (source_device != NULL && device->stage != NULL) _clutter_input_device_set_stage (source_device, device->stage); if (xev->flags & XIPointerEmulated) _clutter_event_set_pointer_emulated (event, TRUE); if (xi_event->evtype == XI_ButtonPress) meta_stage_x11_set_user_time (stage_x11, event->button.time); retval = TRUE; } break; case XI_Motion: { XIDeviceEvent *xev = (XIDeviceEvent *) xi_event; double delta_x, delta_y; source_device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->sourceid)); device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); if (clutter_input_device_get_device_type (source_device) == CLUTTER_PAD_DEVICE) { event->any.stage = stage; if (translate_pad_event (event, xev, source_device)) retval = TRUE; break; } /* Set the stage for core events coming out of nowhere (see bug #684509) */ if (clutter_input_device_get_device_mode (device) == CLUTTER_INPUT_MODE_MASTER && clutter_input_device_get_pointer_stage (device) == NULL && stage != NULL) _clutter_input_device_set_stage (device, stage); if (scroll_valuators_changed (source_device, &xev->valuators, &delta_x, &delta_y)) { event->scroll.type = event->type = CLUTTER_SCROLL; event->scroll.direction = CLUTTER_SCROLL_SMOOTH; event->scroll.stage = stage; event->scroll.time = xev->time; translate_coords (stage_x11, xev->event_x, xev->event_y, &event->scroll.x, &event->scroll.y); meta_input_device_x11_translate_state (event, &xev->mods, &xev->buttons, &xev->group); clutter_event_set_scroll_delta (event, delta_x, delta_y); clutter_event_set_source_device (event, source_device); clutter_event_set_device (event, device); g_debug ("smooth scroll: win:0x%x device:%d '%s' (x:%.2f, y:%.2f, delta:%f, %f)", (unsigned int) stage_x11->xwin, event->scroll.device->id, event->scroll.device->device_name, event->scroll.x, event->scroll.y, delta_x, delta_y); retval = TRUE; break; } event->motion.type = event->type = CLUTTER_MOTION; event->motion.stage = stage; event->motion.time = xev->time; translate_coords (stage_x11, xev->event_x, xev->event_y, &event->motion.x, &event->motion.y); meta_input_device_x11_translate_state (event, &xev->mods, &xev->buttons, &xev->group); clutter_event_set_source_device (event, source_device); clutter_event_set_device (event, device); clutter_event_set_device_tool (event, meta_input_device_x11_get_current_tool (source_device)); event->motion.axes = translate_axes (event->motion.device, event->motion.x, event->motion.y, &xev->valuators); if (source_device != NULL && device->stage != NULL) _clutter_input_device_set_stage (source_device, device->stage); if (xev->flags & XIPointerEmulated) _clutter_event_set_pointer_emulated (event, TRUE); g_debug ("motion: win:0x%x device:%d '%s' (x:%.2f, y:%.2f, axes:%s)", (unsigned int) stage_x11->xwin, event->motion.device->id, event->motion.device->device_name, event->motion.x, event->motion.y, event->motion.axes != NULL ? "yes" : "no"); retval = TRUE; } break; case XI_TouchBegin: { XIDeviceEvent *xev = (XIDeviceEvent *) xi_event; device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); if (!_clutter_input_device_get_stage (device)) _clutter_input_device_set_stage (device, stage); } /* Fall through */ case XI_TouchEnd: { XIDeviceEvent *xev = (XIDeviceEvent *) xi_event; source_device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->sourceid)); if (xi_event->evtype == XI_TouchBegin) event->touch.type = event->type = CLUTTER_TOUCH_BEGIN; else event->touch.type = event->type = CLUTTER_TOUCH_END; event->touch.stage = stage; event->touch.time = xev->time; translate_coords (stage_x11, xev->event_x, xev->event_y, &event->touch.x, &event->touch.y); meta_input_device_x11_translate_state (event, &xev->mods, &xev->buttons, &xev->group); clutter_event_set_source_device (event, source_device); device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); clutter_event_set_device (event, device); event->touch.axes = translate_axes (event->touch.device, event->motion.x, event->motion.y, &xev->valuators); if (xi_event->evtype == XI_TouchBegin) { event->touch.modifier_state |= CLUTTER_BUTTON1_MASK; meta_stage_x11_set_user_time (stage_x11, event->touch.time); } event->touch.sequence = GUINT_TO_POINTER (xev->detail); if (xev->flags & XITouchEmulatingPointer) _clutter_event_set_pointer_emulated (event, TRUE); g_debug ("touch %s: win:0x%x device:%d '%s' (seq:%d, x:%.2f, y:%.2f, axes:%s)", event->type == CLUTTER_TOUCH_BEGIN ? "begin" : "end", (unsigned int) stage_x11->xwin, event->touch.device->id, event->touch.device->device_name, GPOINTER_TO_UINT (event->touch.sequence), event->touch.x, event->touch.y, event->touch.axes != NULL ? "yes" : "no"); retval = TRUE; } break; case XI_TouchUpdate: { XIDeviceEvent *xev = (XIDeviceEvent *) xi_event; source_device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->sourceid)); event->touch.type = event->type = CLUTTER_TOUCH_UPDATE; event->touch.stage = stage; event->touch.time = xev->time; event->touch.sequence = GUINT_TO_POINTER (xev->detail); translate_coords (stage_x11, xev->event_x, xev->event_y, &event->touch.x, &event->touch.y); clutter_event_set_source_device (event, source_device); device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); clutter_event_set_device (event, device); event->touch.axes = translate_axes (event->touch.device, event->motion.x, event->motion.y, &xev->valuators); meta_input_device_x11_translate_state (event, &xev->mods, &xev->buttons, &xev->group); event->touch.modifier_state |= CLUTTER_BUTTON1_MASK; if (xev->flags & XITouchEmulatingPointer) _clutter_event_set_pointer_emulated (event, TRUE); g_debug ("touch update: win:0x%x device:%d '%s' (seq:%d, x:%.2f, y:%.2f, axes:%s)", (unsigned int) stage_x11->xwin, event->touch.device->id, event->touch.device->device_name, GPOINTER_TO_UINT (event->touch.sequence), event->touch.x, event->touch.y, event->touch.axes != NULL ? "yes" : "no"); retval = TRUE; } break; case XI_Enter: case XI_Leave: { XIEnterEvent *xev = (XIEnterEvent *) xi_event; device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->deviceid)); source_device = g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (xev->sourceid)); if (xi_event->evtype == XI_Enter) { event->crossing.type = event->type = CLUTTER_ENTER; event->crossing.stage = stage; event->crossing.source = CLUTTER_ACTOR (stage); event->crossing.related = NULL; event->crossing.time = xev->time; translate_coords (stage_x11, xev->event_x, xev->event_y, &event->crossing.x, &event->crossing.y); _clutter_input_device_set_stage (device, stage); } else { if (device->stage == NULL) { g_debug ("Discarding Leave for ButtonRelease " "event off-stage"); retval = FALSE; break; } event->crossing.type = event->type = CLUTTER_LEAVE; event->crossing.stage = stage; event->crossing.source = CLUTTER_ACTOR (stage); event->crossing.related = NULL; event->crossing.time = xev->time; translate_coords (stage_x11, xev->event_x, xev->event_y, &event->crossing.x, &event->crossing.y); _clutter_input_device_set_stage (device, NULL); } _clutter_input_device_reset_scroll_info (source_device); clutter_event_set_device (event, device); clutter_event_set_source_device (event, source_device); retval = TRUE; } break; case XI_FocusIn: case XI_FocusOut: retval = FALSE; break; case XI_PropertyEvent: handle_property_event (manager_xi2, xi_event); retval = FALSE; break; } return retval; } static void meta_device_manager_x11_add_device (ClutterDeviceManager *manager, ClutterInputDevice *device) { /* XXX implement */ } static void meta_device_manager_x11_remove_device (ClutterDeviceManager *manager, ClutterInputDevice *device) { /* XXX implement */ } static const GSList * meta_device_manager_x11_get_devices (ClutterDeviceManager *manager) { MetaDeviceManagerX11 *manager_xi2 = META_DEVICE_MANAGER_X11 (manager); GSList *all_devices = NULL; GList *l; if (manager_xi2->all_devices != NULL) return manager_xi2->all_devices; for (l = manager_xi2->master_devices; l != NULL; l = l->next) all_devices = g_slist_prepend (all_devices, l->data); for (l = manager_xi2->slave_devices; l != NULL; l = l->next) all_devices = g_slist_prepend (all_devices, l->data); manager_xi2->all_devices = g_slist_reverse (all_devices); return manager_xi2->all_devices; } static ClutterInputDevice * meta_device_manager_x11_get_device (ClutterDeviceManager *manager, gint id) { MetaDeviceManagerX11 *manager_xi2 = META_DEVICE_MANAGER_X11 (manager); return g_hash_table_lookup (manager_xi2->devices_by_id, GINT_TO_POINTER (id)); } static ClutterInputDevice * meta_device_manager_x11_get_core_device (ClutterDeviceManager *manager, ClutterInputDeviceType device_type) { MetaDeviceManagerX11 *manager_xi2 = META_DEVICE_MANAGER_X11 (manager); ClutterInputDevice *pointer = NULL; GList *l; for (l = manager_xi2->master_devices; l != NULL ; l = l->next) { ClutterInputDevice *device = l->data; if (clutter_input_device_get_device_type (device) == CLUTTER_POINTER_DEVICE) { pointer = device; break; } } if (pointer == NULL) return NULL; switch (device_type) { case CLUTTER_POINTER_DEVICE: return pointer; case CLUTTER_KEYBOARD_DEVICE: return clutter_input_device_get_associated_device (pointer); default: break; } return NULL; } static void relate_masters (gpointer key, gpointer value, gpointer data) { MetaDeviceManagerX11 *manager_xi2 = data; ClutterInputDevice *device, *relative; device = g_hash_table_lookup (manager_xi2->devices_by_id, key); relative = g_hash_table_lookup (manager_xi2->devices_by_id, value); _clutter_input_device_set_associated_device (device, relative); _clutter_input_device_set_associated_device (relative, device); } static void relate_slaves (gpointer key, gpointer value, gpointer data) { MetaDeviceManagerX11 *manager_xi2 = data; ClutterInputDevice *master, *slave; slave = g_hash_table_lookup (manager_xi2->devices_by_id, key); master = g_hash_table_lookup (manager_xi2->devices_by_id, value); _clutter_input_device_set_associated_device (slave, master); _clutter_input_device_add_slave (master, slave); } static void meta_device_manager_x11_constructed (GObject *object) { MetaDeviceManagerX11 *manager_xi2 = META_DEVICE_MANAGER_X11 (object); ClutterDeviceManager *manager = CLUTTER_DEVICE_MANAGER (object); ClutterBackend *backend = clutter_get_default_backend (); GHashTable *masters, *slaves; XIDeviceInfo *info; XIEventMask event_mask; unsigned char mask[(XI_LASTEVENT + 7) / 8] = { 0, }; int n_devices, i; masters = g_hash_table_new (NULL, NULL); slaves = g_hash_table_new (NULL, NULL); info = XIQueryDevice (clutter_x11_get_default_display (), XIAllDevices, &n_devices); for (i = 0; i < n_devices; i++) { XIDeviceInfo *xi_device = &info[i]; if (!xi_device->enabled) continue; add_device (manager_xi2, backend, xi_device, TRUE); if (xi_device->use == XIMasterPointer || xi_device->use == XIMasterKeyboard) { g_hash_table_insert (masters, GINT_TO_POINTER (xi_device->deviceid), GINT_TO_POINTER (xi_device->attachment)); } else if (xi_device->use == XISlavePointer || xi_device->use == XISlaveKeyboard) { g_hash_table_insert (slaves, GINT_TO_POINTER (xi_device->deviceid), GINT_TO_POINTER (xi_device->attachment)); } } XIFreeDeviceInfo (info); g_hash_table_foreach (masters, relate_masters, manager_xi2); g_hash_table_destroy (masters); g_hash_table_foreach (slaves, relate_slaves, manager_xi2); g_hash_table_destroy (slaves); XISetMask (mask, XI_HierarchyChanged); XISetMask (mask, XI_DeviceChanged); XISetMask (mask, XI_PropertyEvent); event_mask.deviceid = XIAllDevices; event_mask.mask_len = sizeof (mask); event_mask.mask = mask; meta_device_manager_x11_select_events (manager, clutter_x11_get_root_window (), &event_mask); memset(mask, 0, sizeof (mask)); XISetMask (mask, XI_RawMotion); XISetMask (mask, XI_RawButtonPress); XISetMask (mask, XI_RawButtonRelease); event_mask.deviceid = XIAllMasterDevices; event_mask.mask_len = sizeof (mask); event_mask.mask = mask; meta_device_manager_x11_select_events (manager, clutter_x11_get_root_window (), &event_mask); XSync (clutter_x11_get_default_display (), False); meta_device_manager_x11_a11y_init (manager); if (G_OBJECT_CLASS (meta_device_manager_x11_parent_class)->constructed) G_OBJECT_CLASS (meta_device_manager_x11_parent_class)->constructed (object); } static void meta_device_manager_x11_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MetaDeviceManagerX11 *manager_xi2 = META_DEVICE_MANAGER_X11 (object); switch (prop_id) { case PROP_OPCODE: manager_xi2->opcode = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static ClutterVirtualInputDevice * meta_device_manager_x11_create_virtual_device (ClutterDeviceManager *manager, ClutterInputDeviceType device_type) { return g_object_new (META_TYPE_VIRTUAL_INPUT_DEVICE_X11, "device-manager", manager, "device-type", device_type, NULL); } static ClutterVirtualDeviceType meta_device_manager_x11_get_supported_virtual_device_types (ClutterDeviceManager *device_manager) { return (CLUTTER_VIRTUAL_DEVICE_TYPE_KEYBOARD | CLUTTER_VIRTUAL_DEVICE_TYPE_POINTER); } static void meta_device_manager_x11_class_init (MetaDeviceManagerX11Class *klass) { ClutterDeviceManagerClass *manager_class; GObjectClass *gobject_class; obj_props[PROP_OPCODE] = g_param_spec_int ("opcode", "Opcode", "The XI2 opcode", -1, G_MAXINT, -1, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); gobject_class = G_OBJECT_CLASS (klass); gobject_class->constructed = meta_device_manager_x11_constructed; gobject_class->set_property = meta_device_manager_x11_set_property; g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); manager_class = CLUTTER_DEVICE_MANAGER_CLASS (klass); manager_class->add_device = meta_device_manager_x11_add_device; manager_class->remove_device = meta_device_manager_x11_remove_device; manager_class->get_devices = meta_device_manager_x11_get_devices; manager_class->get_core_device = meta_device_manager_x11_get_core_device; manager_class->get_device = meta_device_manager_x11_get_device; manager_class->select_stage_events = meta_device_manager_x11_select_stage_events; manager_class->create_virtual_device = meta_device_manager_x11_create_virtual_device; manager_class->get_supported_virtual_device_types = meta_device_manager_x11_get_supported_virtual_device_types; manager_class->apply_kbd_a11y_settings = meta_device_manager_x11_apply_kbd_a11y_settings; manager_class->copy_event_data = meta_device_manager_x11_copy_event_data; manager_class->free_event_data = meta_device_manager_x11_free_event_data; } static void meta_device_manager_x11_init (MetaDeviceManagerX11 *self) { self->devices_by_id = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_object_unref); self->tools_by_serial = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_object_unref); #ifdef HAVE_LIBWACOM self->wacom_db = libwacom_database_new (); #endif }