From patchwork Wed Feb 20 13:43:22 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anthony Liguori X-Patchwork-Id: 222090 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 7CC3B2C007E for ; Thu, 21 Feb 2013 00:57:00 +1100 (EST) Received: from localhost ([::1]:60664 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1U89yU-0001ZL-RG for incoming@patchwork.ozlabs.org; Wed, 20 Feb 2013 08:44:42 -0500 Received: from eggs.gnu.org ([208.118.235.92]:45198) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1U89xm-0000HC-9m for qemu-devel@nongnu.org; Wed, 20 Feb 2013 08:44:00 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1U89xf-0005U3-6j for qemu-devel@nongnu.org; Wed, 20 Feb 2013 08:43:58 -0500 Received: from e28smtp02.in.ibm.com ([122.248.162.2]:41522) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1U89xe-0005TW-Cz for qemu-devel@nongnu.org; Wed, 20 Feb 2013 08:43:51 -0500 Received: from /spool/local by e28smtp02.in.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Wed, 20 Feb 2013 19:10:38 +0530 Received: from d28dlp01.in.ibm.com (9.184.220.126) by e28smtp02.in.ibm.com (192.168.1.132) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Wed, 20 Feb 2013 19:10:32 +0530 Received: from d28relay01.in.ibm.com (d28relay01.in.ibm.com [9.184.220.58]) by d28dlp01.in.ibm.com (Postfix) with ESMTP id 67DADE004C for ; Wed, 20 Feb 2013 19:14:40 +0530 (IST) Received: from d28av04.in.ibm.com (d28av04.in.ibm.com [9.184.220.66]) by d28relay01.in.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id r1KDhctZ32440346 for ; Wed, 20 Feb 2013 19:13:38 +0530 Received: from d28av04.in.ibm.com (loopback [127.0.0.1]) by d28av04.in.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id r1KDhfCc014237 for ; Thu, 21 Feb 2013 00:43:41 +1100 Received: from titi.austin.rr.com (sig-9-76-138-226.mts.ibm.com [9.76.138.226]) by d28av04.in.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id r1KDhSxw013442; Thu, 21 Feb 2013 00:43:40 +1100 From: Anthony Liguori To: qemu-devel@nongnu.org Date: Wed, 20 Feb 2013 07:43:22 -0600 Message-Id: <1361367806-4599-6-git-send-email-aliguori@us.ibm.com> X-Mailer: git-send-email 1.8.0 In-Reply-To: <1361367806-4599-1-git-send-email-aliguori@us.ibm.com> References: <1361367806-4599-1-git-send-email-aliguori@us.ibm.com> X-Content-Scanned: Fidelis XPS MAILER x-cbid: 13022013-5816-0000-0000-000006C1DC2B X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic] X-Received-From: 122.248.162.2 Cc: Kevin Wolf , Anthony Liguori Subject: [Qemu-devel] [PATCH 5/9] gtk: add support for input grabbing (v2) X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org 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 --- v1 -> v2 - add a menu option for grab on hover (Jan) --- ui/gtk.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 151 insertions(+), 5 deletions(-) diff --git a/ui/gtk.c b/ui/gtk.c index 94f0461..0c1ec4d 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -80,6 +80,8 @@ typedef struct GtkDisplayState GtkWidget *view_menu_item; GtkWidget *view_menu; + GtkWidget *grab_item; + GtkWidget *grab_on_hover_item; GtkWidget *vga_item; int nb_vcs; @@ -108,6 +110,21 @@ 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 bool gd_grab_on_hover(GtkDisplayState *s) +{ + return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_on_hover_item)); +} + +static bool gd_on_vga(GtkDisplayState *s) +{ + return gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook)) == 0; +} + static void gd_update_cursor(GtkDisplayState *s, gboolean override) { GdkWindow *window; @@ -115,9 +132,10 @@ static void gd_update_cursor(GtkDisplayState *s, gboolean override) window = gtk_widget_get_window(GTK_WIDGET(s->drawing_area)); - on_vga = (gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook)) == 0); + on_vga = gd_on_vga(s); - 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); @@ -128,15 +146,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); @@ -262,6 +285,9 @@ static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque) s->scale_y = 1.0; } + fbw *= s->scale_x; + fbh *= s->scale_y; + cairo_set_source_surface(cr, s->surface, 0, 0); cairo_paint(cr); @@ -313,10 +339,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; } @@ -432,11 +492,49 @@ static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque) } } +static void gd_grab_keyboard(GtkDisplayState *s) +{ + gdk_keyboard_grab(gtk_widget_get_window(GTK_WIDGET(s->drawing_area)), + FALSE, + GDK_CURRENT_TIME); +} + +static void gd_ungrab_keyboard(GtkDisplayState *s) +{ + gdk_keyboard_ungrab(GDK_CURRENT_TIME); +} + +static void gd_menu_grab_input(GtkMenuItem *item, void *opaque) +{ + GtkDisplayState *s = opaque; + + if (gd_is_grab_active(s)) { + gd_grab_keyboard(s); + 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 { + gd_ungrab_keyboard(s); + 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; @@ -448,6 +546,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 { @@ -462,9 +567,33 @@ 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); } +static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing, gpointer data) +{ + GtkDisplayState *s = data; + + if (!gd_is_grab_active(s) && gd_grab_on_hover(s)) { + gd_grab_keyboard(s); + } + + return TRUE; +} + +static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing, gpointer data) +{ + GtkDisplayState *s = data; + + if (!gd_is_grab_active(s) && gd_grab_on_hover(s)) { + gd_ungrab_keyboard(s); + } + + return TRUE; +} + /** Virtual Console Callbacks **/ static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len) @@ -612,8 +741,14 @@ 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); + g_signal_connect(s->drawing_area, "enter-notify-event", + G_CALLBACK(gd_enter_event), s); + g_signal_connect(s->drawing_area, "leave-notify-event", + G_CALLBACK(gd_leave_event), s); } static void gd_create_menus(GtkDisplayState *s) @@ -639,6 +774,15 @@ static void gd_create_menus(GtkDisplayState *s) gtk_menu_set_accel_group(GTK_MENU(s->view_menu), accel_group); s->view_menu_item = gtk_menu_item_new_with_mnemonic("_View"); + s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic("Grab On _Hover"); + gtk_menu_append(GTK_MENU(s->view_menu), s->grab_on_hover_item); + + s->grab_item = gtk_check_menu_item_new_with_mnemonic("_Grab Input"); + gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item), + "/View/Grab Input"); + gtk_accel_map_add_entry("/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); @@ -711,6 +855,8 @@ void gtk_display_init(DisplayState *ds) GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK | GDK_SCROLL_MASK | GDK_KEY_PRESS_MASK); gtk_widget_set_double_buffered(s->drawing_area, FALSE);