[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

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: