summaryrefslogtreecommitdiff
path: root/atspi/atspi-device-x11.c
diff options
context:
space:
mode:
authorMike Gorse <mgorse@suse.com>2021-01-11 13:32:06 -0600
committerMike Gorse <mgorse@suse.com>2021-01-11 13:32:06 -0600
commit7bd1ad706c92a9342e5078238ce4380e544aa967 (patch)
treef09ab765bb97dbcdd0cba047254768da7614eb02 /atspi/atspi-device-x11.c
parentbefced84236ec782d3a41d8cc06ceb3fdb61ae75 (diff)
downloadat-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.c635
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;
+}