diff mbox

[5/8] gtk: add support for input grabbing

Message ID 1330299995-8688-6-git-send-email-aliguori@us.ibm.com
State New
Headers show

Commit Message

Anthony Liguori Feb. 26, 2012, 11:46 p.m. UTC
There is a small deviation from SDL's behavior here.  Instead of Ctrl+Alt
triggering grab, we now use Ctrl-Alt-g to trigger grab.

GTK will not accept Ctrl+Alt as an accelerator since it just consists of
modifiers.  Having grab as a proper accelerator is important as it allows a user
to override the accelerator for accessibility purposes.

We also are not automatically grabbing on left-click.  Besides the inability to
tie mouse clicks to an accelerator, I think this behavior is hard to discover
and since it only happens depending on the guest state, it can lead to confusing
behavior.

This can be changed in the future if there's a strong resistence to dropping
left-click-to-grab, but I think we're better off dropping it.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 ui/gtk.c |  102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 98 insertions(+), 4 deletions(-)
diff mbox

Patch

diff --git a/ui/gtk.c b/ui/gtk.c
index 0579a55..0dac807 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -79,6 +79,7 @@  typedef struct GtkDisplayState
 
     GtkWidget *view_menu_item;
     GtkWidget *view_menu;
+    GtkWidget *grab_item;
     GtkWidget *vga_item;
 
     int nb_vcs;
@@ -107,6 +108,11 @@  static GtkDisplayState *global_state;
 
 /** Utility Functions **/
 
+static bool gd_is_grab_active(GtkDisplayState *s)
+{
+    return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_item));
+}
+
 static void gd_update_cursor(GtkDisplayState *s, gboolean override)
 {
     GdkWindow *window;
@@ -116,7 +122,8 @@  static void gd_update_cursor(GtkDisplayState *s, gboolean override)
 
     on_vga = (gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook)) == 0);
 
-    if ((override || on_vga) && kbd_mouse_is_absolute()) {
+    if ((override || on_vga) &&
+        (kbd_mouse_is_absolute() || gd_is_grab_active(s))) {
 	gdk_window_set_cursor(window, s->null_cursor);
     } else {
 	gdk_window_set_cursor(window, NULL);
@@ -127,15 +134,20 @@  static void gd_update_caption(GtkDisplayState *s)
 {
     const char *status = "";
     gchar *title;
+    const char *grab = "";
+
+    if (gd_is_grab_active(s)) {
+        grab = " - Press Ctrl+Alt+G to release grab";
+    }
 
     if (!runstate_is_running()) {
         status = " [Stopped]";
     }
 
     if (qemu_name) {
-        title = g_strdup_printf("QEMU (%s)%s", qemu_name, status);
+        title = g_strdup_printf("QEMU (%s)%s%s", qemu_name, status, grab);
     } else {
-        title = g_strdup_printf("QEMU%s", status);
+        title = g_strdup_printf("QEMU%s%s", status, grab);
     }
         
     gtk_window_set_title(GTK_WINDOW(s->window), title);
@@ -309,10 +321,44 @@  static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
     s->last_x = x;
     s->last_y = y;
 
-    if (kbd_mouse_is_absolute()) {
+    if (kbd_mouse_is_absolute() || gd_is_grab_active(s)) {
         kbd_mouse_event(dx, dy, 0, s->button_mask);
     }
 
+    if (!kbd_mouse_is_absolute() && gd_is_grab_active(s)) {
+        GdkDrawable *drawable = GDK_DRAWABLE(gtk_widget_get_window(s->drawing_area));
+        GdkDisplay *display = gdk_drawable_get_display(drawable);
+        GdkScreen *screen = gdk_drawable_get_screen(drawable);
+        int x = (int)motion->x_root;
+        int y = (int)motion->y_root;
+
+        /* In relative mode check to see if client pointer hit
+         * one of the screen edges, and if so move it back by
+         * 200 pixels. This is important because the pointer
+         * in the server doesn't correspond 1-for-1, and so
+         * may still be only half way across the screen. Without
+         * this warp, the server pointer would thus appear to hit
+         * an invisible wall */
+        if (x == 0) {
+            x += 200;
+        }
+        if (y == 0) {
+            y += 200;
+        }
+        if (x == (gdk_screen_get_width(screen) - 1)) {
+            x -= 200;
+        }
+        if (y == (gdk_screen_get_height(screen) - 1)) {
+            y -= 200;
+        }
+
+        if (x != (int)motion->x_root || y != (int)motion->y_root) {
+            gdk_display_warp_pointer(display, screen, x, y);
+            s->last_x = -1;
+            s->last_y = -1;
+            return FALSE;
+        }
+    }        
     return TRUE;
 }
 
@@ -428,11 +474,39 @@  static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
     }
 }
 
+static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
+{
+    GtkDisplayState *s = opaque;
+
+    if (gd_is_grab_active(s)) {
+	gdk_keyboard_grab(gtk_widget_get_window(GTK_WIDGET(s->drawing_area)),
+			  FALSE,
+			  GDK_CURRENT_TIME);
+	gdk_pointer_grab(gtk_widget_get_window(GTK_WIDGET(s->drawing_area)),
+			 FALSE, /* All events to come to our window directly */
+			 GDK_POINTER_MOTION_MASK |
+			 GDK_BUTTON_PRESS_MASK |
+			 GDK_BUTTON_RELEASE_MASK |
+			 GDK_BUTTON_MOTION_MASK |
+			 GDK_SCROLL_MASK,
+			 NULL, /* Allow cursor to move over entire desktop */
+                         s->null_cursor,
+			 GDK_CURRENT_TIME);
+    } else {
+	gdk_keyboard_ungrab(GDK_CURRENT_TIME);
+	gdk_pointer_ungrab(GDK_CURRENT_TIME);
+    }
+
+    gd_update_caption(s);
+    gd_update_cursor(s, FALSE);
+}
+
 static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
                            gpointer data)
 {
     GtkDisplayState *s = data;
     guint last_page;
+    gboolean on_vga;
 
     if (!gtk_widget_get_realized(s->notebook)) {
         return;
@@ -444,6 +518,13 @@  static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
         gtk_widget_set_size_request(s->vc[last_page - 1].terminal, -1, -1);
     }
 
+    on_vga = arg2 == 0;
+
+    if (!on_vga) {
+        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
+                                       FALSE);
+    }
+
     if (arg2 == 0) {
         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->vga_item), TRUE);
     } else {
@@ -458,6 +539,8 @@  static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
         gtk_widget_set_size_request(vc->terminal, width, height);
     }        
 
+    gtk_widget_set_sensitive(s->grab_item, on_vga);
+
     gd_update_cursor(s, TRUE);
 }
 
@@ -609,6 +692,8 @@  static void gd_connect_signals(GtkDisplayState *s)
                      G_CALLBACK(gd_menu_quit), s);
     g_signal_connect(s->vga_item, "activate",
                      G_CALLBACK(gd_menu_switch_vc), s);
+    g_signal_connect(s->grab_item, "activate",
+                     G_CALLBACK(gd_menu_grab_input), s);
     g_signal_connect(s->notebook, "switch-page",
                      G_CALLBACK(gd_change_page), s);
 }
@@ -639,6 +724,15 @@  static void gd_create_menus(GtkDisplayState *s)
     separator = gtk_separator_menu_item_new();
     gtk_menu_append(GTK_MENU(s->view_menu), separator);
 
+    s->grab_item = gtk_check_menu_item_new_with_mnemonic("_Grab Input");
+    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item),
+                                 "<QEMU>/View/Grab Input");
+    gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g, GDK_CONTROL_MASK | GDK_MOD1_MASK);
+    gtk_menu_append(GTK_MENU(s->view_menu), s->grab_item);
+
+    separator = gtk_separator_menu_item_new();
+    gtk_menu_append(GTK_MENU(s->view_menu), separator);
+
     s->vga_item = gtk_radio_menu_item_new_with_mnemonic(group, "_VGA");
     group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(s->vga_item));
     gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->vga_item),