Bug#1111485: Acknowledgement (orca: Incorrectly determines the active keyboard layout)
Илья Пащук
ilusha.paschuk at gmail.com
Sun Aug 24 11:35:58 BST 2025
new info:
On testing, I found that my patch solves the main problem, but creates
an other problem, less critical.
Problem description:
steps to reproduce:
1) activate a russian keyboard layout
2) press digital keys with shift (shift+1, shift+2, shift+3 ...)
result: some input chars does not spoken correctly.
In particular:
shift+1 incorrect
shift+5 incorrect
9 0 incorrect
- incorrect
-= incorrect
key point: characters, for which shift+digit value is identical in the
russian and english layouts, are spoken incorrectly.
To fix this, I updated the patch (ai-generation again)
New patch is attached, testing shows correct work now.
-------------- 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)
+{
+ /* Игнорируем Lock/NumLock при выборе уровня */
+ unsigned int cleaned = state & ~(numlock_physical_mask | LockMask);
+
+ XkbStateRec st;
+ memset (&st, 0, sizeof (st));
+ XkbGetState (dpy, XkbUseCoreKbd, &st);
+
+ /* Вычислим «флаги» модификаторов, а не индекс уровня напрямую */
+ 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));
+
+ /* Приоритетная последовательность уровней для распространённых типов (2/4/8 level):
+ * - без Level5:
+ * (none) -> [0,1,2,3]
+ * (Shift) -> [1,0,3,2]
+ * (AltGr) -> [2,3,0,1]
+ * (Shift+AltGr) -> [3,2,1,0]
+ * - с Level5: аналогично, но сначала проверяем «высшие» уровни (4..7) при активном Level5
+ */
+ 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; }
+
+ /* затем добиваем уровни 4..7 про запас */
+ for (int lvl = 4; lvl < 8; lvl++) candidates[n++] = lvl;
+ }
+ else
+ {
+ /* Level5 активен: сначала 4..7, по аналогичной логике */
+ 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; }
+
+ /* затем уровни 0..3 как запасной вариант */
+ for (int lvl = 0; lvl < 4; lvl++) candidates[n++] = lvl;
+ }
+
+ /* 1) Пробуем кандидатов в текущей группе */
+ for (int i = 0; i < n; i++)
+ {
+ KeySym t = XkbKeycodeToKeysym (dpy, keycode, st.group, candidates[i]);
+ if (t != NoSymbol && t != 0)
+ return t;
+ }
+
+ /* 2) Полный перебор уровней текущей группы на всякий случай */
+ for (int lvl = 0; lvl < 8; lvl++)
+ {
+ KeySym t = XkbKeycodeToKeysym (dpy, keycode, st.group, lvl);
+ if (t != NoSymbol && t != 0)
+ return t;
+ }
+
+ /* 3) Последний шанс: группа 0, сначала по тем же кандидатам */
+ for (int i = 0; i < n; i++)
+ {
+ KeySym t = XkbKeycodeToKeysym (dpy, keycode, 0, candidates[i]);
+ if (t != NoSymbol && t != 0)
+ return t;
+ }
+
+ /* 4) И полный перебор группы 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