diff options
author | Mike Gorse <mgorse@suse.com> | 2021-01-11 13:32:06 -0600 |
---|---|---|
committer | Mike Gorse <mgorse@suse.com> | 2021-01-11 13:32:06 -0600 |
commit | 7bd1ad706c92a9342e5078238ce4380e544aa967 (patch) | |
tree | f09ab765bb97dbcdd0cba047254768da7614eb02 /atspi/atspi-device-x11.c | |
parent | befced84236ec782d3a41d8cc06ceb3fdb61ae75 (diff) | |
download | at-spi2-core-7bd1ad706c92a9342e5078238ce4380e544aa967.tar.gz |
Add device API
This is intended to replace the registry-based method for capturing
keystrokes. It is needed because gtk 4 no longer sends key notifications
in a way that atk-bridge can process them. Unlike the original API, key
grabs are separated from key notifications. Clients wishing to consume
keystrokes must proactively register a grab for the given key. Currently,
there is a backend for X11 and an unfinished legacy back end using the old
registry-based method. Hopefully, there will be a mutter/wayland back end in
the future, but we need to define a protocol there first.
Diffstat (limited to 'atspi/atspi-device-x11.c')
-rw-r--r-- | atspi/atspi-device-x11.c | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/atspi/atspi-device-x11.c b/atspi/atspi-device-x11.c new file mode 100644 index 00000000..adc600be --- /dev/null +++ b/atspi/atspi-device-x11.c @@ -0,0 +1,635 @@ +/* + * 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-private.h" +#include "atspi-device-x11.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/XInput2.h> +#include <X11/XKBlib.h> + + +#define ATSPI_VIRTUAL_MODIFIER_MASK 0xffff0000 + +typedef struct _AtspiDeviceX11Private AtspiDeviceX11Private; +struct _AtspiDeviceX11Private +{ + Display *display; + Window window; + GSource *source; + int xi_opcode; + int device_id; + GSList *modifiers; + GSList *key_grabs; + guint virtual_mods_enabled; +}; + +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; +} 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); + 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 +enable_key_grab (AtspiDeviceX11 *x11_device, AtspiX11KeyGrab *grab) +{ + AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); + XIGrabModifiers xi_modifiers; + XIEventMask eventmask; + unsigned char mask[XIMaskLen (XI_LASTEVENT)] = { 0 }; + + g_return_if_fail (priv->display != NULL); + + xi_modifiers.modifiers = grab->kd->modifiers & ~ATSPI_VIRTUAL_MODIFIER_MASK; + xi_modifiers.status = 0; + + eventmask.deviceid = XIAllDevices; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + + XISetMask (mask, XI_KeyPress); + XISetMask (mask, XI_KeyRelease); + + if (!grab_has_active_duplicate (x11_device, grab)) + XIGrabKeycode (priv->display, XIAllMasterDevices, grab->kd->keycode, priv->window, XIGrabModeSync, XIGrabModeAsync, False, &eventmask, 1, &xi_modifiers); + grab->enabled = TRUE; +} + +static void +disable_key_grab (AtspiDeviceX11 *x11_device, AtspiX11KeyGrab *grab) +{ + AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (x11_device); + XIGrabModifiers xi_modifiers; + + g_return_if_fail (priv->display != NULL); + + if (!grab->enabled) + return; + + grab->enabled = FALSE; + + if (grab_has_active_duplicate (x11_device, grab)) + return; + + xi_modifiers.modifiers = grab->kd->modifiers & ~ATSPI_VIRTUAL_MODIFIER_MASK; + xi_modifiers.status = 0; + + XIUngrabKeycode (priv->display, XIAllMasterDevices, grab->kd->keycode, priv->window, sizeof(xi_modifiers), &xi_modifiers); +} + +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); + GSList *l; + + 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; + } + + for (l = priv->key_grabs; l; l = l->next) + { + AtspiX11KeyGrab *grab = l->data; + gboolean new_enabled = grab_should_be_enabled (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 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; + + while (XPending (display)) + { + XNextEvent (display, &xevent); + XEvent keyevent; + + switch (xevent.type) + { + case KeyPress: + case KeyRelease: + XLookupString(&xevent.xkey, text, sizeof (text), &keysym, &status); + atspi_device_notify_key (ATSPI_DEVICE (device), (xevent.type == KeyPress), xevent.xkey.keycode, keysym, xevent.xkey.state & priv->virtual_mods_enabled, text); + 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'; + if (!priv->device_id) + priv->device_id = xiDevEv->deviceid; + set_virtual_modifier (device, xiRawEv->detail, xevent.xcookie.evtype == XI_KeyPress); + if (xiDevEv->deviceid == priv->device_id) + atspi_device_notify_key (ATSPI_DEVICE (device), (xevent.xcookie.evtype == XI_KeyPress), xiRawEv->detail, keysym, keyevent.xkey.state, text); + XFreeEventData (priv->display, &xevent.xcookie); + break; + } + } + default: + if (XFilterEvent (&xevent, None)) + continue; + } + } + 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; + + 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 = 0x10000; + + while (ret) + { + 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) + { + g_free (entry); + priv->modifiers = g_slist_remove (priv->modifiers, 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 +atspi_device_x11_init (AtspiDeviceX11 *device) +{ + AtspiDeviceX11Private *priv = atspi_device_x11_get_instance_private (device); + int first_event, first_error; + + priv->display=XOpenDisplay(""); + g_return_if_fail (priv->display != NULL); + priv->window = DefaultRootWindow(priv->display); + + 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->window, &eventmask, 1); + create_event_source (device); + } + } +} + +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; + } + + 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 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 result; + + g_return_val_if_fail (priv->display != NULL, FALSE); + result = XGrabKeyboard (priv->display, priv->window, TRUE, GrabModeAsync, GrabModeSync, CurrentTime); + return (result == 0); +} + +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); + + g_return_if_fail (priv->display != NULL); + XUngrabKeyboard (priv->display, CurrentTime); +} + +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; +} |