Package: emacs21 Followup-For: Bug #255286 Hi,
I am investigating this issue, and believe that I now have some light to share. The attached test-emacs-mods.c is a debug copy of the x_find_modifier_meanings function in emacs-21.3/src/xterm.c It displays how emacs interpret modifier bits. First consider a pc104 us layout: $ setxkbmap -model pc104 -layout us -option $ xmodmap -pm shift Shift_L (0x32), Shift_R (0x3e) lock Caps_Lock (0x42) control Control_L (0x25), Control_R (0x6d) mod1 Alt_L (0x40), Alt_L (0x7d), Meta_L (0x9c) mod2 Num_Lock (0x4d) mod3 mod4 Super_L (0x7f), Hyper_L (0x80) mod5 Mode_switch (0x5d), ISO_Level3_Shift (0x7c) Keycodes are defined in /etc/X11/xkb/keycodes/xfree86. The key point is that keycodes 0x7d, 0x7f, 0x80 and 0x9c are called 'fake keys' because they do not correspond to physical keys, so a keyboard never send events associated with these keys. They are defined in /etc/X11/xkb/symbols/pc/pc key <ALT> { [ NoSymbol, Alt_L ] }; modifier_map Mod1 { <ALT>, <LALT> }; key <META> { [ NoSymbol, Meta_L ] }; modifier_map Mod1 { <META> }; key <SUPR> { [ NoSymbol, Super_L ] }; modifier_map Mod4 { <SUPR> }; key <HYPR> { [ NoSymbol, Hyper_L ] }; modifier_map Mod4 { <HYPR> }; which means that no symbol is bound to these keys (which is logical for fake keys), but their shift levels are bound to KeySyms. These definitions have then set mod1 = { <ALT>, <LALT>, <META> } mod4 = { <SUPR>, <HYPR> } In the pc104 section of /etc/X11/xkb/symbols/pc/pc, <LALT> is defined by key <LALT> { [ Alt_L, Meta_L ] }; When xmodmap display modifiers, it first look at the level 0 of each key in its list, which gives: <ALT> keycode 0x7d level 0 -> NoSymbol <LALT> keycode 0x40 level 0 -> Alt_L <META> keycode 0x9c level 0 -> NoSymbol It then scans level 1 of the symbols which had no symbol at level 0 <ALT> keycode 0x7d level 1 -> Alt_L <META> keycode 0x9c level 1 -> Meta_L and so forth until all keys found in modifier's list are represented by a KeySym. This explains the line from xmodmap's output: mod1 Alt_L (0x40), Alt_L (0x7d), Meta_L (0x9c) and mod4 Super_L (0x7f), Hyper_L (0x80) becomes obvious. Now consider Daniel's settings: $ setxkbmap -model pc104 -layout us -option -option altwin:meta_win $ xmodmap -pm mod1 Alt_L (0x40), Alt_L (0x7d) mod4 Meta_L (0x73), Meta_R (0x74), Super_L (0x7f), Hyper_L (0x80), Meta_L (0x9c) (other lines are removed for brevity). It slightly differs from Daniel's output due to recent changes in xlibs, but this is not important. By reading /etc/X11/xkb/symbols/{pc/pc,altwin}, one deduces that: key <LALT> { [ Alt_L, Meta_L ] }; key <RALT> { [ Alt_R, Meta_R ] }; key <LWIN> { [ Meta_L ] }; key <RWIN> { [ Meta_R ] }; key <ALT> { [ NoSymbol, Alt_L ] }; key <META> { [ NoSymbol, Meta_L ] }; and mod1 = { <ALT>, <LALT>, <META> } mod4 = { <SUPR>, <HYPR>, <META>, Meta_L, Meta_R } But Meta_L does not appear in xmodmap's output for mod1. The reason is hidden by setxkbmap, it is in fact stored in some logfile. It can be displayed on stderr with this trick: $ setxkbmap -model pc104 -layout us -option -option altwin:meta_win -print \ | xkbcomp -w 0 - :0 Error: Key <META> added to map for multiple modifiers Using Mod4, ignoring Mod1. Okay, so our modifiers in fact are bound to those keys: mod1 = { <ALT>, <LALT> } mod4 = { <SUPR>, <HYPR>, <META>, Meta_L, Meta_R } Now, let's compute the associated keycodes and KeySyms: <ALT> keycode 0x7d level 0 -> NoSymbol <LALT> keycode 0x40 level 0 -> Alt_L <ALT> keycode 0x7d level 1 -> Alt_L <SUPR> keycode 0x7f level 0 -> NoSymbol <HYPR> keycode 0x80 level 0 -> NoSymbol <META> keycode 0x9c level 0 -> NoSymbol Meta_L keycode ???? Meta_R keycode ???? <SUPR> keycode 0x7f level 1 -> Super_L <HYPR> keycode 0x80 level 1 -> Hyper_L <META> keycode 0x9c level 1 -> Meta_L Until now, modifiers were defined by keycodes only, but there are two KeySyms above: Meta_L and Meta_R. The KeySym <-> keycode mapping is not unique, and to find the keycode bound to these KeySyms, xmodmap plays the same game: it scans level 0 for all keycodes, then if no matching KeySym is found it goes to level 1, 2, etc. In our case, Meta_L is defined at the level 0 of <LWIN> and level 1 of <LALT>, but the rule tells to return <LWIN> which is 0x73. We now can understand xmodmap output, and it is time to go back to this bugreport, and compile and run the attached file: $ gcc -Wall -o test-emacs-mods test-emacs-mods.c \ -I/usr/X11R6/include -L /usr/X11R6/lib -lX11 $ ./test-emacs-mods Mod1 keycode=0x40 (pos=0) level 0 -> Alt_L Mod1 keycode=0x40 (pos=0) level 1 -> Meta_L Mod1 keycode=0x7d (pos=1) level 1 -> Alt_L Mod4 keycode=0x73 (pos=0) level 0 -> Meta_L Mod4 keycode=0x74 (pos=1) level 0 -> Meta_R Mod4 keycode=0x7f (pos=2) level 1 -> Super_L Mod4 keycode=0x80 (pos=3) level 1 -> Hyper_L Mod4 keycode=0x9c (pos=4) level 1 -> Meta_L Up to 4 symbols per modifier alt_mod_mask=8 meta_mod_mask=72 super_mod_mask=64 hyper_mod_mask=64 (the 3 last lines are deleted for now) This program displays how (x)emacs interprets X modifiers; unlike xmodmap, it scans all levels of all keys, so Meta_L is bound to mod1 and mod4. The x_x_to_emacs_modifiers function (also is src/xterm.c) contains: return ( ((state & (ShiftMask | dpyinfo->shift_lock_mask)) ? shift_modifier : 0) | ((state & ControlMask) ? ctrl_modifier : 0) | ((state & dpyinfo->meta_mod_mask) ? meta_modifier : 0) | ((state & dpyinfo->alt_mod_mask) ? alt_modifier : 0) | ((state & dpyinfo->super_mod_mask) ? super_modifier : 0) | ((state & dpyinfo->hyper_mod_mask) ? hyper_modifier : 0)); so when a M-x is caught, state is 64 (0x40 in xev output) and this function tells emacs that Meta, Super and Hyper modifiers are active. The lines above clearly show that emacs does not accept different keys being bound to the same X modifier (the meta/alt case is special, I will discuss about it below). Some people claim that this is a requirement for ICCCM compliance. I am not an expert, know nothing about this spec and so read it. All I found is: Clients should determine the meaning of a modifier bit from the KeySyms being used to control it. My understanding is that from (x)emacs authors point of view, if mod4 is bound to Hyper and Super keysyms, client applications cannot guess what this modifier is for. But this quoted sentence is too vague, and can also be read as: "One cannot assume that mod1 is Alt or mod4 is Super, so client applications should check the KeySym returned by the X server." The right Keysym can be chosen by applying the rules explained above, when xmodmap output was discussed. Next, if different keys are bound to the same modifier, (x)emacs is unable to handle these keys. So instead of being unusable, it could decide to make this mapping unique by deleting some keys. It won't work for every configuration, but at least it could work for some people. A good start is to stop parsing a keycode when a modifier key has been seen, or even only consider level 0. Last, the x_find_modifier_meanings function in src/xterm.c has a special handling for Alt/Meta modifiers, explained by these 2 comments: /* If we couldn't find any meta keys, accept any alt keys as meta keys. */ /* If some keys are both alt and meta, make them just meta, not alt. */ So if alt and meta have a special handling, maybe it is worth trying to handle current XFree86 configurations too? I am not telling that I know the truth, but at the moment fixing (x)emacs to not choke on current XFree86 configuration is the easiest solution to help emacs users. It would be great to also provide an XFree86 option to disable those fake keys, but I had no luck until now. My first idea was to skip fake keys, see http://lists.debian.org/debian-x/2004/09/msg00284.html It works with altwin:super_win, but not altwin:meta_win because emacs believes that Meta is bound to mod1 and mod4 (though I am not sure that makes trouble). This can be solved by considering only level 0 of keycodes. But the best solution is to rewrite the x_find_modifier_meanings function to mimic xmodmap behavior, I will try to provide a patch after thinking more about this (and sleeping too, this won't hurt). Denis
#include <stdio.h> #include <X11/Xlib.h> #include <X11/keysym.h> #define PRINT(s) fprintf(stderr, "Mod%d keycode=0x%x (pos=%d) level %d -> %s\n", \ row - 2, code, col, code_col, s) /* Copied from emacs-21.3/src/xterm.c (x_find_modifier_meanings) Compile with: gcc -Wall -o test-emacs-mods test-emacs-mods.c \ -I/usr/X11R6/include -L /usr/X11R6/lib -lX11 */ int main (void) { Display *display = XOpenDisplay(":0"); int min_code, max_code; KeySym *syms; int syms_per_code; XModifierKeymap *mods; unsigned int shift_lock_mask = 0; unsigned int meta_mod_mask, alt_mod_mask, hyper_mod_mask, super_mod_mask; meta_mod_mask = alt_mod_mask = hyper_mod_mask = super_mod_mask = 0; XDisplayKeycodes (display, &min_code, &max_code); syms = XGetKeyboardMapping (display, min_code, max_code - min_code + 1, &syms_per_code); mods = XGetModifierMapping (display); /* Scan the modifier table to see which modifier bits the Meta and Alt keysyms are on. */ { int row, col; /* The row and column in the modifier table. */ for (row = 3; row < 8; row++) for (col = 0; col < mods->max_keypermod; col++) { KeyCode code = mods->modifiermap[(row * mods->max_keypermod) + col]; /* Zeroes are used for filler. Skip them. */ if (code == 0) continue; /* Are any of this keycode's keysyms a meta key? */ { int code_col; for (code_col = 0; code_col < syms_per_code; code_col++) { int sym = syms[((code - min_code) * syms_per_code) + code_col]; switch (sym) { case XK_Meta_L: PRINT("Meta_L"); meta_mod_mask |= (1 << row); break; case XK_Meta_R: PRINT("Meta_R"); meta_mod_mask |= (1 << row); break; case XK_Alt_L: PRINT("Alt_L"); alt_mod_mask |= (1 << row); break; case XK_Alt_R: PRINT("Alt_R"); alt_mod_mask |= (1 << row); break; case XK_Hyper_L: PRINT("Hyper_L"); hyper_mod_mask |= (1 << row); break; case XK_Hyper_R: PRINT("Hyper_R"); hyper_mod_mask |= (1 << row); break; case XK_Super_L: PRINT("Super_L"); super_mod_mask |= (1 << row); break; case XK_Super_R: PRINT("Super_R"); super_mod_mask |= (1 << row); break; case XK_Shift_Lock: /* Ignore this if it's not on the lock modifier. */ if ((1 << row) == LockMask) { PRINT("Super_R"); shift_lock_mask = LockMask; } break; } } } } } fprintf(stderr, "Up to %d symbols per modifier\n", syms_per_code); fprintf(stderr, " alt_mod_mask=%d\n", alt_mod_mask); fprintf(stderr, " meta_mod_mask=%d\n", meta_mod_mask); fprintf(stderr, " super_mod_mask=%d\n", super_mod_mask); fprintf(stderr, " hyper_mod_mask=%d\n", hyper_mod_mask); /* If we couldn't find any meta keys, accept any alt keys as meta keys. */ if (! meta_mod_mask) { meta_mod_mask = alt_mod_mask; alt_mod_mask = 0; } /* If some keys are both alt and meta, make them just meta, not alt. */ if (alt_mod_mask & meta_mod_mask) { alt_mod_mask &= ~meta_mod_mask; } fprintf(stderr, "Fixing alt and meta mods:\n"); fprintf(stderr, " alt_mod_mask=%d\n", alt_mod_mask); fprintf(stderr, " meta_mod_mask=%d\n", meta_mod_mask); XFree ((char *) syms); XFreeModifiermap (mods); return(0); }