Bug#255286: Meta has become alt?
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);
}
Reply to: