2009-12-31

How to fix the Ctrl-Y Ctrl-Z inconsistency in GNOME Terminal with international keyboard layouts

This blog post describes and fixes an inconsistency between some keys with or without Ctrl in GNOME Terminal with international keyboard layouts.

The quick fix:

$ wget -O- -q http://pts-mini-gpl.googlecode.com/svn/trunk/pts-vtefix/vtefix.sh |
sudo bash

In GNOME Terminal, with the Hungarian and German keyboard layouts the keys Y and Z are switched with respect to the US English layout. So when the national layout is active, one of the keys yields the string "z", but when the same key is pressed with Ctrl, it yields "^Y", instead of the expected "^Z". All other applications (e.g. GIMP, Firefox, xterm) work as expected, except for those using VTE (e.g. GNOME Terminal).

Here is a script which fixes this on Ubuntu Hardy, Ubuntu Karmic and Ubuntu Lucid:

perl -we '
  use integer;
  use strict;
  my $fn = $ARGV[0];
  print STDERR "info: vtefix library filename: $fn\n";
  die "error: file not found: $fn\n" if !open F, "<", $fn;
  $_ = join "", ;
  my @L;
  while (/\xf6[\x80-\x8f].[\x09-\x0a]\0\0[\x00\x04]
         |\x80[\x78-\x7f].\0\x74.\xf6\x85.\xff\xff\xff[\x00\x04]
         |\x80\x7d\x32\x00\x74\x11\x41\xf6\xc5[\x00\x04]
          (?=(?:\x0f\x1f\x80\0\0\0\0)?\x0f\x85)
         /sgx) {
    push @L, pos($_) - 1;
  }
  # We patch only the first occurrence, the 2nd one is different.
  pop @L if @L == 2 and vec($_, $L[0] - 6, 8) == 0xf6 and
                        vec($_, $L[1] - 6, 8) == 0xf6;
  if (@L == 1) {
    if (vec($_, $L[0], 8) == 4) {  # need to patch
      print "info: patching at offset $L[0]\n";
      die "error: cannot open file for writing: $fn\n" if !open F, "+<", $fn;
      die if !sysseek F, $L[0], 0;
      die if !syswrite F, "\0";
      print "info: patching OK, no need to restart gnome-terminal\n";
    } else {
      print "info: already patched at offset $L[0]\n";
      exit 2;
    }
  } else {
    die "error: instruction to patch not found (@L)\n";
  }
' -- "${1:-/usr/lib/libvte.so.9}"

Download the script from: http://pts-mini-gpl.googlecode.com/svn/trunk/pts-vtefix/vtefix.sh

The script has to be run each time after the libvte9 package is upgraded.

The script modifies the i386 and amd64 compiled libvte binary:

# amd64 Ubuntu Hardy:
# $ objdump -d /usr/lib/libvte.so.9.2.17 | grep testb | grep '[$]0x4,'
# 2a786:       f6 80 88 0a 00 00 04    testb  $0x4,0xa88(%rax)
# 2a7ba:       f6 87 88 0a 00 00 04    testb  $0x4,0xa88(%rdi)
# 
# i386 Ubuntu Hardy:
# $ objdump -d /usr/lib/libvte.so.9.2.17 | grep testb | grep '[$]0x4,'
# 25cd8:       f6 80 4c 09 00 00 04    testb  $0x0,0x94c(%eax)
# 25d0d:       f6 81 4c 09 00 00 04    testb  $0x4,0x94c(%ecx)

This corresponds to the following snipped in vte-0.16.3/src/vte.c:

if (handled == FALSE && normal == NULL && special == NULL) {
  if (event->group &&
      (terminal->pvt->modifiers & GDK_CONTROL_MASK))
    keyval = vte_translate_national_ctrlkeys(event);
  keychar = gdk_keyval_to_unicode(keyval);

The script replaces GDK_CONTROL_MASK (== 4) by 0, so the condition is always false, and vte_translate_national_ctrlkeys(...) is never called, which fixes the problem. Due to memory mapping, it is not necessary to restart GNOME Terminal instances already running.

To find the spot to patch in the binary, grep for gdk_keyval_to_unicode in the output of objdump -d /usr/lib/libvte.so.9.

More information about the bug (problem):

No comments: