commit:     52bc84a9b01231e584021a095f66016f674e2967
Author:     Ionen Wolkens <ionen <AT> gentoo <DOT> org>
AuthorDate: Fri Nov 28 18:26:37 2025 +0000
Commit:     Ionen Wolkens <ionen <AT> gentoo <DOT> org>
CommitDate: Fri Nov 28 18:34:46 2025 +0000
URL:        https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=52bc84a9

x11-misc/xdotool: backport WIP Fn keys fix for xkeyboard-config-2.46

Would have preferred to make the fix ~testing only given upstream has
not merged this yet and is still seeking feedback on whether this works
right, not to mention that this is a rebase applied to the (4 years old)
release that may or may not be missing something -- but 2.46 is already
stable and given users often use Fn keys in automation, this may break
several users' workflow.

For users experiencing issues, will keep the old unpatched -r1 in case
until a proper release. May mask -r2 if turns out it has severe issues.

Closes: https://bugs.gentoo.org/966686
Signed-off-by: Ionen Wolkens <ionen <AT> gentoo.org>

 ...dotool-3.20211022.1-xkeyboard-config-2.46.patch | 347 +++++++++++++++++++++
 x11-misc/xdotool/xdotool-3.20211022.1-r2.ebuild    |  59 ++++
 2 files changed, 406 insertions(+)

diff --git 
a/x11-misc/xdotool/files/xdotool-3.20211022.1-xkeyboard-config-2.46.patch 
b/x11-misc/xdotool/files/xdotool-3.20211022.1-xkeyboard-config-2.46.patch
new file mode 100644
index 000000000000..65264d173a96
--- /dev/null
+++ b/x11-misc/xdotool/files/xdotool-3.20211022.1-xkeyboard-config-2.46.patch
@@ -0,0 +1,347 @@
+https://bugs.gentoo.org/966686
+https://github.com/jordansissel/xdotool/issues/491
+https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config/-/issues/554
+
+Rebased version of a not-yet-merged fix[1] (excludes tests) that, as
+of the writing of this, is still undergoing testing upstream and may
+or may not work as expected (hoping for a new release soon).
+
+[1] https://github.com/jordansissel/xdotool/pull/493
+--- a/xdo.c
++++ b/xdo.c
+@@ -48,2 +48,5 @@
+ 
++static const char *vmodnames(Display *dpy, XkbDescPtr desc, short vmods);
++static const char *modnames(short mask);
++
+ static void _xdo_populate_charcode_map(xdo_t *xdo);
+@@ -66,3 +69,2 @@
+ 
+-static int _xdo_query_keycode_to_modifier(XModifierKeymap *modmap, KeyCode 
keycode);
+ static int _xdo_mousebutton(const xdo_t *xdo, Window window, int button, int 
is_press);
+@@ -144,2 +146,6 @@
+ 
++  if (getenv("DEBUG")) {
++    xdo->debug = True;
++  }
++
+   if (_xdo_has_xtest(xdo)) {
+@@ -1090,3 +1096,4 @@
+       KeySym keysym_list[] = { keys[i].symbol };
+-      _xdo_debug(xdo, "Mapping sym %lu to %d", keys[i].symbol, 
scratch_keycode);
++      const char *text = XKeysymToString(keys[i].symbol);
++      _xdo_debug(xdo, "Mapping sym %lu (%s) to %d", keys[i].symbol, text, 
scratch_keycode);
+       XChangeKeyboardMapping(xdo->xdpy, scratch_keycode, 1, keysym_list, 1);
+@@ -1283,2 +1290,3 @@
+   _xdo_charcodemap_from_keysym(xdo, key, keysym);
++  _xdo_debug(xdo, "Char '%c' made with Symbol(%s) using keycode %d mask %s", 
key->key, XKeysymToString(keysym), key->code, modnames(key->modmask));
+ 
+@@ -1312,2 +1320,3 @@
+   }
++  _xdo_debug(xdo, "No mapping found: Symbol(%s)", XKeysymToString(keysym));
+ }
+@@ -1319,46 +1328,143 @@
+ 
+-static void _xdo_populate_charcode_map(xdo_t *xdo) {
+-  /* assert xdo->display is valid */
+-  int keycodes_length = 0;
+-  int idx = 0;
+-  int keycode, group, groups, level, modmask, num_map;
+-
+-  XDisplayKeycodes(xdo->xdpy, &(xdo->keycode_low), &(xdo->keycode_high));
+-  XModifierKeymap *modmap = XGetModifierMapping(xdo->xdpy);
+-  KeySym *keysyms = XGetKeyboardMapping(xdo->xdpy, xdo->keycode_low,
+-                                        xdo->keycode_high - xdo->keycode_low 
+ 1,
+-                                        &xdo->keysyms_per_keycode);
+-  XFree(keysyms);
++#define AddCharcodeEntry(idx, xdo, keysym, keycode, group, mask) \
++          if (idx == charcodes_size) { \
++            xdo->charcodes = realloc(xdo->charcodes, (charcodes_size += 100) 
* sizeof(charcodemap_t)); \
++          } \
++          xdo->charcodes[idx].key = _keysym_to_char(keysym); \
++          xdo->charcodes[idx].code = keycode; \
++          xdo->charcodes[idx].group = group; \
++          xdo->charcodes[idx].modmask = mask; \
++          xdo->charcodes[idx].symbol = keysym; \
++          xdo->charcodes_len = idx; \
++          idx++; 
+ 
+-  /* Add 2 to the size because the range [low, high] is inclusive */
+-  /* Add 2 more for tab (\t) and newline (\n) */
+-  keycodes_length = ((xdo->keycode_high - xdo->keycode_low) + 1)
+-                     * xdo->keysyms_per_keycode;
+ 
+-  xdo->charcodes = calloc(keycodes_length, sizeof(charcodemap_t));
++static void _xdo_populate_charcode_map(xdo_t *xdo) {
++  size_t idx = 0;
+   XkbDescPtr desc = XkbGetMap(xdo->xdpy, XkbAllClientInfoMask, XkbUseCoreKbd);
+ 
+-  for (keycode = xdo->keycode_low; keycode <= xdo->keycode_high; keycode++) {
+-    groups = XkbKeyNumGroups(desc, keycode);
+-    for (group = 0; group < groups; group++) {
++  xdo->keycode_low = desc->min_key_code;
++  xdo->keycode_high = desc->max_key_code;
++
++  // Fetch atom names so XGetAtomName works on Xkb atoms
++  XkbGetNames(
++      xdo->xdpy,
++      XkbKeyTypeNamesMask | XkbKTLevelNamesMask | XkbVirtualModNamesMask, 
desc);
++
++  size_t charcodes_size = 100;
++
++  xdo->charcodes = calloc(charcodes_size, sizeof(charcodemap_t));
++
++  Display* dpy = xdo->xdpy;
++
++  // Scan all known keycodes and modifier mappings to find what symbols can 
be typed
++  for (int keycode = desc->min_key_code; keycode <= desc->max_key_code; 
keycode++) {
++
++    // For each keycode, scan across the groups
++    for (int group = 0; group < XkbKeyNumGroups(desc, keycode); group++) {
++
+       XkbKeyTypePtr key_type = XkbKeyKeyType(desc, keycode, group);
+-      for (level = 0; level < key_type->num_levels; level++) {
+-        KeySym keysym = XkbKeycodeToKeysym(xdo->xdpy, keycode, group, level);
+-        modmask = 0;
+-
+-        for (num_map = 0; num_map < key_type->map_count; num_map++) {
+-          XkbKTMapEntryRec map = key_type->map[num_map];
+-          if (map.active && map.level == level) {
+-            modmask = map.mods.mask;
+-            break;
++      if (key_type->num_levels == 0) {
++        printf("Bug? No shift levels found for code:%d, group: %d\n", keycode,
++               group);
++        continue;
++      }
++      // For each shift level on this keycode and group
++      for (unsigned char li = 0; li < key_type->num_levels; li++) {
++        XkbKTMapEntryRec map = {
++          .active =  False,
++          .level =  0,
++          .mods = {
++            .mask = 0,
++            .real_mods = 0,
++            .vmods = 0,
+           }
++        };
++
++        KeySym keysym = XkbKeycodeToKeysym(dpy, keycode, group, map.level);
++
++        if (keysym == NoSymbol) {
++          // No keysym produced by the given (keycode, group, shift level)
++          _xdo_debug(
++              xdo, "[group %d, level %s[%d]] (KT: %s) keycode %d is not bound 
at this level",
++              group, XGetAtomName(dpy, key_type->level_names[li]),
++              li + 1 /* levels are named starting at 1 */,
++              XGetAtomName(dpy, key_type->name), keycode);
++          continue;
++        }
++
++        /* From:
++         * 
https://x.z-yx.cc/libX11/XKB/16-chapter-15-xkb-client-keyboard-mapping.html#The_Canonical_Key_Types
++         * > Any combination of modifiers not explicitly listed somewhere in
++         * > the map yields shift level one.
++         *
++         * Also: xkbcomp will delete all level 1 key map entries when 
compiling.
++         * Reference:
++         * 
https://gitlab.freedesktop.org/xorg/app/xkbcomp/-/blob/d03a4ab1c0b24f6581411622ccf729ceb329aeb8/keytypes.c#L1247
++         *
++         * Thus, if no active map entry is found, and the current shift
++         * level is "one" (0), we must assume that the keycode alone will
++         * produce a keysym.
++         *
++         * Further, if no map entry with 'mask == 0' is found, it means that
++         * shift level one is produced when the keycode is used without any
++         * modifiers.
++         *
++         * As a shortcut, for shift level 1 (li == 0), check if the keycode
++         * produces this level's keysym without any modifiers. If so, then
++         * prefer it in the keymap.
++         */
++        if (li == 0) {
++          KeySym key_without_mask;
++          // Ask what keysym is produced by this keycode when mask == 0.
++          if (XkbTranslateKeyCode(desc, keycode, 0 /* modifier mask */, NULL, 
&key_without_mask) == True) {
++            // Safety check: does the keysym found above match the keysm 
produced by this shift level?
++            if (key_without_mask == keysym) {
++              // pretend we found the map entry in the XkbKTMap
++              map.level = li;
++              map.active = True;
++            }
++          }
++        }
++
++        // Find out if there's any active modifier mappings for this keycode, 
group, and shift level.
++        // We can stop looking after we find one.
++        for (int mi = 0; mi < key_type->map_count && !map.active; mi++) {
++          if (key_type->map[mi].active && key_type->map[mi].level == li) {
++            //_xdo_debug(xdo, "Found modifiers -> level mapping for keycode 
%d level %d", keycode, li);
++            memcpy(&map, &(key_type->map[mi]), sizeof(map));
++
++            if (map.mods.real_mods == 0 && map.mods.vmods == 0) {
++              _xdo_debug(xdo, "Warning: found a mod entry with mods=0. This 
isn't expected?");
++            }
++            keysym = XkbKeycodeToKeysym(dpy, keycode, group, map.level);
++          }
++        }
++
++        // If no active mapping is found, then no keysym is produced by this
++        // keycode+modifier at this shift level.
++        if (!map.active) {
++            continue;
+         }
+ 
+-        xdo->charcodes[idx].key = _keysym_to_char(keysym);
+-        xdo->charcodes[idx].code = keycode;
+-        xdo->charcodes[idx].group = group;
+-        xdo->charcodes[idx].modmask = modmask | 
_xdo_query_keycode_to_modifier(modmap, keycode);
+-        xdo->charcodes[idx].symbol = keysym;
++        _xdo_debug(
++          xdo,
++          "[group %d, level %s[%d]] Symbol(%s) = keycode %d with "
++          "mask:%s, "
++          "real_mods:%x, vmods:%s%s",
++          group, XGetAtomName(dpy, key_type->level_names[li]),
++          li + 1 /* levels are named starting at 1 */,
++          XKeysymToString(keysym), keycode,
++          modnames(map.mods.mask), map.mods.real_mods,
++          vmodnames(dpy, desc, map.mods.vmods),
++
++          // Here, "IMPLICIT" means: Xkb defines any missing mappings as
++          // reaching shift level 1. Per the Xkb documentation: 
++          // > Any combination of modifiers not explicitly listed somewhere 
in the
++          // > map yields shift level one
++          // If no active mapping was found for level 1 (li == 0), the 
keycode alone
++          // shall be valid.
++          (!map.active && li == 0) ? " [IMPLICIT] " : "");
+ 
+-        idx++;
++        AddCharcodeEntry(idx, xdo, keysym, keycode, group, map.mods.mask);
+       }
+@@ -1366,7 +1472,7 @@
+   }
+-  xdo->charcodes_len = idx;
+-  XkbFreeClientMap(desc, 0, 1);
+-  XFreeModifiermap(modmap);
++
++  XkbFreeKeyboard(desc, 0, True);
+ }
+ 
++
+ /* context-free functions */
+@@ -1559,3 +1665,2 @@
+   if (use_xtest) {
+-    //printf("XTEST: Sending key %d %s\n", key->code, is_press ? "down" : 
"up");
+     XkbStateRec state;
+@@ -1566,3 +1671,4 @@
+       _xdo_send_modifier(xdo, mask, is_press);
+-    //printf("XTEST: Sending key %d %s %x %d\n", key->code, is_press ? "down" 
: "up", key->modmask, key->group);
++    _xdo_debug(xdo, "XTEST: %s key: keycode %d", is_press ? "Press" : 
"Release",
++               key->code);
+     XTestFakeKeyEvent(xdo->xdpy, key->code, is_press, CurrentTime);
+@@ -1571,2 +1677,3 @@
+   } else {
++    _xdo_debug(xdo, "XSendEvent: Sending key %s, keycode %d mask %s\n", 
is_press ? "down" : "up", key->code, modnames(key->modmask));
+     /* Since key events have 'state' (shift, etc) in the event, we don't
+@@ -1589,27 +1696,2 @@
+ 
+-int _xdo_query_keycode_to_modifier(XModifierKeymap *modmap, KeyCode keycode) {
+-  int i = 0, j = 0;
+-  int max = modmap->max_keypermod;
+-
+-  for (i = 0; i < 8; i++) { /* 8 modifier types, per XGetModifierMapping(3X) 
*/
+-    for (j = 0; j < max && modmap->modifiermap[(i * max) + j]; j++) {
+-      if (keycode == modmap->modifiermap[(i * max) + j]) {
+-        switch (i) {
+-          case ShiftMapIndex: return ShiftMask; break;
+-          case LockMapIndex: return LockMask; break;
+-          case ControlMapIndex: return ControlMask; break;
+-          case Mod1MapIndex: return Mod1Mask; break;
+-          case Mod2MapIndex: return Mod2Mask; break;
+-          case Mod3MapIndex: return Mod3Mask; break;
+-          case Mod4MapIndex: return Mod4Mask; break;
+-          case Mod5MapIndex: return Mod5Mask; break;
+-        }
+-      } /* end if */
+-    } /* end loop j */
+-  } /* end loop i */
+-
+-  /* No modifier found for this keycode, return no mask */
+-  return 0;
+-}
+-
+ void _xdo_send_modifier(const xdo_t *xdo, int modmask, int is_press) {
+@@ -1623,2 +1705,8 @@
+         if (keycode) {
++          _xdo_debug(
++            xdo,
++            "XTEST: %s modifier %s using keycode %d",
++            is_press ? "Press" : "Release",
++            modnames(modmask & (1 << mod_index)),
++            keycode);
+           XTestFakeKeyEvent(xdo->xdpy, keycode, is_press, CurrentTime);
+@@ -2060,2 +2148,62 @@
+   return 0;
++}
++
++static const char *vmodnames(Display *dpy, XkbDescPtr desc, short vmods) {
++  static char names[1024];
++  memset(names, 0, sizeof(names));
++
++  if (vmods == 0) {
++    return "<none>";
++  }
++
++  for (int i = 0; (1 << i) <= vmods; i++) {
++    if (vmods & (1 << i)) {
++      const char *n = XGetAtomName(dpy, desc->names->vmods[i]);
++      if (names[0] == 0) {
++        strncpy(names, n, 20);
++      } else {
++        strncpy(names+strlen(names), "+", 20);
++        strncpy(names+strlen(names), n,20);
++      }
++    }
++  }
++
++  if (strlen(names) == 0) {
++    printf("bug: empty vmod string for value: %d\n", vmods);
++  }
++
++  return names;
++}
++
++static const char *modnames(short mask) {
++  static char names[1024];
++  memset(names, 0, sizeof(names));
++
++  if (mask == 0) {
++    return "<none>";
++  }
++
++  if (mask & ShiftMask)
++    strncat(names, "+Shift", 20);
++  if (mask & LockMask)
++    strncat(names, "+Lock", 20);
++  if (mask & ControlMask)
++    strncat(names, "+Control", 20);
++  if (mask & Mod1Mask)
++    strncat(names, "+Mod1", 20);
++  if (mask & Mod2Mask)
++    strncat(names, "+Mod2", 20);
++  if (mask & Mod3Mask)
++    strncat(names, "+Mod3", 20);
++  if (mask & Mod4Mask)
++    strncat(names, "+Mod4", 20);
++  if (mask & Mod5Mask)
++    strncat(names, "+Mod5", 20);
++
++  if (names[0] == '+') {
++    memmove(names, names+1, strlen(names)-1);
++    names[strlen(names)-1] = 0;
++  }
++
++  return names;
+ }
+\ No newline at end of file

diff --git a/x11-misc/xdotool/xdotool-3.20211022.1-r2.ebuild 
b/x11-misc/xdotool/xdotool-3.20211022.1-r2.ebuild
new file mode 100644
index 000000000000..cb4ec98d46e4
--- /dev/null
+++ b/x11-misc/xdotool/xdotool-3.20211022.1-r2.ebuild
@@ -0,0 +1,59 @@
+# Copyright 1999-2025 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=8
+
+DOCS_BUILDER="doxygen"
+inherit docs toolchain-funcs
+
+DESCRIPTION="Simulate keyboard input and mouse activity, move and resize 
windows"
+HOMEPAGE="https://www.semicomplete.com/projects/xdotool/";
+SRC_URI="https://github.com/jordansissel/xdotool/releases/download/v${PV}/${P}.tar.gz";
+
+LICENSE="BSD"
+SLOT="0"
+KEYWORDS="amd64 arm arm64 ~loong ppc ppc64 ~riscv ~sparc x86"
+IUSE="examples"
+
+# tests have various troublesome requirements
+RESTRICT="test"
+
+# libXi is "unused" but it still uses headers from it and relies on the
+# compiler optimizing out a function to be dropped by as-needed, so keep
+# until next release (not important to patch given libXtst pulls libXi
+# anyway, so the dependency is not really avoidable either way)
+# https://github.com/jordansissel/xdotool/pull/446
+RDEPEND="
+       x11-libs/libX11
+       x11-libs/libXi
+       x11-libs/libXinerama
+       x11-libs/libXtst
+       x11-libs/libxkbcommon
+"
+DEPEND="
+       ${RDEPEND}
+       x11-base/xorg-proto
+"
+BDEPEND="
+       virtual/pkgconfig
+"
+
+PATCHES=(
+       "${FILESDIR}"/${PN}-3.20210804.2-no_hardcoded_pkg-config.patch
+       "${FILESDIR}"/${P}-xkeyboard-config-2.46.patch
+)
+
+src_compile() {
+       tc-export CC LD PKG_CONFIG
+
+       emake PREFIX="${EPREFIX}"/usr
+       docs_compile
+}
+
+src_install() {
+       emake PREFIX="${ED}"/usr INSTALLMAN="${ED}"/usr/share/man \
+               INSTALLLIB="${ED}"/usr/$(get_libdir) LDCONFIG=: install
+
+       dodoc -r CHANGELIST $(usev examples)
+       einstalldocs
+}

Reply via email to