Search code examples
linuxkeyboard-layoutxdotool

xdotool, ctrl key and keyboard layouts


Problem

I use xdotool keydown Control and xdotool keyup Control from my application to emulate Ctrl presses. When layout is set to us, everything works, but when layout changes to something else (fr or ru), applications stop seeing the ctrl events.

Question

Why is that happening? What can I do to make ctrl key manipulation work uniformly across layouts?

Some info

Command that I use to setup layouts:

setxkbmap -layout us,fr -option -option "grp:lctrl_lshift_toggle,ctrl:nocaps"

Output from xev with us layout:

KeyPress event, serial 25, synthetic NO, window 0x4a00001,
    root 0x5c, subw 0x0, time 11278564, (317,709), root:(1279,736),
    state 0x10, keycode 37 (keysym 0xffe3, Control_L), same_screen YES,
    XLookupString gives 0 bytes:
    XmbLookupString gives 0 bytes:
    XFilterEvent returns: False

KeyRelease event, serial 28, synthetic NO, window 0x4a00001,
    root 0x5c, subw 0x0, time 11278676, (317,709), root:(1279,736),
    state 0x14, keycode 37 (keysym 0xffe3, Control_L), same_screen YES,
    XLookupString gives 0 bytes:
    XFilterEvent returns: False

Output from xev with fr layout:

KeyPress event, serial 109, synthetic NO, window 0x4a00001,
    root 0x5c, subw 0x0, time 11343218, (312,520), root:(1274,547),
    state 0x2010, keycode 8 (keysym 0xffe3, Control_L), same_screen YES,
    XLookupString gives 0 bytes:
    XmbLookupString gives 0 bytes:
    XFilterEvent returns: False

MappingNotify event, serial 109, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 109, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 109, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 109, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 109, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 109, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 109, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 116, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 116, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 116, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 116, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 116, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 116, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 116, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

KeyRelease event, serial 123, synthetic NO, window 0x4a00001,
    root 0x5c, subw 0x0, time 11343460, (312,520), root:(1274,547),
    state 0x2010, keycode 8 (keysym 0xffe3, Control_L), same_screen YES,
    XLookupString gives 0 bytes:
    XFilterEvent returns: False

MappingNotify event, serial 123, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 123, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 123, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 123, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 123, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 123, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 123, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

Verbose output from setxkbmap:

Setting verbose level to 10
locale is C
Applied rules from evdev:
rules:      evdev
model:      pc105
layout:     us,fr
options:    grp:lctrl_lshift_toggle,ctrl:nocaps
Trying to build keymap using the following components:
keycodes:   evdev+aliases(qwerty)
types:      complete
compat:     complete
symbols:    pc+us+fr:2+inet(evdev)+group(lctrl_lshift_toggle)+ctrl(nocaps)
geometry:   pc(pc105)
xkb_keymap {
  xkb_keycodes  { include "evdev+aliases(qwerty)"   };
  xkb_types     { include "complete"    };
  xkb_compat    { include "complete"    };
  xkb_symbols   { include "pc+us+fr:2+inet(evdev)+group(lctrl_lshift_toggle)+ctrl(nocaps)"  };
  xkb_geometry  { include "pc(pc105)"   };
};

xmodmap output for control:

$  xmodmap -pme | grep -i control
control     Control_L (0x25),  Control_L (0x42),  Control_R (0x69)
$  xmodmap -pke | grep -i control
keycode  37 = Control_L Control_L Control_L Control_L
keycode  66 = Control_L Control_L Control_L Control_L
keycode 105 = Control_R NoSymbol Control_R

Solution

  • As you can see from your xev output, when in the us mode the Control_L key down is equivalent to keycode 37 with the state moving from 0x10 to 0x14, whereas in the fr mode you have keycode 8 and state 0x2010 not changing, and several MappingNotify events.

    The state is a bitmap of which modifiers are currently applied, eg shift, control, alt and so on. They can be shown with

    xmodmap -pme
    

    which for example for me (on a completely different keyboard setup) is currently

    shift       Shift_L (0x32),  Shift_R (0x3e)
    lock      
    control     Control_L (0x25),  Control_L (0x42),  Control_R (0x69)
    mod1        Meta_R (0x86)
    mod2        Num_Lock (0x4d)
    mod3      
    mod4      
    mod5        ISO_Level3_Shift (0x5c),  Mode_switch (0xcb)
    

    These list the 8 bit positions in state, with:

    1. shift = bit 0 (state 0x1),
    2. lock = bit 1 (state 0x2),
    3. control = bit 2 (state 0x4), etc.

    Your state 0x2010 includes a value 0x2000 beyond these 8 bits, which is how the 2 keymaps in one is implemented, and shows when you are in fr mode.

    If we look at how xdotool works, when you are in the 2nd key mapping as shown by state 0x2000, it looks up Control_L to find the keycode, then looks up the keycode in the xmodmap -pke columns for the current state, and doesnt find Control_L. So it takes a spare keycode 8, and temporarily changes the mapping so keycode 8 = keysym Control_L, and then sends that key event. Unfortunately, this keycode is not in the modifier mapping for the control bit.


    So perhaps what will work is if you change the mapping you have for keycode 37 so that all the columns have Control_L. I dont know how many columns you have, but do xmodmap -pke | grep 'keycode 37' and count them, then change them all eg:

    xmodmap -e 'keycode 37 = Control_L Control_L Control_L Control_L'
    

    As mentioned in the comments, xdotool key can take keycode decimal numbers instead of keysym arguments. This isn't mentioned in the man pages.