Bug#1111485: Acknowledgement (orca: Incorrectly determines the active keyboard layout)

Илья Пащук ilusha.paschuk at gmail.com
Mon Aug 25 14:07:09 BST 2025


That is the fixed patch version, without non-English comments.


-------------- next part --------------
Index: at-spi2-core-2.56.2/atspi/atspi-device-x11.c
===================================================================
--- at-spi2-core-2.56.2.orig/atspi/atspi-device-x11.c
+++ at-spi2-core-2.56.2/atspi/atspi-device-x11.c
@@ -27,6 +27,7 @@
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 #include <X11/extensions/XInput2.h>
+#include <X11/keysymdef.h>
 
 #define ATSPI_VIRTUAL_MODIFIER_MASK 0x0000f000
 
@@ -343,6 +344,97 @@ set_virtual_modifier (AtspiDeviceX11 *x1
   refresh_key_grabs (x11_device);
 }
 
+
+/* Resolve keysym using explicit current group (st.group) and effective level
+ * computed from modifiers. Falls back to scan levels in current group, and
+ * finally group 0 to handle specials.
+ */
+static KeySym
+resolve_keysym_with_xkb (Display *dpy, KeyCode keycode, unsigned int state, unsigned int numlock_physical_mask)
+{
+  /* Ignore Lock/NumLock when selecting level */
+  unsigned int cleaned = state & ~(numlock_physical_mask | LockMask);
+
+  XkbStateRec st;
+  memset (&st, 0, sizeof (st));
+  XkbGetState (dpy, XkbUseCoreKbd, &st);
+
+  /* Compute modifier flags, not level index directly */
+  unsigned int altgr_mask = XkbKeysymToModifiers (dpy, XK_ISO_Level3_Shift);
+  unsigned int level5_mask = XkbKeysymToModifiers (dpy, XK_ISO_Level5_Shift);
+
+  gboolean shift_on = (cleaned & ShiftMask) != 0;
+  gboolean altgr_on = (altgr_mask && (cleaned & altgr_mask));
+  gboolean level5_on = (level5_mask && (cleaned & level5_mask));
+
+  /* Preferred level order for common 2/4/8-level layouts:
+   * - without Level5:
+   *   (none) -> [0,1,2,3]
+   *   (Shift) -> [1,0,3,2]
+   *   (AltGr) -> [2,3,0,1]
+   *   (Shift+AltGr) -> [3,2,1,0]
+   * - with Level5: same logic but prefer the upper levels (4..7) when Level5 is active
+   */
+  int candidates[8];
+  int n = 0;
+
+  if (!level5_on)
+    {
+      if (!shift_on && !altgr_on) { int pref[] = {0,1,2,3}; memcpy(candidates, pref, sizeof(pref)); n = 4; }
+      else if (shift_on && !altgr_on) { int pref[] = {1,0,3,2}; memcpy(candidates, pref, sizeof(pref)); n = 4; }
+      else if (!shift_on && altgr_on) { int pref[] = {2,3,0,1}; memcpy(candidates, pref, sizeof(pref)); n = 4; }
+      else /* shift_on && altgr_on */ { int pref[] = {3,2,1,0}; memcpy(candidates, pref, sizeof(pref)); n = 4; }
+
+      /* then append levels 4..7 as fallback */
+      for (int lvl = 4; lvl < 8; lvl++) candidates[n++] = lvl;
+    }
+  else
+    {
+      /* Level5 active: try 4..7 first, analogous logic */
+      if (!shift_on && !altgr_on) { int pref[] = {4,5,6,7}; memcpy(candidates, pref, sizeof(pref)); n = 4; }
+      else if (shift_on && !altgr_on) { int pref[] = {5,4,7,6}; memcpy(candidates, pref, sizeof(pref)); n = 4; }
+      else if (!shift_on && altgr_on) { int pref[] = {6,7,4,5}; memcpy(candidates, pref, sizeof(pref)); n = 4; }
+      else /* shift_on && altgr_on */ { int pref[] = {7,6,5,4}; memcpy(candidates, pref, sizeof(pref)); n = 4; }
+
+      /* then levels 0..3 as fallback */
+      for (int lvl = 0; lvl < 4; lvl++) candidates[n++] = lvl;
+    }
+
+  /* 1) Try candidates in current group */
+  for (int i = 0; i < n; i++)
+    {
+      KeySym t = XkbKeycodeToKeysym (dpy, keycode, st.group, candidates[i]);
+      if (t != NoSymbol && t != 0)
+        return t;
+    }
+
+  /* 2) Exhaustive scan of levels in current group */
+  for (int lvl = 0; lvl < 8; lvl++)
+    {
+      KeySym t = XkbKeycodeToKeysym (dpy, keycode, st.group, lvl);
+      if (t != NoSymbol && t != 0)
+        return t;
+    }
+
+  /* 3) Last chance: group 0, same candidates first */
+  for (int i = 0; i < n; i++)
+    {
+      KeySym t = XkbKeycodeToKeysym (dpy, keycode, 0, candidates[i]);
+      if (t != NoSymbol && t != 0)
+        return t;
+    }
+
+  /* 4) Exhaustive scan of group 0 */
+  for (int lvl = 0; lvl < 8; lvl++)
+    {
+      KeySym t = XkbKeycodeToKeysym (dpy, keycode, 0, lvl);
+      if (t != NoSymbol && t != 0)
+        return t;
+    }
+
+  return NoSymbol;
+}
+
 static gboolean
 do_event_dispatch (gpointer user_data)
 {
@@ -352,7 +444,6 @@ do_event_dispatch (gpointer user_data)
   XEvent xevent;
   char text[10];
   KeySym keysym;
-  XComposeStatus status;
   guint modifiers;
 
   g_object_ref (device);
@@ -365,7 +456,14 @@ do_event_dispatch (gpointer user_data)
         {
         case KeyPress:
         case KeyRelease:
-          XLookupString (&xevent.xkey, text, sizeof (text), &keysym, &status);
+          /* Resolve keysym via XKB using current state; avoid stale text. */
+          {
+            keysym = resolve_keysym_with_xkb (priv->display,
+                                              xevent.xkey.keycode,
+                                              xevent.xkey.state,
+                                              priv->numlock_physical_mask);
+            text[0] = '\0';
+          }
           modifiers = xevent.xkey.state | priv->virtual_mods_enabled;
           if (modifiers & priv->numlock_physical_mask)
             {
@@ -388,9 +486,14 @@ do_event_dispatch (gpointer user_data)
                 case XI_KeyPress:
                 case XI_KeyRelease:
                   xi2keyevent (xiDevEv, &keyevent);
-                  XLookupString ((XKeyEvent *) &keyevent, text, sizeof (text), &keysym, &status);
-                  if (text[0] < ' ')
+                  /* Resolve keysym via XKB using current state (XI2 path). */
+                  {
+                    keysym = resolve_keysym_with_xkb (priv->display,
+                                                      xiDevEv->detail,
+                                                      keyevent.xkey.state,
+                                                      priv->numlock_physical_mask);
                     text[0] = '\0';
+                  }
                   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)
Index: at-spi2-core-2.56.2/registryd/deviceeventcontroller-x11.c
===================================================================
--- at-spi2-core-2.56.2.orig/registryd/deviceeventcontroller-x11.c
+++ at-spi2-core-2.56.2/registryd/deviceeventcontroller-x11.c
@@ -720,6 +720,64 @@ spi_controller_register_with_devices (Sp
   x_default_error_handler = XSetErrorHandler (_spi_controller_device_error_handler);
 }
 
+/* Compute effective XKB level from modifier state:
+ * bit0: Shift, bit1: ISO_Level3_Shift (AltGr), bit2: ISO_Level5_Shift.
+ * Lock and NumLock do not affect level.
+ */
+static unsigned int
+get_level_from_state (Display *dpy, unsigned int state)
+{
+  unsigned int level = 0;
+  unsigned int altgr_mask = XkbKeysymToModifiers (dpy, XK_ISO_Level3_Shift);
+  unsigned int level5_mask = XkbKeysymToModifiers (dpy, XK_ISO_Level5_Shift);
+
+  if (state & ShiftMask)
+    level |= 1;
+  if (altgr_mask && (state & altgr_mask))
+    level |= 2;
+  if (level5_mask && (state & level5_mask))
+    level |= 4;
+
+  return level;
+}
+
+/* Resolve keysym using explicit current group (st.group) and effective level.
+ * Fallback: scan levels in current group, then group 0 for specials.
+ */
+static KeySym
+resolve_keysym_with_xkb (Display *dpy, KeyCode keycode, unsigned int state)
+{
+  unsigned int cleaned = state & ~(_numlock_physical_mask | LockMask);
+
+  XkbStateRec st;
+  memset (&st, 0, sizeof (st));
+  XkbGetState (dpy, XkbUseCoreKbd, &st);
+
+  unsigned int level = get_level_from_state (dpy, cleaned);
+
+  KeySym ks = XkbKeycodeToKeysym (dpy, keycode, st.group, level);
+  if (ks != NoSymbol && ks != 0)
+    return ks;
+
+  /* Fallback 1: scan levels in current group */
+  for (int lvl = 0; lvl < 8; lvl++)
+    {
+      KeySym t = XkbKeycodeToKeysym (dpy, keycode, st.group, lvl);
+      if (t != NoSymbol && t != 0)
+        return t;
+    }
+
+  /* Fallback 2: scan group 0 */
+  for (int lvl = 0; lvl < 8; lvl++)
+    {
+      KeySym t = XkbKeycodeToKeysym (dpy, keycode, 0, lvl);
+      if (t != NoSymbol && t != 0)
+        return t;
+    }
+
+  return NoSymbol;
+}
+
 static Accessibility_DeviceEvent
 spi_keystroke_from_x_key_event (XKeyEvent *x_key_event)
 {
@@ -729,7 +787,11 @@ spi_keystroke_from_x_key_event (XKeyEven
   char cbuf[21];
   int nbytes;
 
-  nbytes = XLookupString (x_key_event, cbuf, cbuf_bytes, &keysym, NULL);
+  /* Resolve keysym via XKB using current state; honors group and effective modifiers. */
+  keysym = resolve_keysym_with_xkb (spi_get_display (),
+                                    x_key_event->keycode,
+                                    x_key_event->state);
+
   key_event.id = (dbus_int32_t) (keysym);
   key_event.hw_code = (dbus_int16_t) x_key_event->keycode;
   if (((XEvent *) x_key_event)->type == KeyPress)
@@ -820,22 +882,21 @@ spi_keystroke_from_x_key_event (XKeyEven
       key_event.event_string = g_strdup ("Right");
       break;
     default:
-      if (nbytes > 0)
-        {
-          gunichar c;
-          cbuf[nbytes] = '\0'; /* OK since length is cbuf_bytes+1 */
-          key_event.event_string = g_strdup (cbuf);
-          c = keysym2ucs (keysym);
-          if (c > 0 && !g_unichar_iscntrl (c))
-            {
-              key_event.is_text = TRUE;
-              /* incorrect for some composed chars? */
-            }
-        }
-      else
-        {
-          key_event.event_string = g_strdup ("");
-        }
+      {
+        gunichar uc = (gunichar) keysym2ucs (keysym);
+        if (uc > 0 && !g_unichar_iscntrl (uc))
+          {
+            char utf8[6];
+            int len = g_unichar_to_utf8 (uc, utf8);
+            utf8[len] = '\0';
+            key_event.event_string = g_strdup (utf8);
+            key_event.is_text = TRUE;
+          }
+        else
+          {
+            key_event.event_string = g_strdup ("");
+          }
+      }
     }
 
   key_event.timestamp = (dbus_uint32_t) x_key_event->time;


More information about the Pkg-a11y-devel mailing list