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 +}
