From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1LCc83-0001mZ-2Q for qemu-devel@nongnu.org; Tue, 16 Dec 2008 10:46:35 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1LCc82-0001ly-6k for qemu-devel@nongnu.org; Tue, 16 Dec 2008 10:46:34 -0500 Received: from [199.232.76.173] (port=39388 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1LCc81-0001ll-NW for qemu-devel@nongnu.org; Tue, 16 Dec 2008 10:46:33 -0500 Received: from acsinet12.oracle.com ([141.146.126.234]:47969) by monty-python.gnu.org with esmtps (TLS-1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.60) (envelope-from ) id 1LCc81-0006rW-1L for qemu-devel@nongnu.org; Tue, 16 Dec 2008 10:46:33 -0500 Received: from acsinet13.oracle.com (acsinet13.oracle.com [141.146.126.235]) by acsinet12.oracle.com (Switch-3.3.1/Switch-3.3.1) with ESMTP id mBGFk7SH018446 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Tue, 16 Dec 2008 15:46:08 GMT Received: from acsmt704.oracle.com (acsmt704.oracle.com [141.146.40.82]) by acsinet13.oracle.com (Switch-3.3.1/Switch-3.3.1) with ESMTP id mBGFkrkB011609 for ; Tue, 16 Dec 2008 15:46:55 GMT Message-ID: <4947CD50.8040806@oracle.com> Date: Tue, 16 Dec 2008 15:46:24 +0000 From: John Haxby MIME-Version: 1.0 References: <4947CA2B.5030401@oracle.com> In-Reply-To: <4947CA2B.5030401@oracle.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit Subject: [Qemu-devel] [PATCH 1 of 3] Fix keymap handling for vnc console Reply-To: qemu-devel@nongnu.org List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Fix keymap handling for international keyboards Signed-off-by: John Haxby curses_keys.h | 2 keymaps.c | 347 ++++++++++++++++++++++++++++++++++++---------------------- sdl_keysym.h | 2 vnc.c | 87 ++++++++++---- vnc_keysym.h | 2 5 files changed, 286 insertions(+), 154 deletions(-) diff -up qemu-0.9.1.6042/curses_keys.h.keymap01 qemu-0.9.1.6042/curses_keys.h --- qemu-0.9.1.6042/curses_keys.h.keymap01 2008-10-28 00:11:06.000000000 +0000 +++ qemu-0.9.1.6042/curses_keys.h 2008-12-16 10:47:36.000000000 +0000 @@ -244,7 +244,7 @@ typedef struct { int keysym; } name2keysym_t; -static const name2keysym_t name2keysym[] = { +static name2keysym_t name2keysym[] = { /* Plain ASCII */ { "space", 0x020 }, { "exclam", 0x021 }, diff -up qemu-0.9.1.6042/keymaps.c.keymap01 qemu-0.9.1.6042/keymaps.c --- qemu-0.9.1.6042/keymaps.c.keymap01 2008-10-02 19:26:42.000000000 +0100 +++ qemu-0.9.1.6042/keymaps.c 2008-12-16 10:47:36.000000000 +0000 @@ -22,58 +22,64 @@ * THE SOFTWARE. */ +static int cmp_keysym(const void *a, const void *b) { + return strcmp(((name2keysym_t *) a)->name, ((name2keysym_t *) b)->name); +} + + static int get_keysym(const char *name) { - const name2keysym_t *p; - for(p = name2keysym; p->name != NULL; p++) { - if (!strcmp(p->name, name)) - return p->keysym; + static int n = -1; + int l, r, m; + + if (n < 0) { + for (n = 0; name2keysym[n].name; n++); + qsort(name2keysym, n, sizeof(name2keysym_t), cmp_keysym); + } + l = 0; + r = n-1; + while (l <= r) { + m = (l + r) / 2; + int cmp = strcmp(name2keysym[m].name, name); + if (cmp < 0) + l = m + 1; + else if (cmp > 0) + r = m - 1; + else + return name2keysym[m].keysym; + } + if (name[0] == 'U') { + /* unicode symbol key */ + char *ptr; + int k = strtol(name+1, &ptr, 16); + return *ptr ? 0 : k; } - return 0; } -struct key_range { - int start; - int end; - struct key_range *next; -}; +#define MAX_SCANCODE 256 +#define KEY_LOCALSTATE 0x1 +#define KEY_KEYPAD 0x2 #define MAX_NORMAL_KEYCODE 512 #define MAX_EXTRA_COUNT 256 typedef struct { - uint16_t keysym2keycode[MAX_NORMAL_KEYCODE]; - struct { - int keysym; - uint16_t keycode; - } keysym2keycode_extra[MAX_EXTRA_COUNT]; - int extra_count; - struct key_range *keypad_range; - struct key_range *numlock_range; -} kbd_layout_t; + int keysym; + int keycode; +} keysym2keycode_t; -static void add_to_key_range(struct key_range **krp, int code) { - struct key_range *kr; - for (kr = *krp; kr; kr = kr->next) { - if (code >= kr->start && code <= kr->end) - break; - if (code == kr->start - 1) { - kr->start--; - break; - } - if (code == kr->end + 1) { - kr->end++; - break; - } - } - if (kr == NULL) { - kr = qemu_mallocz(sizeof(*kr)); - if (kr) { - kr->start = kr->end = code; - kr->next = *krp; - *krp = kr; - } - } -} +typedef struct { + int n; + keysym2keycode_t k[MAX_SCANCODE]; +} keysym_map_t; + +typedef struct { + keysym_map_t plain; + keysym_map_t shift; + keysym_map_t altgr; + keysym_map_t shift_altgr; + keysym_map_t numlock; + uint32_t flags [MAX_SCANCODE]; +} kbd_layout_t; static kbd_layout_t *parse_keyboard_layout(const char *language, kbd_layout_t * k) @@ -81,105 +87,194 @@ static kbd_layout_t *parse_keyboard_layo FILE *f; char file_name[1024]; char line[1024]; - int len; snprintf(file_name, sizeof(file_name), - "%s/keymaps/%s", bios_dir, language); + "%s/keymaps/%s", bios_dir, language); if (!k) k = qemu_mallocz(sizeof(kbd_layout_t)); if (!k) - return 0; + return 0; if (!(f = fopen(file_name, "r"))) { fprintf(stderr, "Could not read keymap file: '%s'\n", file_name); return 0; } - for(;;) { - if (fgets(line, 1024, f) == NULL) - break; - len = strlen(line); - if (len > 0 && line[len - 1] == '\n') - line[len - 1] = '\0'; - if (line[0] == '#') - continue; - if (!strncmp(line, "map ", 4)) - continue; - if (!strncmp(line, "include ", 8)) { - parse_keyboard_layout(line + 8, k); - } else { - char *end_of_keysym = line; - while (*end_of_keysym != 0 && *end_of_keysym != ' ') - end_of_keysym++; - if (*end_of_keysym) { - int keysym; - *end_of_keysym = 0; - keysym = get_keysym(line); - if (keysym == 0) { - // fprintf(stderr, "Warning: unknown keysym %s\n", line); - } else { - const char *rest = end_of_keysym + 1; - char *rest2; - int keycode = strtol(rest, &rest2, 0); - - if (rest && strstr(rest, "numlock")) { - add_to_key_range(&k->keypad_range, keycode); - add_to_key_range(&k->numlock_range, keysym); - //fprintf(stderr, "keypad keysym %04x keycode %d\n", keysym, keycode); - } - - /* if(keycode&0x80) - keycode=(keycode<<8)^0x80e0; */ - if (keysym < MAX_NORMAL_KEYCODE) { - //fprintf(stderr,"Setting keysym %s (%d) to %d\n",line,keysym,keycode); - k->keysym2keycode[keysym] = keycode; - } else { - if (k->extra_count >= MAX_EXTRA_COUNT) { - fprintf(stderr, - "Warning: Could not assign keysym %s (0x%x) because of memory constraints.\n", - line, keysym); - } else { -#if 0 - fprintf(stderr, "Setting %d: %d,%d\n", - k->extra_count, keysym, keycode); -#endif - k->keysym2keycode_extra[k->extra_count]. - keysym = keysym; - k->keysym2keycode_extra[k->extra_count]. - keycode = keycode; - k->extra_count++; - } - } - } - } + while (fgets(line, 1024, f)) { + char *ptr = strchr(line, '#'); + char keyname[1024], p1[1024], p2[1024]; + int keysym, keycode; + int shift = 0; + int altgr = 0; + int addupper = 0; + int numlock = 0; + int inhibit = 0; + + if (ptr) + *ptr-- = '\0'; + else + ptr = &line[strlen(line)-1]; + while (isspace(*ptr)) + *ptr-- = '\0'; + if (!*line) + continue; + if (strncmp(line, "map ", 4) == 0) + continue; + if (sscanf(line, "include %s", p1) == 1) { + parse_keyboard_layout(p1, k); + continue; + } + if (sscanf(line, "%s %i %s %s", keyname, &keycode, p1, p2) == 4) { + shift = (strcmp(p1, "shift") == 0 || strcmp(p2, "shift") == 0); + altgr = (strcmp(p1, "altgr") == 0 || strcmp(p2, "altgr") == 0); + } else if (sscanf(line, "%s %i %s", keyname, &keycode, p1) == 3) { + shift = (strcmp(p1, "shift") == 0); + altgr = (strcmp(p1, "altgr") == 0); + addupper = (strcmp(p1, "addupper") == 0); + numlock = (strcmp(p1, "numlock") == 0); + inhibit = (strcmp(p1, "inhibit") == 0); + } else if (sscanf(line, "%s %i", keyname, &keycode) != 2) + /* silently ignore spurious lines */ + continue; + + if (inhibit) + continue; + if ((keysym = get_keysym(keyname)) == 0) { + fprintf(stderr, "%s: warning: unknown keysym %s\n", + file_name, keyname); + continue; + } + if (keycode <= 0 || keycode >= MAX_SCANCODE) { + fprintf(stderr, "%s: warning: keycode %#x for %s out of range\n", + file_name, keycode, keyname); + continue; + } + if (numlock) + k->numlock.k[keycode].keysym = keysym; + else if (shift && altgr) + k->shift_altgr.k[keycode].keysym = keysym; + else if (altgr) + k->altgr.k[keycode].keysym = keysym; + else if (shift) + k->shift.k[keycode].keysym = keysym; + else { + k->plain.k[keycode].keysym = keysym; + if (addupper) + k->shift.k[keycode].keysym = keysym + 'A' - 'a'; } } fclose(f); return k; } +static int cmp_map (const void *a, const void *b) { + return ((keysym2keycode_t *) b)->keysym - ((keysym2keycode_t *) a)->keysym; +} + +static void sort_map (keysym_map_t *map) { + int i; + for (i = 0; i < MAX_SCANCODE; i++) + map->k[i].keycode = i; + /* sort undefined scancodes to the end */ + qsort(map->k, MAX_SCANCODE, sizeof(keysym2keycode_t), cmp_map); + for (map->n = 0; map->n < MAX_SCANCODE; map->n++) + if (!map->k[map->n].keysym) + break; +} + static void *init_keyboard_layout(const char *language) { - return parse_keyboard_layout(language, 0); + kbd_layout_t *k = parse_keyboard_layout(language, NULL); + if (k) { + int i; + for (i = 0; i < MAX_SCANCODE; i++) { + if (!(k->shift.k[i].keysym + || k->altgr.k[i].keysym + || k->shift_altgr.k[i].keysym)) + k->flags[i] |= KEY_LOCALSTATE; + if (k->numlock.k[i].keysym) + k->flags[i] |= KEY_KEYPAD; + } + sort_map(&k->plain); + sort_map(&k->shift); + sort_map(&k->altgr); + sort_map(&k->shift_altgr); + sort_map(&k->numlock); + } + return k; +} + +static int keysym2scancode_map (keysym_map_t *map, int keysym) +{ + int l = 0, r = map->n - 1, m; + while (l <= r) { + m = (l + r) / 2; + if (map->k[m].keysym == keysym) + return map->k[m].keycode; + else if (map->k[m].keysym < keysym) + r = m - 1; + else + l = m + 1; + } + return 0; } static int keysym2scancode(void *kbd_layout, int keysym) { kbd_layout_t *k = kbd_layout; - if (keysym < MAX_NORMAL_KEYCODE) { - if (k->keysym2keycode[keysym] == 0) - fprintf(stderr, "Warning: no scancode found for keysym %d\n", - keysym); - return k->keysym2keycode[keysym]; - } else { - int i; -#ifdef XK_ISO_Left_Tab - if (keysym == XK_ISO_Left_Tab) - keysym = XK_Tab; -#endif - for (i = 0; i < k->extra_count; i++) - if (k->keysym2keycode_extra[i].keysym == keysym) - return k->keysym2keycode_extra[i].keycode; + int scancode; + + if ((scancode = keysym2scancode_map(&k->plain, keysym))) + return scancode; + if ((scancode = keysym2scancode_map(&k->numlock, keysym))) + return scancode; + if ((scancode = keysym2scancode_map(&k->shift, keysym))) + return scancode; + if ((scancode = keysym2scancode_map(&k->altgr, keysym))) + return scancode; + if ((scancode = keysym2scancode_map(&k->shift_altgr, keysym))) + return scancode; + return 0; +} + +static inline int keysym2scancode1(void *kbd_layout, int keysym, + int *shift, int *altgr) +{ + kbd_layout_t *k = kbd_layout; + int s; + + /* normal case: keysym can be found the expected modifier's map */ + if (*shift && *altgr && (s = keysym2scancode_map(&k->shift_altgr, keysym))) + return s; + if (*altgr && (s = keysym2scancode_map(&k->altgr, keysym))) + return s; + if (*shift && (s = keysym2scancode_map(&k->shift, keysym))) + return s; + if ((s = keysym2scancode_map(&k->plain, keysym))) + return s; + if ((s = keysym2scancode_map(&k->numlock, keysym))) + return s; + + /* fallback for when there is some keyboard/state mismatch */ + if ((s = keysym2scancode_map(&k->plain, keysym))) { + *shift = 0; + *altgr = 0; + return s; + } + if ((s = keysym2scancode_map(&k->shift, keysym))) { + *shift = 1; + *altgr = 0; + return s; + } + if ((s = keysym2scancode_map(&k->altgr, keysym))) { + *shift = 0; + *altgr = 1; + return s; + } + if ((s = keysym2scancode_map(&k->shift_altgr, keysym))) { + *shift = 1; + *altgr = 1; + return s; } return 0; } @@ -187,21 +282,21 @@ static int keysym2scancode(void *kbd_lay static inline int keycode_is_keypad(void *kbd_layout, int keycode) { kbd_layout_t *k = kbd_layout; - struct key_range *kr; - for (kr = k->keypad_range; kr; kr = kr->next) - if (keycode >= kr->start && keycode <= kr->end) - return 1; - return 0; + if (keycode >= 0 && keycode < MAX_SCANCODE) + return !!(k->flags[keycode] & KEY_KEYPAD); } static inline int keysym_is_numlock(void *kbd_layout, int keysym) { kbd_layout_t *k = kbd_layout; - struct key_range *kr; + return (keysym2scancode_map(&k->numlock, keysym) != 0); +} - for (kr = k->numlock_range; kr; kr = kr->next) - if (keysym >= kr->start && keysym <= kr->end) - return 1; +static inline int keycode_is_shiftable(void *kbd_layout, int keycode) +{ + kbd_layout_t *k = kbd_layout; + if (keycode >= 0 || keycode < MAX_SCANCODE) + return !(k->flags[keycode] & KEY_LOCALSTATE); return 0; } diff -up qemu-0.9.1.6042/sdl_keysym.h.keymap01 qemu-0.9.1.6042/sdl_keysym.h --- qemu-0.9.1.6042/sdl_keysym.h.keymap01 2008-10-02 19:26:42.000000000 +0100 +++ qemu-0.9.1.6042/sdl_keysym.h 2008-12-16 10:47:36.000000000 +0000 @@ -2,7 +2,7 @@ typedef struct { const char* name; int keysym; } name2keysym_t; -static const name2keysym_t name2keysym[]={ +static name2keysym_t name2keysym[]={ /* ascii */ { "space", 0x020}, { "exclam", 0x021}, diff -up qemu-0.9.1.6042/vnc.c.keymap01 qemu-0.9.1.6042/vnc.c --- qemu-0.9.1.6042/vnc.c.keymap01 2008-12-13 18:57:12.000000000 +0000 +++ qemu-0.9.1.6042/vnc.c 2008-12-16 10:52:09.000000000 +0000 @@ -1031,14 +1031,22 @@ static void pointer_event(VncState *vs, check_pointer_type_change(vs, kbd_mouse_is_absolute()); } +static void put_keycode(int keycode, int down) +{ + if (keycode & 0x80) + kbd_put_keycode(0xe0); + if (down) + kbd_put_keycode(keycode & 0x7f); + else + kbd_put_keycode(keycode | 0x80); +} + static void reset_keys(VncState *vs) { int i; for(i = 0; i < 256; i++) { if (vs->modifiers_state[i]) { - if (i & 0x80) - kbd_put_keycode(0xe0); - kbd_put_keycode(i | 0x80); + put_keycode(i, 0); vs->modifiers_state[i] = 0; } } @@ -1046,12 +1054,29 @@ static void reset_keys(VncState *vs) static void press_key(VncState *vs, int keysym) { - kbd_put_keycode(keysym2scancode(vs->kbd_layout, keysym) & 0x7f); - kbd_put_keycode(keysym2scancode(vs->kbd_layout, keysym) | 0x80); + put_keycode(keysym2scancode(vs->kbd_layout, keysym), 1); + put_keycode(keysym2scancode(vs->kbd_layout, keysym), 0); +} + +static void twiddle_modifiers(VncState *vs, int down, int shift, int altgr) +{ + if (shift && !(vs->modifiers_state[0x2a] || vs->modifiers_state[0x36])) + put_keycode(0x2a, down); + if (!shift && vs->modifiers_state[0x2a]) + put_keycode(0x2a, !down); + if (!shift && vs->modifiers_state[0x36]) + put_keycode(0x36, !down); + if (altgr && !vs->modifiers_state[0xb8]) + put_keycode(0xb8, down); + if (!altgr && vs->modifiers_state[0xb8]) + put_keycode(0xb8, !down); } -static void do_key_event(VncState *vs, int down, int keycode, int sym) +static void do_key_event(VncState *vs, int down, int keycode, int sym, + int shift, int altgr) { + int keypad = 0; + /* QEMU console switch */ switch(keycode) { case 0x2a: /* Left Shift */ @@ -1059,11 +1084,8 @@ static void do_key_event(VncState *vs, i case 0x1d: /* Left CTRL */ case 0x9d: /* Right CTRL */ case 0x38: /* Left ALT */ - case 0xb8: /* Right ALT */ - if (down) - vs->modifiers_state[keycode] = 1; - else - vs->modifiers_state[keycode] = 0; + case 0xb8: /* Right ALT aka AltGr*/ + vs->modifiers_state[keycode] = down; break; case 0x02 ... 0x0a: /* '1' to '9' keys */ if (down && vs->modifiers_state[0x1d] && vs->modifiers_state[0x38]) { @@ -1074,16 +1096,22 @@ static void do_key_event(VncState *vs, i } break; case 0x3a: /* CapsLock */ + if (is_graphic_console()) + return; case 0x45: /* NumLock */ if (!down) vs->modifiers_state[keycode] ^= 1; break; } - if (keycode_is_keypad(vs->kbd_layout, keycode)) { + keypad = keycode_is_keypad(vs->kbd_layout, keycode); + if (keypad) { /* If the numlock state needs to change then simulate an additional keypress before sending this one. This will happen if the user - toggles numlock away from the VNC window. + toggles numlock away from the VNC window. This isn't perfect as + pressig the shift key will typically and temporarily have the + effect of inverting the numlock setting: the shift key will be + effectively cancelled out. */ if (keysym_is_numlock(vs->kbd_layout, sym & 0xFFFF)) { if (!vs->modifiers_state[0x45]) { @@ -1099,12 +1127,14 @@ static void do_key_event(VncState *vs, i } if (is_graphic_console()) { - if (keycode & 0x80) - kbd_put_keycode(0xe0); - if (down) - kbd_put_keycode(keycode & 0x7f); - else - kbd_put_keycode(keycode | 0x80); + if (keycode_is_shiftable(vs->kbd_layout, keycode) && !keypad) { + if (down) + twiddle_modifiers(vs, down, shift, altgr); + put_keycode(keycode, down); + if (!down) + twiddle_modifiers(vs, down, shift, altgr); + } else + put_keycode(keycode, down); } else { /* QEMU console emulation */ if (down) { @@ -1153,13 +1183,18 @@ static void do_key_event(VncState *vs, i static void key_event(VncState *vs, int down, uint32_t sym) { + int shift; + int altgr; int keycode; - if (sym >= 'A' && sym <= 'Z' && is_graphic_console()) - sym = sym - 'A' + 'a'; - - keycode = keysym2scancode(vs->kbd_layout, sym & 0xFFFF); - do_key_event(vs, down, keycode, sym); + shift = (vs->modifiers_state[0x2a] || vs->modifiers_state[0x36]); + altgr = vs->modifiers_state[0xb8]; + keycode = keysym2scancode1(vs->kbd_layout, sym & 0xFFFF, &shift, &altgr); + if (keycode == 0) { + fprintf(stderr, "Key lost : keysym 0x%x(%d)\n", sym, sym); + return; + } + do_key_event(vs, down, keycode, sym, shift, altgr); } static void ext_key_event(VncState *vs, int down, @@ -1169,7 +1204,9 @@ static void ext_key_event(VncState *vs, if (keyboard_layout) key_event(vs, down, sym); else - do_key_event(vs, down, keycode, sym); + do_key_event(vs, down, keycode, sym, + vs->modifiers_state[0x2a] || vs->modifiers_state[0x36], + vs->modifiers_state[0xb8]); } static void framebuffer_update_request(VncState *vs, int incremental, diff -up qemu-0.9.1.6042/vnc_keysym.h.keymap01 qemu-0.9.1.6042/vnc_keysym.h --- qemu-0.9.1.6042/vnc_keysym.h.keymap01 2008-10-02 19:26:42.000000000 +0100 +++ qemu-0.9.1.6042/vnc_keysym.h 2008-12-16 10:47:36.000000000 +0000 @@ -2,7 +2,7 @@ typedef struct { const char* name; int keysym; } name2keysym_t; -static const name2keysym_t name2keysym[]={ +static name2keysym_t name2keysym[]={ /* ascii */ { "space", 0x020}, { "exclam", 0x021},