[gtk-vnc-devel] PATCH: key state tracking to avoid various bugs



There are various problems with the VNC protocol and GTK relating to key
state tracking. The VNC protocol has no concept of modifier keys, nor a
way to reset key state. So if a client looses focus while one or more
keys are pressed down, the server will never see any 'release' event for
them. This is particularly troublesome with modifiers because it will 
appear that Ctlr or Alt is stuck down and you'll get really wierd behaviour
with keyboard shortcuts in your guest activating.

Second, GTK has the 'feature' where by it compresses  sequential press+release
events for the same key. So if you hold down the 'a' key long enough to get
keyboard repeat running, instead of down+up, down+up, down+up you'll merely
get down,down,down,...,down+up.  This seriously annoys some VNC servers,
in particular the Xen paravirt framebuffer will ignore the repeated down
events.  

So this patch introduces  keystate tracking. This is then used in two cases
to send extra fake events. If we get two sequential down events for the same
key we will send an extra fake 'up' event before the extra down event. If
we get a focus-out event, we will send fake 'up' events for all currently held
down keys.  The state tracking also ensures that if the key is still held
when focusin occurs, we won't get duplicated 'up' events.

I posted a similar patch quite a while ago, and Anthony suggested trying to
hook into the mapping-notify event, but GTK doesn't expose this to apps,
and we need to track state anyway to work around the GTK key repeat insanity

Regards,
Dan.
-- 
|=- Red Hat, Engineering, Emerging Technologies, Boston.  +1 978 392 2496 -=|
|=-           Perl modules: http://search.cpan.org/~danberr/              -=|
|=-               Projects: http://freshmeat.net/~danielpb/               -=|
|=-  GnuPG: 7D3B9505   F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505  -=| 
diff -r 73b4f4043cb5 src/vncdisplay.c
--- a/src/vncdisplay.c	Tue Jan 01 14:57:39 2008 -0600
+++ b/src/vncdisplay.c	Wed Jan 09 15:11:15 2008 -0500
@@ -43,6 +43,8 @@ struct _VncDisplayPrivate
 
 	gboolean in_pointer_grab;
 	gboolean in_keyboard_grab;
+
+	guint down_keyval[16];
 
 	int button_mask;
 	int last_x;
@@ -392,7 +394,58 @@ static gboolean key_event(GtkWidget *wid
 					    &level,
 					    &consumed);
 
-	gvnc_key_event(priv->gvnc, key->type == GDK_KEY_PRESS ? 1 : 0, keyval);
+	/*
+	 * More VNC suckiness with key state & modifiers in particular
+	 *
+	 * Because VNC has no concept of modifiers, we have to track what keys are
+	 * pressed and when the widget looses focus send fake key up events for all
+	 * keys current held down. This is because upon gaining focus any keys held
+	 * down are no longer likely to be down. This would thus result in keys
+	 * being 'stuck on' in the remote server. eg upon Alt-Tab to switch window
+	 * focus you'd never see key up for the Alt or Tab keys without this :-(
+	 *
+	 * This is mostly a problem with modifier keys, but its best to just track
+	 * all key presses regardless. There's a limit to how many keys a user can
+	 * press at once due to a max of 10 fingers (normally :-), so down_key_vals
+	 * is only storing upto 16 for now. Should be plenty...
+	 *
+	 * Arggggh.
+	 */
+	if (key->type == GDK_KEY_PRESS) {
+		int i;
+		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;
+				/* Send the actual key event we're dealing with */
+				gvnc_key_event(priv->gvnc, 1, keyval);
+				break;
+			} else if (priv->down_keyval[i] == keyval) {
+				/* Got an press when we're already pressed ! Why ... ?
+				 *
+				 * Well, GTK merges sequential press+release pairs of the same
+				 * key so instead of press+release,press+release,press+release
+				 * we only get press+press+press+press+press+release. This
+				 * really annoys some VNC servers, so we have to un-merge
+				 * 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);
+				/* Now send our actual ldown event */
+				gvnc_key_event(priv->gvnc, 1, keyval);
+			}
+		}
+	} else {
+		int i;
+		for (i = 0 ; i < (int)(sizeof(priv->down_keyval)/sizeof(priv->down_keyval[0])) ; i++) {
+			/* We were pressed, and now we're released, so... */
+			if (priv->down_keyval[i] == keyval) {
+				priv->down_keyval[i] = 0;
+				/* ..send the key releae event we're dealing with */
+				gvnc_key_event(priv->gvnc, 0, keyval);
+				break;
+			}
+		}
+	}
 
 	if (key->type == GDK_KEY_PRESS &&
 	    ((keyval == GDK_Control_L && (key->state & GDK_MOD1_MASK)) ||
@@ -436,6 +489,28 @@ static gboolean leave_event(GtkWidget *w
 
         if (priv->grab_keyboard)
                 do_keyboard_ungrab(VNC_DISPLAY(widget), FALSE);
+
+        return TRUE;
+}
+
+
+static gboolean focus_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UNUSED,
+                            gpointer data G_GNUC_UNUSED)
+{
+        VncDisplayPrivate *priv = VNC_DISPLAY(widget)->priv;
+	int i;
+
+        if (priv->gvnc == NULL || !gvnc_is_initialized(priv->gvnc))
+                return TRUE;
+
+	for (i = 0 ; i < (int)(sizeof(priv->down_keyval)/sizeof(priv->down_keyval[0])) ; i++) {
+		/* 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]);
+			priv->down_keyval[i] = 0;
+		}
+	}
 
         return TRUE;
 }
@@ -1128,6 +1203,8 @@ static void vnc_display_init(VncDisplay 
 			 G_CALLBACK(enter_event), NULL);
 	g_signal_connect(obj, "leave-notify-event",
 			 G_CALLBACK(leave_event), NULL);
+	g_signal_connect(obj, "focus-out-event",
+			 G_CALLBACK(focus_event), NULL);
 
 	GTK_WIDGET_SET_FLAGS(obj, GTK_CAN_FOCUS);
 


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