Skip to content

Incorrect keyboard mapping for Dvorak Programmer Layout on Xorg #652

@Adam-D-Lewis

Description

@Adam-D-Lewis

Description
When using pynput.Keyboard on Xorg to type on a keyboard with the Dvorak Programmer layout, most characters are correct, but a few are incorrect. If I try to type "Y" via Controller().press('Y') then an "F" is typed. I believe I've tracked this down to not having an AltGr key set on this keyboard layout.

Xlib.display.Display.keysym_to_keycode returns a 0 if the keysym is not bound to any key. This causes the __altgr_mask, and then the group_mask in pynput._util.xorg.keyboard_mapping (see below) to become 1, the same as the shift mask. When constructing the mapping dict in that function, the "Y" keysym is written to mapping correctly an first, but the key_codes to a few keysyms (e.g. "Y", "C", "?") get overwritten with the QWERTY layout key_code. A hacky workaround is shown below (added break statement).

def keyboard_mapping(display):
    """Generates a mapping from *keysyms* to *key codes* and required
    modifier shift states.

    :param Xlib.display.Display display: The display for which to retrieve the
        keyboard mapping.

    :return: the keyboard mapping
    """
    mapping = {}

    shift_mask = 1 << 0
    group_mask = alt_gr_mask(display)

    # Iterate over all keysym lists in the keyboard mapping
    min_keycode = display.display.info.min_keycode
    keycode_count = display.display.info.max_keycode - min_keycode + 1
    for index, keysyms in enumerate(display.get_keyboard_mapping(
            min_keycode, keycode_count)):
        key_code = index + min_keycode

        # Normalise the keysym list to yield a tuple containing the two groups
        normalized = keysym_normalize(keysyms)
        if not normalized:
            continue

        # Iterate over the groups to extract the shift and modifier state
        for groups, group in zip(normalized, (False, True)):
            for keysym, shift in zip(groups, (False, True)):
                if not keysym:
                    continue
                shift_state = 0 \
                    | (shift_mask if shift else 0) \
                    | (group_mask if group else 0)

                # Prefer already known lesser shift states
                if keysym in mapping and mapping[keysym][1] < shift_state:
                    continue
                mapping[keysym] = (key_code, shift_state)
            break  # <---------- Adding this break "fixes" the issue for me, though of course this isn't the right solution

    return mapping

Platform and pynput version
Your operating system and version, and the version of pynput.
Ubuntu 24.04
Pynput 1.8.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions