[gtk-vnc-devel] [PATCH][RFC] VNC extension for passing PC keycodes (v2)



Hi,

The attached two patches implement a VNC extension for passing PC keycodes directly in the VNC session. This should reduce the need for users of non-US keyboards to specify the '-k' option to QEMU. The extension uses my extended client message (so that we can multiplex multiple message values over the '255' client message type). It also uses a psuedo encoding to negotiate the existence of the new client message.

The format of the client message is:

u8 msg_id    255
u8 xmsg_id    0
u16 down_flag   (1 if press; 0 if release)
u32 keyval   (standard VNC key value)
u32 keycode   (PC keycode)

I decided to include both values because it's easy for the client (it should have both) and it gives the server more information which is always a good thing. I'm happy with the state of the patches so I'll commit the gtk-vnc patch if there aren't any objections.

Regards,

Anthony Liguori
This patch implements support for the ExtendKeyEvent client message.  This
message allows scan codes to be sent directly to the server.  This is
particularly useful for virtualization as servers normally have to translate
from VNC key symbols to PC scan codes.  In the event that the host and guest
are using the same keymap, it's unnecessary to explicitly specify the keymap
when creating the guest.

diff -r 492cc3ad13a7 src/Makefile.am
--- a/src/Makefile.am	Fri Jan 11 17:38:00 2008 -0600
+++ b/src/Makefile.am	Sun Jan 13 13:44:32 2008 -0600
@@ -12,7 +12,7 @@ libgtk_vnc_1_0_la_LDFLAGS = -Wl,--versio
                             -version-info 0:1:0
 
 gtk_vnc_includedir = $(includedir)/gtk-vnc-1.0/
-gtk_vnc_include_HEADERS = vncdisplay.h gvnc.h
+gtk_vnc_include_HEADERS = vncdisplay.h
 
 libgtk_vnc_1_0_la_SOURCES = blt.h blt1.h \
 	coroutine.h \
@@ -20,6 +20,7 @@ libgtk_vnc_1_0_la_SOURCES = blt.h blt1.h
 	gvnc.h gvnc.c \
 	vncdisplay.h vncdisplay.c \
         vncmarshal.h vncmarshal.c \
+	x_keymap.h x_keymap.c \
 	utils.h
 
 if WITH_UCONTEXT
diff -r 492cc3ad13a7 src/gvnc.c
--- a/src/gvnc.c	Fri Jan 11 17:38:00 2008 -0600
+++ b/src/gvnc.c	Sun Jan 13 13:44:32 2008 -0600
@@ -31,6 +31,8 @@
 #include "coroutine.h"
 #include "d3des.h"
 
+#include "x_keymap.h"
+
 #include "utils.h"
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
@@ -158,6 +160,8 @@ struct gvnc
 
 	uint8_t zrle_pi;
 	int zrle_pi_bits;
+
+	gboolean has_ext_key_event;
 };
 
 #define nibhi(a) (((a) >> 4) & 0x0F)
@@ -815,6 +819,7 @@ gboolean gvnc_set_encodings(struct gvnc 
 	uint8_t pad[1] = {0};
 	int i;
 
+	gvnc->has_ext_key_event = FALSE;
 	gvnc_write_u8(gvnc, 2);
 	gvnc_write(gvnc, pad, 1);
 	gvnc_write_u16(gvnc, n_encoding);
@@ -879,14 +884,33 @@ static void gvnc_buffered_flush(struct g
 	g_io_wakeup(&gvnc->wait);
 }
 
-gboolean gvnc_key_event(struct gvnc *gvnc, uint8_t down_flag, uint32_t key)
+gboolean gvnc_key_event(struct gvnc *gvnc, uint8_t down_flag,
+			uint32_t key, uint16_t scancode)
 {
 	uint8_t pad[2] = {0};
 
-	gvnc_buffered_write_u8(gvnc, 4);
-	gvnc_buffered_write_u8(gvnc, down_flag);
-	gvnc_buffered_write(gvnc, pad, 2);
-	gvnc_buffered_write_u32(gvnc, key);
+	if (gvnc->has_ext_key_event) {
+		scancode = x_keycode_to_pc_keycode(scancode);
+
+		gvnc_buffered_write_u8(gvnc, 255);
+		gvnc_buffered_write_u8(gvnc, 0);
+		gvnc_buffered_write_u16(gvnc, down_flag);
+		gvnc_buffered_write_u32(gvnc, key);
+		gvnc_buffered_write_u32(gvnc, scancode);
+	} else {
+		gvnc_buffered_write_u8(gvnc, 4);
+		gvnc_buffered_write_u8(gvnc, down_flag);
+		gvnc_buffered_write(gvnc, pad, 2);
+		gvnc_buffered_write_u32(gvnc, key);
+	}
+
+	gvnc_buffered_flush(gvnc);
+	return !gvnc_has_error(gvnc);
+}
+
+gboolean gvnc_key_event_scancode(struct gvnc *gvnc, uint8_t down_flag,
+				 uint16_t scancode)
+{
 	gvnc_buffered_flush(gvnc);
 	return !gvnc_has_error(gvnc);
 }
@@ -1852,6 +1876,9 @@ static void gvnc_framebuffer_update(stru
 	case GVNC_ENCODING_XCURSOR:
 		gvnc_xcursor(gvnc, x, y, width, height);
 		break;
+	case GVNC_ENCODING_EXT_KEY_EVENT:
+		gvnc->has_ext_key_event = TRUE;
+		break;
 	default:
 		gvnc->has_error = TRUE;
 		break;
diff -r 492cc3ad13a7 src/gvnc.h
--- a/src/gvnc.h	Fri Jan 11 17:38:00 2008 -0600
+++ b/src/gvnc.h	Sun Jan 13 13:44:32 2008 -0600
@@ -89,6 +89,7 @@ typedef enum {
 	GVNC_ENCODING_XCURSOR = -240,
 
 	GVNC_ENCODING_POINTER_CHANGE = -257,
+	GVNC_ENCODING_EXT_KEY_EVENT = -258,
 } gvnc_encoding;
 
 typedef enum {
@@ -149,7 +150,8 @@ gboolean gvnc_pointer_event(struct gvnc 
 gboolean gvnc_pointer_event(struct gvnc *gvnc, uint8_t button_mask,
 			    uint16_t x, uint16_t y);
 
-gboolean gvnc_key_event(struct gvnc *gvnc, uint8_t down_flag, uint32_t key);
+gboolean gvnc_key_event(struct gvnc *gvnc, uint8_t down_flag,
+			uint32_t key, uint16_t scancode);
 
 gboolean gvnc_framebuffer_update_request(struct gvnc *gvnc,
 					 uint8_t incremental,
diff -r 492cc3ad13a7 src/vncdisplay.c
--- a/src/vncdisplay.c	Fri Jan 11 17:38:00 2008 -0600
+++ b/src/vncdisplay.c	Sun Jan 13 13:44:32 2008 -0600
@@ -47,6 +47,7 @@ struct _VncDisplayPrivate
 	gboolean in_keyboard_grab;
 
 	guint down_keyval[16];
+	guint down_scancode[16];
 
 	int button_mask;
 	int last_x;
@@ -418,8 +419,9 @@ static gboolean key_event(GtkWidget *wid
 		for (i = 0 ; i < (int)(sizeof(priv->down_keyval)/sizeof(priv->down_keyval[0])) ; i++) {
 			if (priv->down_keyval[i] == 0) {
 				priv->down_keyval[i] = keyval;
+				priv->down_scancode[i] = key->hardware_keycode;
 				/* Send the actual key event we're dealing with */
-				gvnc_key_event(priv->gvnc, 1, keyval);
+				gvnc_key_event(priv->gvnc, 1, keyval, key->hardware_keycode);
 				break;
 			} else if (priv->down_keyval[i] == keyval) {
 				/* Got an press when we're already pressed ! Why ... ?
@@ -431,9 +433,9 @@ static gboolean key_event(GtkWidget *wid
 				 * them into a sensible stream of press+release pairs
 				 */
 				/* Fake an up event for the previous down event */
-				gvnc_key_event(priv->gvnc, 0, keyval);
+				gvnc_key_event(priv->gvnc, 0, keyval, key->hardware_keycode);
 				/* Now send our actual ldown event */
-				gvnc_key_event(priv->gvnc, 1, keyval);
+				gvnc_key_event(priv->gvnc, 1, keyval, key->hardware_keycode);
 			}
 		}
 	} else {
@@ -442,8 +444,9 @@ static gboolean key_event(GtkWidget *wid
 			/* We were pressed, and now we're released, so... */
 			if (priv->down_keyval[i] == keyval) {
 				priv->down_keyval[i] = 0;
+				priv->down_scancode[i] = 0;
 				/* ..send the key releae event we're dealing with */
-				gvnc_key_event(priv->gvnc, 0, keyval);
+				gvnc_key_event(priv->gvnc, 0, keyval, key->hardware_keycode);
 				break;
 			}
 		}
@@ -509,8 +512,10 @@ static gboolean focus_event(GtkWidget *w
 		/* We are currently pressed so... */
 		if (priv->down_keyval[i] != 0) {
 			/* ..send the fake key releae event to match */
-			gvnc_key_event(priv->gvnc, 0, priv->down_keyval[i]);
+			gvnc_key_event(priv->gvnc, 0,
+				       priv->down_keyval[i], priv->down_scancode[i]);
 			priv->down_keyval[i] = 0;
+			priv->down_scancode[i] = 0;
 		}
 	}
 
@@ -803,6 +808,7 @@ static void *vnc_coroutine(void *opaque)
 	/* this order is extremely important! */
 	int32_t encodings[] = {	GVNC_ENCODING_TIGHT_JPEG5,
 				GVNC_ENCODING_TIGHT,
+				GVNC_ENCODING_EXT_KEY_EVENT,
 				GVNC_ENCODING_DESKTOP_RESIZE,
 				GVNC_ENCODING_RICH_CURSOR,
 				GVNC_ENCODING_XCURSOR,
@@ -971,21 +977,40 @@ void vnc_display_send_keys(VncDisplay *o
 				 nkeyvals, VNC_DISPLAY_KEY_EVENT_CLICK);
 }
 
+static guint get_keycode_from_keyval(guint keyval)
+{
+	guint keycode = 0;
+	GdkKeymapKey *keys = NULL;
+	gint n_keys = 0;
+
+	if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
+					      keyval, &keys, &n_keys)) {
+		/* FIXME what about levels? */
+		keycode = keys[0].keycode;
+		g_free(keys);
+	}
+
+	return keycode;
+}
+
 void vnc_display_send_keys_ex(VncDisplay *obj, const guint *keyvals,
 			      int nkeyvals, VncDisplayKeyEvent kind)
 {
 	int i;
+
 	if (obj->priv->gvnc == NULL || !gvnc_is_open(obj->priv->gvnc))
 		return;
 
 	if (kind & VNC_DISPLAY_KEY_EVENT_PRESS) {
 		for (i = 0 ; i < nkeyvals ; i++)
-			gvnc_key_event(obj->priv->gvnc, 1, keyvals[i]);
+			gvnc_key_event(obj->priv->gvnc, 1, keyvals[i],
+				       get_keycode_from_keyval(keyvals[i]));
 	}
 
 	if (kind & VNC_DISPLAY_KEY_EVENT_RELEASE) {
 		for (i = (nkeyvals-1) ; i >= 0 ; i--)
-			gvnc_key_event(obj->priv->gvnc, 0, keyvals[i]);
+			gvnc_key_event(obj->priv->gvnc, 0, keyvals[i],
+				       get_keycode_from_keyval(keyvals[i]));
 	}
 }
 
diff -r 492cc3ad13a7 src/x_keymap.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/x_keymap.c	Sun Jan 13 13:44:32 2008 -0600
@@ -0,0 +1,133 @@
+/*
+ * QEMU SDL display driver
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/*
+ * Adapted for gtk-vnc from QEMU x_keymap.c revision 1.3 (on 20080113)
+ *
+ * Copyright (C) 2008  Anthony Liguori <anthony codemonkey ws>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "x_keymap.h"
+
+static const uint8_t x_keycode_to_pc_keycode_table[115] = {
+   0xc7,      /*  97  Home   */
+   0xc8,      /*  98  Up     */
+   0xc9,      /*  99  PgUp   */
+   0xcb,      /* 100  Left   */
+   0x4c,        /* 101  KP-5   */
+   0xcd,      /* 102  Right  */
+   0xcf,      /* 103  End    */
+   0xd0,      /* 104  Down   */
+   0xd1,      /* 105  PgDn   */
+   0xd2,      /* 106  Ins    */
+   0xd3,      /* 107  Del    */
+   0x9c,      /* 108  Enter  */
+   0x9d,      /* 109  Ctrl-R */
+   0x0,       /* 110  Pause  */
+   0xb7,      /* 111  Print  */
+   0xb5,      /* 112  Divide */
+   0xb8,      /* 113  Alt-R  */
+   0xc6,      /* 114  Break  */
+   0x0,         /* 115 */
+   0x0,         /* 116 */
+   0x0,         /* 117 */
+   0x0,         /* 118 */
+   0x0,         /* 119 */
+   0x0,         /* 120 */
+   0x0,         /* 121 */
+   0x0,         /* 122 */
+   0x0,         /* 123 */
+   0x0,         /* 124 */
+   0x0,         /* 125 */
+   0x0,         /* 126 */
+   0x0,         /* 127 */
+   0x0,         /* 128 */
+   0x79,         /* 129 Henkan */
+   0x0,         /* 130 */
+   0x7b,         /* 131 Muhenkan */
+   0x0,         /* 132 */
+   0x7d,         /* 133 Yen */
+   0x0,         /* 134 */
+   0x0,         /* 135 */
+   0x47,         /* 136 KP_7 */
+   0x48,         /* 137 KP_8 */
+   0x49,         /* 138 KP_9 */
+   0x4b,         /* 139 KP_4 */
+   0x4c,         /* 140 KP_5 */
+   0x4d,         /* 141 KP_6 */
+   0x4f,         /* 142 KP_1 */
+   0x50,         /* 143 KP_2 */
+   0x51,         /* 144 KP_3 */
+   0x52,         /* 145 KP_0 */
+   0x53,         /* 146 KP_. */
+   0x47,         /* 147 KP_HOME */
+   0x48,         /* 148 KP_UP */
+   0x49,         /* 149 KP_PgUp */
+   0x4b,         /* 150 KP_Left */
+   0x4c,         /* 151 KP_ */
+   0x4d,         /* 152 KP_Right */
+   0x4f,         /* 153 KP_End */
+   0x50,         /* 154 KP_Down */
+   0x51,         /* 155 KP_PgDn */
+   0x52,         /* 156 KP_Ins */
+   0x53,         /* 157 KP_Del */
+   0x0,         /* 158 */
+   0x0,         /* 159 */
+   0x0,         /* 160 */
+   0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,         /* 170 */
+   0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,         /* 180 */
+   0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,         /* 190 */
+   0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,         /* 200 */
+   0x0,         /* 201 */
+   0x0,         /* 202 */
+   0x0,         /* 203 */
+   0x0,         /* 204 */
+   0x0,         /* 205 */
+   0x0,         /* 206 */
+   0x0,         /* 207 */
+   0x70,         /* 208 Hiragana_Katakana */
+   0x0,         /* 209 */
+   0x0,         /* 210 */
+   0x73,         /* 211 backslash */
+};
+
+/* FIXME N.B. on Windows, gtk probably returns PC scan codes */
+
+uint8_t x_keycode_to_pc_keycode(int keycode)
+{
+	if (keycode < 9)
+		keycode = 0;
+	else if (keycode < 97)
+		keycode -= 8; /* just an offset */
+	else if (keycode < 212)
+		keycode = x_keycode_to_pc_keycode_table[keycode - 97];
+	else
+		keycode = 0;
+
+	return keycode;
+}
diff -r 492cc3ad13a7 src/x_keymap.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/x_keymap.h	Sun Jan 13 13:44:32 2008 -0600
@@ -0,0 +1,8 @@
+#ifndef _GTK_VNC_X_KEYMAP_H
+#define _GTK_VNC_X_KEYMAP_H
+
+#include <stdint.h>
+
+uint8_t x_keycode_to_pc_keycode(int keycode);
+
+#endif
This patch adds support for the ExtendedKeyEvent client message.  This message
allows a client to send raw scan codes directly to the server.  If the client
and server are using the same keymap, then it's unnecessary to use the '-k'
option with QEMU when this extension is supported.

Index: qemu/vnc.c
===================================================================
--- qemu.orig/vnc.c	2008-01-12 18:36:49.000000000 -0600
+++ qemu/vnc.c	2008-01-13 13:43:07.000000000 -0600
@@ -927,12 +927,8 @@
     kbd_put_keycode(keysym2scancode(vs->kbd_layout, keysym) | 0x80);
 }
 
-static void do_key_event(VncState *vs, int down, uint32_t sym)
+static void do_key_event(VncState *vs, int down, int keycode, int sym)
 {
-    int keycode;
-
-    keycode = keysym2scancode(vs->kbd_layout, sym & 0xFFFF);
-
     /* QEMU console switch */
     switch(keycode) {
     case 0x2a:                          /* Left Shift */
@@ -1033,9 +1029,24 @@
 
 static void key_event(VncState *vs, int down, uint32_t sym)
 {
+    int keycode;
+
     if (sym >= 'A' && sym <= 'Z' && is_graphic_console())
 	sym = sym - 'A' + 'a';
-    do_key_event(vs, down, sym);
+
+    keycode = keysym2scancode(vs->kbd_layout, sym & 0xFFFF);
+    do_key_event(vs, down, keycode, sym);
+}
+
+static void ext_key_event(VncState *vs, int down,
+			  uint32_t sym, uint16_t keycode)
+{
+    /* if the user specifies a keyboard layout, always use it */
+    if (keyboard_layout)
+	key_event(vs, down, sym);
+    else
+	do_key_event(vs, down, keycode, sym);
+
 }
 
 static void framebuffer_update_request(VncState *vs, int incremental,
@@ -1065,6 +1076,15 @@
     }
 }
 
+static void send_ext_key_event_ack(VncState *vs)
+{
+    vnc_write_u8(vs, 0);
+    vnc_write_u8(vs, 0);
+    vnc_write_u16(vs, 1);
+    vnc_framebuffer_update(vs, 0, 0, vs->ds->width, vs->ds->height, -258);
+    vnc_flush(vs);
+}
+
 static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
 {
     int i;
@@ -1092,6 +1112,9 @@
 	case -257:
 	    vs->has_pointer_type_change = 1;
 	    break;
+	case -258:
+	    send_ext_key_event_ack(vs);
+	    break;
 	default:
 	    break;
 	}
@@ -1245,6 +1268,24 @@
 
 	client_cut_text(vs, read_u32(data, 4), data + 8);
 	break;
+    case 255:
+	if (len == 1)
+	    return 2;
+
+	switch (read_u8(data, 1)) {
+	case 0:
+	    if (len == 2)
+		return 12;
+
+	    ext_key_event(vs, read_u16(data, 2),
+			  read_u32(data, 4), read_u32(data, 8));
+	    break;
+	default:
+	    printf("Msg: %d\n", read_u16(data, 0));
+	    vnc_client_error(vs);
+	    break;
+	}
+	break;
     default:
 	printf("Msg: %d\n", data[0]);
 	vnc_client_error(vs);
@@ -1952,10 +1993,11 @@
 
     vs->ds = ds;
 
-    if (!keyboard_layout)
-	keyboard_layout = "en-us";
+    if (keyboard_layout)
+	vs->kbd_layout = init_keyboard_layout(keyboard_layout);
+    else
+	vs->kbd_layout = init_keyboard_layout("en-us");
 
-    vs->kbd_layout = init_keyboard_layout(keyboard_layout);
     if (!vs->kbd_layout)
 	exit(1);
 


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