/* * AT-SPI - Assistive Technology Service Provider Interface * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap) * * Copyright 2020 SUSE LLC. * * 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.1 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, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "atspi-device-x11.h" #include "atspi-private.h" #include #include #include #include #define ATSPI_VIRTUAL_MODIFIER_MASK 0x0000f000 typedef struct _AtspiDeviceX11Private AtspiDeviceX11Private; struct _AtspiDeviceX11Private { Display *display; Window root_window; Window focused_window; GSource *source; int xi_opcode; int device_id; int device_id_alt; GSList *modifiers; GSList *key_grabs; guint virtual_mods_enabled; gboolean keyboard_grabbed; unsigned int numlock_physical_mask; AtspiEventListener *event_listener; }; GObjectClass *device_x11_parent_class; typedef struct _DisplaySource { GSource source; Display *display; GPollFD event_poll_fd; } DisplaySource; typedef struct { guint keycode; guint modifier; } AtspiX11KeyModifier; typedef struct { AtspiKeyDefinition *kd; gboolean enabled; Window window; } AtspiX11KeyGrab; static gboolean event_prepare (GSource *source, gint *timeout) { Display *display = ((DisplaySource *) source)->display; gboolean retval; *timeout = -1; retval = XPending (display); return retval; } static gboolean event_check (GSource *source) { DisplaySource *display_source = (DisplaySource *) source; gboolean retval; if (display_source->event_poll_fd.revents & G_IO_IN) retval = XPending (display_source->display); else retval = FALSE; return retval; } static void xi2keyevent (XIDeviceEvent *xievent, XEvent *xkeyevent) { memset (xkeyevent, 0, sizeof (*xkeyevent)); switch (xievent->evtype) { case XI_KeyPress: xkeyevent->type = KeyPress; break; case XI_KeyRelease: xkeyevent->type = KeyRelease; break; default: break; } xkeyevent->xkey.serial = xievent->serial; xkeyevent->xkey.send_event = xievent->send_event; xkeyevent->xkey.display = xievent->display; xkeyevent->xkey.window = xievent->event; xkeyevent->xkey.root = xievent->root; xkeyevent->xkey.subwindow = xievent->child; xkeyevent->xkey.time = xievent->time; xkeyevent->xkey.x = xievent->event_x; xkeyevent->xkey.y = xievent->event_y; xkeyevent->xkey.x_root = xievent->root_x; xkeyevent->xkey.y_root = xievent->root_y; xkeyevent->xkey.state = xievent->mods.effective; xkeyevent->xkey.keycode = xievent->detail; xkeyevent->xkey.same_screen = 1; } G_DEFINE_TYPE_WITH_CODE (AtspiDeviceX11, atspi_device_x11, ATSPI_TYPE_DEVICE, G_ADD_PRIVATE (AtspiDeviceX11)) static guint find_virtual_mapping (AtspiDeviceX11 *x11_device, gint keycode) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); GSList *l; for (l = priv->modifiers; l; l = l->next) { AtspiX11KeyModifier *entry = l->data; if (entry->keycode == keycode) return entry->modifier; } return 0; } static gboolean grab_should_be_enabled (AtspiDeviceX11 *x11_device, AtspiX11KeyGrab *grab) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); /* If the whole keyboard is grabbed, then all keys are grabbed elsewhere */ if (priv->keyboard_grabbed) return FALSE; guint virtual_mods_used = grab->kd->modifiers & ATSPI_VIRTUAL_MODIFIER_MASK; return ((priv->virtual_mods_enabled & virtual_mods_used) == virtual_mods_used); } static gboolean grab_has_active_duplicate (AtspiDeviceX11 *x11_device, AtspiX11KeyGrab *grab) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); GSList *l; for (l = priv->key_grabs; l; l = l->next) { AtspiX11KeyGrab *other = l->data; if (other != grab && other->enabled && other->kd->keycode == grab->kd->keycode && (other->kd->modifiers & ~ATSPI_VIRTUAL_MODIFIER_MASK) == (grab->kd->modifiers & ~ATSPI_VIRTUAL_MODIFIER_MASK)) return TRUE; } return FALSE; } static void grab_key_aux (AtspiDeviceX11 *x11_device, Window window, int keycode, int modmask) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); XIGrabModifiers xi_modifiers; XIEventMask eventmask; unsigned char mask[XIMaskLen (XI_LASTEVENT)] = { 0 }; xi_modifiers.modifiers = modmask; xi_modifiers.status = 0; eventmask.deviceid = XIAllDevices; eventmask.mask_len = sizeof (mask); eventmask.mask = mask; XISetMask (mask, XI_KeyPress); XISetMask (mask, XI_KeyRelease); XIGrabKeycode (priv->display, XIAllMasterDevices, keycode, window, XIGrabModeSync, XIGrabModeAsync, False, &eventmask, 1, &xi_modifiers); } static void grab_key (AtspiDeviceX11 *x11_device, Window window, int keycode, int modmask) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); grab_key_aux (x11_device, window, keycode, modmask); if (!(modmask & LockMask)) grab_key_aux (x11_device, window, keycode, modmask | LockMask); if (!(modmask & priv->numlock_physical_mask)) { grab_key_aux (x11_device, window, keycode, modmask | priv->numlock_physical_mask); if (!(modmask & LockMask)) grab_key_aux (x11_device, window, keycode, modmask | LockMask | priv->numlock_physical_mask); } } static void enable_key_grab (AtspiDeviceX11 *x11_device, AtspiX11KeyGrab *grab) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); g_return_if_fail (priv->display != NULL); if (!grab_has_active_duplicate (x11_device, grab)) grab_key (x11_device, priv->focused_window, grab->kd->keycode, grab->kd->modifiers & ~ATSPI_VIRTUAL_MODIFIER_MASK); grab->enabled = TRUE; grab->window = priv->focused_window; } static void ungrab_key_aux (AtspiDeviceX11 *x11_device, Window window, int keycode, int modmask) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); XIGrabModifiers xi_modifiers; xi_modifiers.modifiers = modmask; xi_modifiers.status = 0; XIUngrabKeycode (priv->display, XIAllMasterDevices, keycode, window, sizeof (xi_modifiers), &xi_modifiers); } static void ungrab_key (AtspiDeviceX11 *x11_device, Window window, int keycode, int modmask) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); ungrab_key_aux (x11_device, window, keycode, modmask); if (!(modmask & LockMask)) ungrab_key_aux (x11_device, window, keycode, modmask | LockMask); if (!(modmask & priv->numlock_physical_mask)) { ungrab_key_aux (x11_device, window, keycode, modmask | priv->numlock_physical_mask); if (!(modmask & LockMask)) ungrab_key_aux (x11_device, window, keycode, modmask | LockMask | priv->numlock_physical_mask); } } static void disable_key_grab (AtspiDeviceX11 *x11_device, AtspiX11KeyGrab *grab) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); g_return_if_fail (priv->display != NULL); if (!grab->enabled) return; grab->enabled = FALSE; if (grab_has_active_duplicate (x11_device, grab)) return; ungrab_key (x11_device, grab->window, grab->kd->keycode, grab->kd->modifiers & ~ATSPI_VIRTUAL_MODIFIER_MASK); } static void refresh_key_grabs (AtspiDeviceX11 *x11_device) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); GSList *l; int focus_revert; XGetInputFocus (priv->display, &priv->focused_window, &focus_revert); for (l = priv->key_grabs; l; l = l->next) { AtspiX11KeyGrab *grab = l->data; gboolean new_enabled = grab_should_be_enabled (x11_device, grab); if (grab->window != priv->focused_window) disable_key_grab (x11_device, grab); if (new_enabled && !grab->enabled) enable_key_grab (x11_device, grab); else if (grab->enabled && !new_enabled) disable_key_grab (x11_device, grab); } } static void set_virtual_modifier (AtspiDeviceX11 *x11_device, gint keycode, gboolean enabled) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); guint modifier = find_virtual_mapping (x11_device, keycode); if (!modifier) return; if (enabled) { if (priv->virtual_mods_enabled & modifier) return; priv->virtual_mods_enabled |= modifier; } else { if (!(priv->virtual_mods_enabled & modifier)) return; priv->virtual_mods_enabled &= ~modifier; } refresh_key_grabs (x11_device); } static gboolean do_event_dispatch (gpointer user_data) { AtspiDeviceX11 *device = user_data; AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (device); Display *display = priv->display; XEvent xevent; char text[10]; KeySym keysym; XComposeStatus status; guint modifiers; g_object_ref (device); while (XPending (display)) { XNextEvent (display, &xevent); XEvent keyevent; switch (xevent.type) { case KeyPress: case KeyRelease: XLookupString (&xevent.xkey, text, sizeof (text), &keysym, &status); modifiers = xevent.xkey.state | priv->virtual_mods_enabled; if (modifiers & priv->numlock_physical_mask) { modifiers |= (1 << ATSPI_MODIFIER_NUMLOCK); modifiers &= ~priv->numlock_physical_mask; } atspi_device_notify_key (ATSPI_DEVICE (device), (xevent.type == KeyPress), xevent.xkey.keycode, keysym, modifiers, text); break; case FocusIn: refresh_key_grabs (device); break; case GenericEvent: if (xevent.xcookie.extension == priv->xi_opcode) { XGetEventData (priv->display, &xevent.xcookie); XIRawEvent *xiRawEv = (XIRawEvent *) xevent.xcookie.data; XIDeviceEvent *xiDevEv = (XIDeviceEvent *) xevent.xcookie.data; switch (xevent.xcookie.evtype) { case XI_KeyPress: case XI_KeyRelease: xi2keyevent (xiDevEv, &keyevent); XLookupString ((XKeyEvent *) &keyevent, text, sizeof (text), &keysym, &status); if (text[0] < ' ') text[0] = '\0'; /* The deviceid can change. Would be nice to find a better way of handling this */ if (priv->device_id && priv->device_id_alt && xiDevEv->deviceid != priv->device_id && xiDevEv->deviceid != priv->device_id_alt) priv->device_id = priv->device_id_alt = 0; else if (priv->device_id && !priv->device_id_alt && xiDevEv->deviceid != priv->device_id) priv->device_id_alt = xiDevEv->deviceid; if (!priv->device_id) priv->device_id = xiDevEv->deviceid; set_virtual_modifier (device, xiRawEv->detail, xevent.xcookie.evtype == XI_KeyPress); modifiers = keyevent.xkey.state | priv->virtual_mods_enabled; if (modifiers & priv->numlock_physical_mask) modifiers |= (1 << ATSPI_MODIFIER_NUMLOCK); if (xiDevEv->deviceid == priv->device_id) atspi_device_notify_key (ATSPI_DEVICE (device), (xevent.xcookie.evtype == XI_KeyPress), xiRawEv->detail, keysym, modifiers, text); /* otherwise it's probably a duplicate event from a key grab */ XFreeEventData (priv->display, &xevent.xcookie); break; case FocusIn: refresh_key_grabs (device); break; } } default: if (XFilterEvent (&xevent, None)) continue; } } g_object_unref (device); return TRUE; } static gboolean event_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { if (callback) callback (user_data); return G_SOURCE_CONTINUE; } static GSourceFuncs event_funcs = { event_prepare, event_check, event_dispatch, NULL }; static GSource * display_source_new (Display *display) { GSource *source = g_source_new (&event_funcs, sizeof (DisplaySource)); DisplaySource *display_source = (DisplaySource *) source; g_source_set_name (source, "[at-spi2-core] display_source_funcs"); display_source->display = display; return source; } static void create_event_source (AtspiDeviceX11 *device) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (device); DisplaySource *display_source; int connection_number = ConnectionNumber (priv->display); priv->source = display_source_new (priv->display); display_source = (DisplaySource *) priv->source; g_source_set_priority (priv->source, G_PRIORITY_DEFAULT); display_source->event_poll_fd.fd = connection_number; display_source->event_poll_fd.events = G_IO_IN; g_source_add_poll (priv->source, &display_source->event_poll_fd); g_source_set_can_recurse (priv->source, TRUE); g_source_set_callback (priv->source, do_event_dispatch, device, NULL); g_source_attach (priv->source, NULL); } static gboolean check_virtual_modifier (AtspiDeviceX11 *x11_device, guint modifier) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); GSList *l; if (modifier == (1 << ATSPI_MODIFIER_NUMLOCK)) return TRUE; for (l = priv->modifiers; l; l = l->next) { AtspiX11KeyModifier *entry = l->data; if (entry->modifier == modifier) return TRUE; } return FALSE; } static guint get_unused_virtual_modifier (AtspiDeviceX11 *x11_device) { guint ret = 0x1000; while (ret < 0x10000) { if (!check_virtual_modifier (x11_device, ret)) return ret; ret <<= 1; } return 0; } static guint atspi_device_x11_map_modifier (AtspiDevice *device, gint keycode) { AtspiDeviceX11 *x11_device = ATSPI_DEVICE_X11 (device); AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); XkbDescPtr desc; guint ret; AtspiX11KeyModifier *entry; desc = XkbGetMap (priv->display, XkbModifierMapMask, XkbUseCoreKbd); if (keycode < desc->min_key_code || keycode >= desc->max_key_code) { XkbFreeKeyboard (desc, XkbModifierMapMask, TRUE); g_warning ("Passed invalid keycode %d", keycode); return 0; } ret = desc->map->modmap[keycode]; XkbFreeKeyboard (desc, XkbModifierMapMask, TRUE); if (ret & (ShiftMask | ControlMask)) return ret; ret = find_virtual_mapping (x11_device, keycode); if (ret) return ret; ret = get_unused_virtual_modifier (x11_device); entry = g_new (AtspiX11KeyModifier, 1); entry->keycode = keycode; entry->modifier = ret; priv->modifiers = g_slist_append (priv->modifiers, entry); return ret; } static void atspi_device_x11_unmap_modifier (AtspiDevice *device, gint keycode) { AtspiDeviceX11 *x11_device = ATSPI_DEVICE_X11 (device); AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); GSList *l; for (l = priv->modifiers; l; l = l->next) { AtspiX11KeyModifier *entry = l->data; if (entry->keycode == keycode) { priv->modifiers = g_slist_remove (priv->modifiers, entry); g_free (entry); return; } } } static guint atspi_device_x11_get_modifier (AtspiDevice *device, gint keycode) { AtspiDeviceX11 *x11_device = ATSPI_DEVICE_X11 (device); AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); XkbDescPtr desc; guint ret; desc = XkbGetMap (priv->display, XkbModifierMapMask, XkbUseCoreKbd); if (keycode < desc->min_key_code || keycode >= desc->max_key_code) { XkbFreeKeyboard (desc, XkbModifierMapMask, TRUE); g_warning ("Passed invalid keycode %d", keycode); return 0; } ret = desc->map->modmap[keycode]; XkbFreeKeyboard (desc, XkbModifierMapMask, TRUE); if (ret) return ret; return find_virtual_mapping (x11_device, keycode); } static void event_listener_cb (AtspiEvent *event, void *user_data) { AtspiDeviceX11 *x11_device = user_data; refresh_key_grabs (x11_device); g_boxed_free (ATSPI_TYPE_EVENT, event); } static void atspi_device_x11_init (AtspiDeviceX11 *device) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (device); int first_event, first_error; int focus_revert; priv->display = XOpenDisplay (""); g_return_if_fail (priv->display != NULL); priv->root_window = DefaultRootWindow (priv->display); XGetInputFocus (priv->display, &priv->focused_window, &focus_revert); if (XQueryExtension (priv->display, "XInputExtension", &priv->xi_opcode, &first_event, &first_error)) { int major = 2; int minor = 1; if (XIQueryVersion (priv->display, &major, &minor) != BadRequest) { XIEventMask eventmask; unsigned char mask[XIMaskLen (XI_LASTEVENT)] = { 0 }; eventmask.deviceid = XIAllDevices; eventmask.mask_len = sizeof (mask); eventmask.mask = mask; XISetMask (mask, XI_KeyPress); XISetMask (mask, XI_KeyRelease); XISetMask (mask, XI_ButtonPress); XISetMask (mask, XI_ButtonRelease); XISetMask (mask, XI_Motion); XISelectEvents (priv->display, priv->root_window, &eventmask, 1); XSelectInput (priv->display, priv->root_window, FocusChangeMask); create_event_source (device); } } priv->numlock_physical_mask = XkbKeysymToModifiers (priv->display, XK_Num_Lock); priv->event_listener = atspi_event_listener_new (event_listener_cb, device, NULL); atspi_event_listener_register (priv->event_listener, "window:activate", NULL); atspi_event_listener_register (priv->event_listener, "window:deactivate", NULL); } static void atspi_device_x11_finalize (GObject *object) { AtspiDeviceX11 *device = ATSPI_DEVICE_X11 (object); AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (device); GSList *l; for (l = priv->key_grabs; l; l = l->next) { AtspiX11KeyGrab *grab = l->data; disable_key_grab (device, grab); g_boxed_free (ATSPI_TYPE_KEY_DEFINITION, grab->kd); g_free (grab); } g_slist_free (priv->key_grabs); priv->key_grabs = NULL; g_slist_free_full (priv->modifiers, g_free); priv->modifiers = NULL; if (priv->source) { g_source_destroy ((GSource *) priv->source); g_source_unref ((GSource *) priv->source); priv->source = NULL; } /* TODO: deregister event listeners automatically when the listener is finalized */ atspi_event_listener_deregister (priv->event_listener, "window:deactivate", NULL); atspi_event_listener_deregister (priv->event_listener, "window:activate", NULL); g_object_unref (priv->event_listener); device_x11_parent_class->finalize (object); } static void atspi_device_x11_add_key_grab (AtspiDevice *device, AtspiKeyDefinition *kd) { AtspiDeviceX11 *x11_device = ATSPI_DEVICE_X11 (device); AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); AtspiX11KeyGrab *grab; grab = g_new (AtspiX11KeyGrab, 1); grab->kd = g_boxed_copy (ATSPI_TYPE_KEY_DEFINITION, kd); grab->enabled = FALSE; priv->key_grabs = g_slist_append (priv->key_grabs, grab); if (grab_should_be_enabled (x11_device, grab)) enable_key_grab (x11_device, grab); } static void atspi_device_x11_remove_key_grab (AtspiDevice *device, guint id) { AtspiDeviceX11 *x11_device = ATSPI_DEVICE_X11 (device); AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); AtspiKeyDefinition *kd; GSList *l; kd = atspi_device_get_grab_by_id (device, id); for (l = priv->key_grabs; l; l = g_slist_next (l)) { AtspiX11KeyGrab *other = l->data; if (other->kd->keycode == kd->keycode && other->kd->modifiers == kd->modifiers) { disable_key_grab (x11_device, other); priv->key_grabs = g_slist_remove (priv->key_grabs, other); return; } } } static guint atspi_device_x11_get_locked_modifiers (AtspiDevice *device) { AtspiDeviceX11 *x11_device = ATSPI_DEVICE_X11 (device); AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); XkbStateRec state_rec; memset (&state_rec, 0, sizeof (state_rec)); XkbGetState (priv->display, XkbUseCoreKbd, &state_rec); return state_rec.locked_mods; } static void get_keycode_range (AtspiDeviceX11 *x11_device, int *min, int *max) { AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); XkbDescPtr desc; desc = XkbGetMap (priv->display, XkbModifierMapMask, XkbUseCoreKbd); *min = desc->min_key_code; *max = desc->max_key_code; XkbFreeKeyboard (desc, XkbModifierMapMask, TRUE); } static gboolean atspi_device_x11_grab_keyboard (AtspiDevice *device) { AtspiDeviceX11 *x11_device = ATSPI_DEVICE_X11 (device); AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); int min, max; gint i; g_return_val_if_fail (priv->display != NULL, FALSE); #if 0 /* THis seems like the right thing to do, but it fails for me */ return (XGrabKeyboard (priv->display, priv->focused_window, TRUE, GrabModeAsync, GrabModeSync, CurrentTime)) == 0; #else if (priv->keyboard_grabbed) return TRUE; priv->keyboard_grabbed = TRUE; refresh_key_grabs (x11_device); get_keycode_range (x11_device, &min, &max); for (i = min; i < max; i++) grab_key (x11_device, priv->focused_window, i, 0); return TRUE; #endif } static void atspi_device_x11_ungrab_keyboard (AtspiDevice *device) { AtspiDeviceX11 *x11_device = ATSPI_DEVICE_X11 (device); AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); int min, max; gint i; g_return_if_fail (priv->display != NULL); #if 0 XUngrabKeyboard (priv->display, CurrentTime); #else if (!priv->keyboard_grabbed) return; priv->keyboard_grabbed = FALSE; get_keycode_range (x11_device, &min, &max); for (i = min; i < max; i++) ungrab_key (x11_device, priv->focused_window, i, 0); refresh_key_grabs (x11_device); #endif } static void atspi_device_x11_class_init (AtspiDeviceX11Class *klass) { AtspiDeviceClass *device_class = ATSPI_DEVICE_CLASS (klass); GObjectClass *object_class = (GObjectClass *) klass; device_x11_parent_class = g_type_class_peek_parent (klass); device_class->add_key_grab = atspi_device_x11_add_key_grab; device_class->map_modifier = atspi_device_x11_map_modifier; device_class->unmap_modifier = atspi_device_x11_unmap_modifier; device_class->get_modifier = atspi_device_x11_get_modifier; device_class->remove_key_grab = atspi_device_x11_remove_key_grab; device_class->get_locked_modifiers = atspi_device_x11_get_locked_modifiers; device_class->grab_keyboard = atspi_device_x11_grab_keyboard; device_class->ungrab_keyboard = atspi_device_x11_ungrab_keyboard; object_class->finalize = atspi_device_x11_finalize; } /** * atspi_device_x11_new: * * Creates a new #AtspiDeviceX11. * * Returns: (transfer full): a pointer to a newly-created #AtspiDeviceX11. * **/ AtspiDeviceX11 * atspi_device_x11_new () { AtspiDeviceX11 *device = g_object_new (atspi_device_x11_get_type (), NULL); return device; }