X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/d3ef575080c1d7a59a7df0678702f5e320000e87..03de06dde54df1f64bb099efe22edfa773b16e8e:/ui/gtk.c diff --git a/ui/gtk.c b/ui/gtk.c index 6790cf8f7c..0385757bf5 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -68,13 +68,37 @@ #include "keymaps.h" #include "sysemu/char.h" #include "qom/object.h" +#ifdef GDK_WINDOWING_X11 +#include +#include +#endif #define MAX_VCS 10 +#define VC_WINDOW_X_MIN 320 +#define VC_WINDOW_Y_MIN 240 +#define VC_TERM_X_MIN 80 +#define VC_TERM_Y_MIN 25 +#define VC_SCALE_MIN 0.25 +#define VC_SCALE_STEP 0.25 #if !defined(CONFIG_VTE) # define VTE_CHECK_VERSION(a, b, c) 0 #endif +#if defined(CONFIG_VTE) && !GTK_CHECK_VERSION(3, 0, 0) +/* + * The gtk2 vte terminal widget seriously messes up the window resize + * for some reason. You basically can't make the qemu window smaller + * any more because the toplevel window geoemtry hints are overridden. + * + * Workaround that by hiding all vte widgets, except the one in the + * current tab. + * + * Luckily everything works smooth in gtk3. + */ +# define VTE_RESIZE_HACK 1 +#endif + /* Compatibility define to let us build on both Gtk2 and Gtk3 */ #if GTK_CHECK_VERSION(3, 0, 0) static inline void gdk_drawable_get_size(GdkWindow *w, gint *ww, gint *wh) @@ -88,6 +112,13 @@ static inline void gdk_drawable_get_size(GdkWindow *w, gint *ww, gint *wh) #define gtk_widget_get_realized(widget) GTK_WIDGET_REALIZED(widget) #endif +#ifndef GDK_IS_X11_DISPLAY +#define GDK_IS_X11_DISPLAY(dpy) (dpy == dpy) +#endif +#ifndef GDK_IS_WIN32_DISPLAY +#define GDK_IS_WIN32_DISPLAY(dpy) (dpy == dpy) +#endif + #ifndef GDK_KEY_0 #define GDK_KEY_0 GDK_0 #define GDK_KEY_1 GDK_1 @@ -97,6 +128,7 @@ static inline void gdk_drawable_get_size(GdkWindow *w, gint *ww, gint *wh) #define GDK_KEY_q GDK_q #define GDK_KEY_plus GDK_plus #define GDK_KEY_minus GDK_minus +#define GDK_KEY_Pause GDK_Pause #endif #define HOTKEY_MODIFIERS (GDK_CONTROL_MASK | GDK_MOD1_MASK) @@ -134,6 +166,8 @@ typedef enum VirtualConsoleType { typedef struct VirtualConsole { GtkDisplayState *s; + char *label; + GtkWidget *window; GtkWidget *menu_item; GtkWidget *tab_item; VirtualConsoleType type; @@ -173,6 +207,7 @@ struct GtkDisplayState { VirtualConsole vc[MAX_VCS]; GtkWidget *show_tabs_item; + GtkWidget *untabify_item; GtkWidget *vbox; GtkWidget *notebook; @@ -182,6 +217,8 @@ struct GtkDisplayState { int last_y; int grab_x_root; int grab_y_root; + VirtualConsole *kbd_owner; + VirtualConsole *ptr_owner; gboolean full_screen; @@ -192,9 +229,11 @@ struct GtkDisplayState { bool external_pause_update; bool modifier_pressed[ARRAY_SIZE(modifier_keycode)]; + bool has_evdev; }; -static GtkDisplayState *global_state; +static void gd_grab_pointer(VirtualConsole *vc); +static void gd_ungrab_pointer(GtkDisplayState *s); /** Utility Functions **/ @@ -256,7 +295,7 @@ static void gd_update_cursor(VirtualConsole *vc) } window = gtk_widget_get_window(GTK_WIDGET(vc->gfx.drawing_area)); - if (s->full_screen || qemu_input_is_absolute() || gd_is_grab_active(s)) { + if (s->full_screen || qemu_input_is_absolute() || s->ptr_owner == vc) { gdk_window_set_cursor(window, s->null_cursor); } else { gdk_window_set_cursor(window, NULL); @@ -266,11 +305,20 @@ static void gd_update_cursor(VirtualConsole *vc) static void gd_update_caption(GtkDisplayState *s) { const char *status = ""; + gchar *prefix; gchar *title; const char *grab = ""; bool is_paused = !runstate_is_running(); + int i; - if (gd_is_grab_active(s)) { + if (qemu_name) { + prefix = g_strdup_printf("QEMU (%s)", qemu_name); + } else { + prefix = g_strdup_printf("QEMU"); + } + + if (s->ptr_owner != NULL && + s->ptr_owner->window == NULL) { grab = _(" - Press Ctrl+Alt+G to release grab"); } @@ -282,37 +330,84 @@ static void gd_update_caption(GtkDisplayState *s) is_paused); s->external_pause_update = false; - if (qemu_name) { - title = g_strdup_printf("QEMU (%s)%s%s", qemu_name, status, grab); - } else { - title = g_strdup_printf("QEMU%s%s", status, grab); + title = g_strdup_printf("%s%s%s", prefix, status, grab); + gtk_window_set_title(GTK_WINDOW(s->window), title); + g_free(title); + + for (i = 0; i < s->nb_vcs; i++) { + VirtualConsole *vc = &s->vc[i]; + + if (!vc->window) { + continue; + } + title = g_strdup_printf("%s: %s%s%s", prefix, vc->label, + vc == s->kbd_owner ? " +kbd" : "", + vc == s->ptr_owner ? " +ptr" : ""); + gtk_window_set_title(GTK_WINDOW(vc->window), title); + g_free(title); } - gtk_window_set_title(GTK_WINDOW(s->window), title); + g_free(prefix); +} - g_free(title); +static void gd_update_geometry_hints(VirtualConsole *vc) +{ + GtkDisplayState *s = vc->s; + GdkWindowHints mask = 0; + GdkGeometry geo = {}; + GtkWidget *geo_widget = NULL; + GtkWindow *geo_window; + + if (vc->type == GD_VC_GFX) { + if (s->free_scale) { + geo.min_width = surface_width(vc->gfx.ds) * VC_SCALE_MIN; + geo.min_height = surface_height(vc->gfx.ds) * VC_SCALE_MIN; + mask |= GDK_HINT_MIN_SIZE; + } else { + geo.min_width = surface_width(vc->gfx.ds) * vc->gfx.scale_x; + geo.min_height = surface_height(vc->gfx.ds) * vc->gfx.scale_y; + mask |= GDK_HINT_MIN_SIZE; + } + geo_widget = vc->gfx.drawing_area; + gtk_widget_set_size_request(geo_widget, geo.min_width, geo.min_height); + +#if defined(CONFIG_VTE) + } else if (vc->type == GD_VC_VTE) { + VteTerminal *term = VTE_TERMINAL(vc->vte.terminal); + GtkBorder *ib; + + geo.width_inc = vte_terminal_get_char_width(term); + geo.height_inc = vte_terminal_get_char_height(term); + mask |= GDK_HINT_RESIZE_INC; + geo.base_width = geo.width_inc; + geo.base_height = geo.height_inc; + mask |= GDK_HINT_BASE_SIZE; + geo.min_width = geo.width_inc * VC_TERM_X_MIN; + geo.min_height = geo.height_inc * VC_TERM_Y_MIN; + mask |= GDK_HINT_MIN_SIZE; + gtk_widget_style_get(vc->vte.terminal, "inner-border", &ib, NULL); + geo.base_width += ib->left + ib->right; + geo.base_height += ib->top + ib->bottom; + geo.min_width += ib->left + ib->right; + geo.min_height += ib->top + ib->bottom; + geo_widget = vc->vte.terminal; +#endif + } + + geo_window = GTK_WINDOW(vc->window ? vc->window : s->window); + gtk_window_set_geometry_hints(geo_window, geo_widget, &geo, mask); } static void gd_update_windowsize(VirtualConsole *vc) { GtkDisplayState *s = vc->s; - double sx, sy; - if (vc->type != GD_VC_GFX || s->full_screen) { - return; - } + gd_update_geometry_hints(vc); - if (s->free_scale) { - sx = 1.0; - sy = 1.0; - } else { - sx = vc->gfx.scale_x; - sy = vc->gfx.scale_y; + if (vc->type == GD_VC_GFX && !s->full_screen && !s->free_scale) { + gtk_window_resize(GTK_WINDOW(vc->window ? vc->window : s->window), + VC_WINDOW_X_MIN, VC_WINDOW_Y_MIN); } - gtk_widget_set_size_request(vc->gfx.drawing_area, - surface_width(vc->gfx.ds) * sx, - surface_height(vc->gfx.ds) * sy); - gtk_window_resize(GTK_WINDOW(s->window), 320, 240); } static void gd_update_full_redraw(VirtualConsole *vc) @@ -341,6 +436,15 @@ static void gtk_release_modifiers(GtkDisplayState *s) } } +static void gd_widget_reparent(GtkWidget *from, GtkWidget *to, + GtkWidget *widget) +{ + g_object_ref(G_OBJECT(widget)); + gtk_container_remove(GTK_CONTAINER(from), widget); + gtk_container_add(GTK_CONTAINER(to), widget); + g_object_unref(G_OBJECT(widget)); +} + /** DisplayState Callbacks **/ static void gd_update(DisplayChangeListener *dcl, @@ -352,7 +456,7 @@ static void gd_update(DisplayChangeListener *dcl, int fbw, fbh; int ww, wh; - trace_gd_update(x, y, w, h); + trace_gd_update(vc->label, x, y, w, h); if (vc->gfx.convert) { pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image, @@ -458,7 +562,7 @@ static void gd_switch(DisplayChangeListener *dcl, VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); bool resized = true; - trace_gd_switch(surface_width(surface), surface_height(surface)); + trace_gd_switch(vc->label, surface_width(surface), surface_height(surface)); if (vc->gfx.surface) { cairo_surface_destroy(vc->gfx.surface); @@ -527,6 +631,7 @@ static void gd_change_runstate(void *opaque, int running, RunState state) static void gd_mouse_mode_change(Notifier *notify, void *data) { GtkDisplayState *s; + int i; s = container_of(notify, GtkDisplayState, mouse_mode_notifier); /* release the grab at switching to absolute mode */ @@ -534,7 +639,10 @@ static void gd_mouse_mode_change(Notifier *notify, void *data) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), FALSE); } - gd_update_cursor(gd_vc_find_current(s)); + for (i = 0; i < s->nb_vcs; i++) { + VirtualConsole *vc = &s->vc[i]; + gd_update_cursor(vc); + } } /** GTK Events **/ @@ -679,7 +787,7 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y, surface_height(vc->gfx.ds)); qemu_input_event_sync(); - } else if (s->last_set && gd_is_grab_active(s)) { + } else if (s->last_set && s->ptr_owner == vc) { qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x); qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y); qemu_input_event_sync(); @@ -688,7 +796,7 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, s->last_y = y; s->last_set = TRUE; - if (!qemu_input_is_absolute() && gd_is_grab_active(s)) { + if (!qemu_input_is_absolute() && s->ptr_owner == vc) { GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area); int x = (int)motion->x_root; int y = (int)motion->y_root; @@ -737,9 +845,15 @@ static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button, /* implicitly grab the input at the first click in the relative mode */ if (button->button == 1 && button->type == GDK_BUTTON_PRESS && - !qemu_input_is_absolute() && !gd_is_grab_active(s)) { - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), - TRUE); + !qemu_input_is_absolute() && s->ptr_owner != vc) { + gd_ungrab_pointer(s); + if (!vc->window) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), + TRUE); + } else { + gd_grab_pointer(vc); + gd_update_caption(s); + } return TRUE; } @@ -780,29 +894,34 @@ static gboolean gd_scroll_event(GtkWidget *widget, GdkEventScroll *scroll, return TRUE; } -static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque) +static int gd_map_keycode(GtkDisplayState *s, GdkDisplay *dpy, int gdk_keycode) { - VirtualConsole *vc = opaque; - GtkDisplayState *s = vc->s; - int gdk_keycode = key->hardware_keycode; - int i; + int qemu_keycode; -#ifdef _WIN32 - UINT qemu_keycode = MapVirtualKey(gdk_keycode, MAPVK_VK_TO_VSC); - switch (qemu_keycode) { - case 103: /* alt gr */ - qemu_keycode = 56 | SCANCODE_GREY; - break; +#ifdef GDK_WINDOWING_WIN32 + if (GDK_IS_WIN32_DISPLAY(dpy)) { + qemu_keycode = MapVirtualKey(gdk_keycode, MAPVK_VK_TO_VSC); + switch (qemu_keycode) { + case 103: /* alt gr */ + qemu_keycode = 56 | SCANCODE_GREY; + break; + } + return qemu_keycode; } -#else - int qemu_keycode; +#endif if (gdk_keycode < 9) { qemu_keycode = 0; } else if (gdk_keycode < 97) { qemu_keycode = gdk_keycode - 8; - } else if (gdk_keycode < 158) { - qemu_keycode = translate_evdev_keycode(gdk_keycode - 97); +#ifdef GDK_WINDOWING_X11 + } else if (GDK_IS_X11_DISPLAY(dpy) && gdk_keycode < 158) { + if (s->has_evdev) { + qemu_keycode = translate_evdev_keycode(gdk_keycode - 97); + } else { + qemu_keycode = translate_xfree86_keycode(gdk_keycode - 97); + } +#endif } else if (gdk_keycode == 208) { /* Hiragana_Katakana */ qemu_keycode = 0x70; } else if (gdk_keycode == 211) { /* backslash */ @@ -810,9 +929,28 @@ static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque) } else { qemu_keycode = 0; } -#endif - trace_gd_key_event(gdk_keycode, qemu_keycode, + return qemu_keycode; +} + +static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque) +{ + VirtualConsole *vc = opaque; + GtkDisplayState *s = vc->s; + int gdk_keycode = key->hardware_keycode; + int qemu_keycode; + int i; + + if (key->keyval == GDK_KEY_Pause) { + qemu_input_event_send_key_qcode(vc->gfx.dcl.con, Q_KEY_CODE_PAUSE, + key->type == GDK_KEY_PRESS); + return TRUE; + } + + qemu_keycode = gd_map_keycode(s, gtk_widget_get_display(widget), + gdk_keycode); + + trace_gd_key_event(vc->label, gdk_keycode, qemu_keycode, (key->type == GDK_KEY_PRESS) ? "down" : "up"); for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) { @@ -870,25 +1008,94 @@ static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque) { GtkDisplayState *s = opaque; VirtualConsole *vc = gd_vc_find_by_menu(s); + GtkNotebook *nb = GTK_NOTEBOOK(s->notebook); + GtkWidget *child; gint page; gtk_release_modifiers(s); if (vc) { - page = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), - vc->tab_item); - gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), page); + page = gtk_notebook_page_num(nb, vc->tab_item); + gtk_notebook_set_current_page(nb, page); + child = gtk_notebook_get_nth_page(nb, page); + gtk_widget_grab_focus(child); } } +static void gd_accel_switch_vc(void *opaque) +{ + VirtualConsole *vc = opaque; + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE); +} + static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque) { GtkDisplayState *s = opaque; + VirtualConsole *vc = gd_vc_find_current(s); if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->show_tabs_item))) { gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), TRUE); } else { gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE); } + gd_update_windowsize(vc); +} + +static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event, + void *opaque) +{ + VirtualConsole *vc = opaque; + GtkDisplayState *s = vc->s; + + gtk_widget_set_sensitive(vc->menu_item, true); + gd_widget_reparent(vc->window, s->notebook, vc->tab_item); + gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook), + vc->tab_item, vc->label); + gtk_widget_destroy(vc->window); + vc->window = NULL; + return TRUE; +} + +static gboolean gd_win_grab(void *opaque) +{ + VirtualConsole *vc = opaque; + + fprintf(stderr, "%s: %s\n", __func__, vc->label); + if (vc->s->ptr_owner) { + gd_ungrab_pointer(vc->s); + } else { + gd_grab_pointer(vc); + } + gd_update_caption(vc->s); + return TRUE; +} + +static void gd_menu_untabify(GtkMenuItem *item, void *opaque) +{ + GtkDisplayState *s = opaque; + VirtualConsole *vc = gd_vc_find_current(s); + + if (vc->type == GD_VC_GFX) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), + FALSE); + } + if (!vc->window) { + gtk_widget_set_sensitive(vc->menu_item, false); + vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gd_widget_reparent(s->notebook, vc->window, vc->tab_item); + + g_signal_connect(vc->window, "delete-event", + G_CALLBACK(gd_tab_window_close), vc); + gtk_widget_show_all(vc->window); + + GtkAccelGroup *ag = gtk_accel_group_new(); + gtk_window_add_accel_group(GTK_WINDOW(vc->window), ag); + + GClosure *cb = g_cclosure_new_swap(G_CALLBACK(gd_win_grab), vc, NULL); + gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb); + + gd_update_geometry_hints(vc); + gd_update_caption(s); + } } static void gd_menu_full_screen(GtkMenuItem *item, void *opaque) @@ -898,7 +1105,7 @@ static void gd_menu_full_screen(GtkMenuItem *item, void *opaque) if (!s->full_screen) { gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE); - gtk_widget_set_size_request(s->menu_bar, 0, 0); + gtk_widget_hide(s->menu_bar); if (vc->type == GD_VC_GFX) { gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), @@ -909,14 +1116,12 @@ static void gd_menu_full_screen(GtkMenuItem *item, void *opaque) } else { gtk_window_unfullscreen(GTK_WINDOW(s->window)); gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s); - gtk_widget_set_size_request(s->menu_bar, -1, -1); + gtk_widget_show(s->menu_bar); s->full_screen = FALSE; if (vc->type == GD_VC_GFX) { vc->gfx.scale_x = 1.0; vc->gfx.scale_y = 1.0; - gtk_widget_set_size_request(vc->gfx.drawing_area, - surface_width(vc->gfx.ds), - surface_height(vc->gfx.ds)); + gd_update_windowsize(vc); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), FALSE); } @@ -925,6 +1130,12 @@ static void gd_menu_full_screen(GtkMenuItem *item, void *opaque) gd_update_cursor(vc); } +static void gd_accel_full_screen(void *opaque) +{ + GtkDisplayState *s = opaque; + gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item)); +} + static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque) { GtkDisplayState *s = opaque; @@ -933,8 +1144,8 @@ static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item), FALSE); - vc->gfx.scale_x += .25; - vc->gfx.scale_y += .25; + vc->gfx.scale_x += VC_SCALE_STEP; + vc->gfx.scale_y += VC_SCALE_STEP; gd_update_windowsize(vc); } @@ -947,11 +1158,11 @@ static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item), FALSE); - vc->gfx.scale_x -= .25; - vc->gfx.scale_y -= .25; + vc->gfx.scale_x -= VC_SCALE_STEP; + vc->gfx.scale_y -= VC_SCALE_STEP; - vc->gfx.scale_x = MAX(vc->gfx.scale_x, .25); - vc->gfx.scale_y = MAX(vc->gfx.scale_y, .25); + vc->gfx.scale_x = MAX(vc->gfx.scale_x, VC_SCALE_MIN); + vc->gfx.scale_y = MAX(vc->gfx.scale_y, VC_SCALE_MIN); gd_update_windowsize(vc); } @@ -978,61 +1189,69 @@ static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque) s->free_scale = FALSE; vc->gfx.scale_x = 1.0; vc->gfx.scale_y = 1.0; - gd_update_windowsize(vc); } + gd_update_windowsize(vc); gd_update_full_redraw(vc); } -static void gd_grab_keyboard(VirtualConsole *vc) -{ #if GTK_CHECK_VERSION(3, 0, 0) +static void gd_grab_devices(VirtualConsole *vc, bool grab, + GdkInputSource source, GdkEventMask mask, + GdkCursor *cursor) +{ GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area); GdkDeviceManager *mgr = gdk_display_get_device_manager(display); - GList *devices = gdk_device_manager_list_devices(mgr, - GDK_DEVICE_TYPE_MASTER); - GList *tmp = devices; - while (tmp) { + GList *devs = gdk_device_manager_list_devices(mgr, GDK_DEVICE_TYPE_MASTER); + GList *tmp = devs; + + for (tmp = devs; tmp; tmp = tmp->next) { GdkDevice *dev = tmp->data; - if (gdk_device_get_source(dev) == GDK_SOURCE_KEYBOARD) { - gdk_device_grab(dev, - gtk_widget_get_window(vc->gfx.drawing_area), - GDK_OWNERSHIP_NONE, - FALSE, - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, - NULL, - GDK_CURRENT_TIME); + if (gdk_device_get_source(dev) != source) { + continue; + } + if (grab) { + GdkWindow *win = gtk_widget_get_window(vc->gfx.drawing_area); + gdk_device_grab(dev, win, GDK_OWNERSHIP_NONE, FALSE, + mask, cursor, GDK_CURRENT_TIME); + } else { + gdk_device_ungrab(dev, GDK_CURRENT_TIME); } - tmp = tmp->next; } - g_list_free(devices); + g_list_free(devs); +} +#endif + +static void gd_grab_keyboard(VirtualConsole *vc) +{ +#if GTK_CHECK_VERSION(3, 0, 0) + gd_grab_devices(vc, true, GDK_SOURCE_KEYBOARD, + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, + NULL); #else gdk_keyboard_grab(gtk_widget_get_window(vc->gfx.drawing_area), FALSE, GDK_CURRENT_TIME); #endif + vc->s->kbd_owner = vc; + trace_gd_grab(vc->label, "kbd", true); } -static void gd_ungrab_keyboard(VirtualConsole *vc) +static void gd_ungrab_keyboard(GtkDisplayState *s) { -#if GTK_CHECK_VERSION(3, 0, 0) - GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area); - GdkDeviceManager *mgr = gdk_display_get_device_manager(display); - GList *devices = gdk_device_manager_list_devices(mgr, - GDK_DEVICE_TYPE_MASTER); - GList *tmp = devices; - while (tmp) { - GdkDevice *dev = tmp->data; - if (gdk_device_get_source(dev) == GDK_SOURCE_KEYBOARD) { - gdk_device_ungrab(dev, - GDK_CURRENT_TIME); - } - tmp = tmp->next; + VirtualConsole *vc = s->kbd_owner; + + if (vc == NULL) { + return; } - g_list_free(devices); + s->kbd_owner = NULL; + +#if GTK_CHECK_VERSION(3, 0, 0) + gd_grab_devices(vc, false, GDK_SOURCE_KEYBOARD, 0, NULL); #else gdk_keyboard_ungrab(GDK_CURRENT_TIME); #endif + trace_gd_grab(vc->label, "kbd", false); } static void gd_grab_pointer(VirtualConsole *vc) @@ -1040,28 +1259,13 @@ static void gd_grab_pointer(VirtualConsole *vc) GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area); #if GTK_CHECK_VERSION(3, 0, 0) GdkDeviceManager *mgr = gdk_display_get_device_manager(display); - GList *devices = gdk_device_manager_list_devices(mgr, - GDK_DEVICE_TYPE_MASTER); - GList *tmp = devices; - while (tmp) { - GdkDevice *dev = tmp->data; - if (gdk_device_get_source(dev) == GDK_SOURCE_MOUSE) { - gdk_device_grab(dev, - gtk_widget_get_window(vc->gfx.drawing_area), - GDK_OWNERSHIP_NONE, - 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, - vc->s->null_cursor, - GDK_CURRENT_TIME); - } - tmp = tmp->next; - } - g_list_free(devices); + gd_grab_devices(vc, true, GDK_SOURCE_MOUSE, + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_MOTION_MASK | + GDK_SCROLL_MASK, + vc->s->null_cursor); gdk_device_get_position(gdk_device_manager_get_client_pointer(mgr), NULL, &vc->s->grab_x_root, &vc->s->grab_y_root); #else @@ -1078,25 +1282,23 @@ static void gd_grab_pointer(VirtualConsole *vc) gdk_display_get_pointer(display, NULL, &vc->s->grab_x_root, &vc->s->grab_y_root, NULL); #endif + vc->s->ptr_owner = vc; + trace_gd_grab(vc->label, "ptr", true); } -static void gd_ungrab_pointer(VirtualConsole *vc) +static void gd_ungrab_pointer(GtkDisplayState *s) { + VirtualConsole *vc = s->ptr_owner; + + if (vc == NULL) { + return; + } + s->ptr_owner = NULL; + GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area); #if GTK_CHECK_VERSION(3, 0, 0) GdkDeviceManager *mgr = gdk_display_get_device_manager(display); - GList *devices = gdk_device_manager_list_devices(mgr, - GDK_DEVICE_TYPE_MASTER); - GList *tmp = devices; - while (tmp) { - GdkDevice *dev = tmp->data; - if (gdk_device_get_source(dev) == GDK_SOURCE_MOUSE) { - gdk_device_ungrab(dev, - GDK_CURRENT_TIME); - } - tmp = tmp->next; - } - g_list_free(devices); + gd_grab_devices(vc, false, GDK_SOURCE_MOUSE, 0, NULL); gdk_device_warp(gdk_device_manager_get_client_pointer(mgr), gtk_widget_get_screen(vc->gfx.drawing_area), vc->s->grab_x_root, vc->s->grab_y_root); @@ -1106,6 +1308,7 @@ static void gd_ungrab_pointer(VirtualConsole *vc) gtk_widget_get_screen(vc->gfx.drawing_area), vc->s->grab_x_root, vc->s->grab_y_root); #endif + trace_gd_grab(vc->label, "ptr", false); } static void gd_menu_grab_input(GtkMenuItem *item, void *opaque) @@ -1114,11 +1317,13 @@ static void gd_menu_grab_input(GtkMenuItem *item, void *opaque) VirtualConsole *vc = gd_vc_find_current(s); if (gd_is_grab_active(s)) { - gd_grab_keyboard(vc); + if (!gd_grab_on_hover(s)) { + gd_grab_keyboard(vc); + } gd_grab_pointer(vc); } else { - gd_ungrab_keyboard(vc); - gd_ungrab_pointer(vc); + gd_ungrab_keyboard(s); + gd_ungrab_pointer(s); } gd_update_caption(s); @@ -1136,13 +1341,23 @@ static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2, return; } +#ifdef VTE_RESIZE_HACK + vc = gd_vc_find_current(s); + if (vc && vc->type == GD_VC_VTE) { + gtk_widget_hide(vc->vte.terminal); + } +#endif vc = gd_vc_find_by_page(s, arg2); if (!vc) { return; } +#ifdef VTE_RESIZE_HACK + if (vc->type == GD_VC_VTE) { + gtk_widget_show(vc->vte.terminal); + } +#endif gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE); - on_vga = (vc->type == GD_VC_GFX); if (!on_vga) { gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), @@ -1153,6 +1368,7 @@ static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2, } gtk_widget_set_sensitive(s->grab_item, on_vga); + gd_update_windowsize(vc); gd_update_cursor(vc); } @@ -1162,10 +1378,11 @@ static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing, VirtualConsole *vc = opaque; GtkDisplayState *s = vc->s; - if (!gd_is_grab_active(s) && gd_grab_on_hover(s)) { + if (gd_grab_on_hover(s)) { + gd_ungrab_keyboard(s); gd_grab_keyboard(vc); + gd_update_caption(s); } - return TRUE; } @@ -1175,10 +1392,10 @@ static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing, VirtualConsole *vc = opaque; GtkDisplayState *s = vc->s; - if (!gd_is_grab_active(s) && gd_grab_on_hover(s)) { - gd_ungrab_keyboard(vc); + if (gd_grab_on_hover(s)) { + gd_ungrab_keyboard(s); + gd_update_caption(s); } - return TRUE; } @@ -1189,29 +1406,29 @@ static gboolean gd_focus_out_event(GtkWidget *widget, GtkDisplayState *s = vc->s; gtk_release_modifiers(s); - return TRUE; } /** Virtual Console Callbacks **/ static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc, - const char *label, int idx, - GSList *group, GtkWidget *view_menu) + int idx, GSList *group, GtkWidget *view_menu) { - char path[32]; - - snprintf(path, sizeof(path), "/View/VC%d", idx); - - vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, label); - group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item)); - gtk_menu_item_set_accel_path(GTK_MENU_ITEM(vc->menu_item), path); - gtk_accel_map_add_entry(path, GDK_KEY_1 + idx, HOTKEY_MODIFIERS); + vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label); + gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx, + HOTKEY_MODIFIERS, 0, + g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL)); +#if GTK_CHECK_VERSION(3, 8, 0) + gtk_accel_label_set_accel( + GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))), + GDK_KEY_1 + idx, HOTKEY_MODIFIERS); +#endif g_signal_connect(vc->menu_item, "activate", G_CALLBACK(gd_menu_switch_vc), s); gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item); + group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item)); return group; } @@ -1266,7 +1483,6 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, CharDriverState *chr, int idx, GSList *group, GtkWidget *view_menu) { - const char *label; char buffer[32]; GtkWidget *box; GtkWidget *scrollbar; @@ -1276,14 +1492,16 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, vc->vte.chr = chr; snprintf(buffer, sizeof(buffer), "vc%d", idx); - label = vc->vte.chr->label ? vc->vte.chr->label : buffer; - group = gd_vc_menu_init(s, vc, vc->vte.chr->label, idx, group, view_menu); + vc->label = g_strdup_printf("%s", vc->vte.chr->label + ? vc->vte.chr->label : buffer); + group = gd_vc_menu_init(s, vc, idx, group, view_menu); vc->vte.terminal = vte_terminal_new(); g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc); vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->vte.terminal), -1); - vte_terminal_set_size(VTE_TERMINAL(vc->vte.terminal), 80, 25); + vte_terminal_set_size(VTE_TERMINAL(vc->vte.terminal), + VC_TERM_X_MIN, VC_TERM_Y_MIN); #if VTE_CHECK_VERSION(0, 28, 0) && GTK_CHECK_VERSION(3, 0, 0) vadjustment = gtk_scrollable_get_vadjustment @@ -1313,7 +1531,7 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, vc->type = GD_VC_VTE; vc->tab_item = box; gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), vc->tab_item, - gtk_label_new(label)); + gtk_label_new(vc->label)); qemu_chr_be_generic_open(vc->vte.chr); if (vc->vte.chr->init) { @@ -1372,6 +1590,8 @@ static void gd_connect_signals(GtkDisplayState *s) { g_signal_connect(s->show_tabs_item, "activate", G_CALLBACK(gd_menu_show_tabs), s); + g_signal_connect(s->untabify_item, "activate", + G_CALLBACK(gd_menu_untabify), s); g_signal_connect(s->window, "delete-event", G_CALLBACK(gd_window_close), s); @@ -1400,13 +1620,13 @@ static void gd_connect_signals(GtkDisplayState *s) G_CALLBACK(gd_change_page), s); } -static GtkWidget *gd_create_menu_machine(GtkDisplayState *s, GtkAccelGroup *accel_group) +static GtkWidget *gd_create_menu_machine(GtkDisplayState *s) { GtkWidget *machine_menu; GtkWidget *separator; machine_menu = gtk_menu_new(); - gtk_menu_set_accel_group(GTK_MENU(machine_menu), accel_group); + gtk_menu_set_accel_group(GTK_MENU(machine_menu), s->accel_group); s->pause_item = gtk_check_menu_item_new_with_mnemonic(_("_Pause")); gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->pause_item); @@ -1446,13 +1666,13 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, QemuConsole *con, int idx, GSList *group, GtkWidget *view_menu) { - const char *label = "VGA"; - Error *local_err = NULL; Object *obj; - obj = object_property_get_link(OBJECT(con), "device", &local_err); + obj = object_property_get_link(OBJECT(con), "device", NULL); if (obj) { - label = object_get_typename(obj); + vc->label = g_strdup_printf("%s", object_get_typename(obj)); + } else { + vc->label = g_strdup_printf("VGA"); } vc->s = s; @@ -1469,16 +1689,15 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, GDK_LEAVE_NOTIFY_MASK | GDK_SCROLL_MASK | GDK_KEY_PRESS_MASK); - gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE); gtk_widget_set_can_focus(vc->gfx.drawing_area, TRUE); vc->type = GD_VC_GFX; vc->tab_item = vc->gfx.drawing_area; gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), - vc->tab_item, gtk_label_new(label)); + vc->tab_item, gtk_label_new(vc->label)); gd_connect_vc_gfx_signals(vc); - group = gd_vc_menu_init(s, vc, label, idx, group, view_menu); + group = gd_vc_menu_init(s, vc, idx, group, view_menu); vc->gfx.dcl.ops = &dcl_ops; vc->gfx.dcl.con = con; @@ -1487,7 +1706,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, return group; } -static GtkWidget *gd_create_menu_view(GtkDisplayState *s, GtkAccelGroup *accel_group) +static GtkWidget *gd_create_menu_view(GtkDisplayState *s) { GSList *group = NULL; GtkWidget *view_menu; @@ -1496,13 +1715,17 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, GtkAccelGroup *accel_g int vc; view_menu = gtk_menu_new(); - gtk_menu_set_accel_group(GTK_MENU(view_menu), accel_group); + gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group); s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen")); - gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->full_screen_item), - "/View/Full Screen"); - gtk_accel_map_add_entry("/View/Full Screen", GDK_KEY_f, - HOTKEY_MODIFIERS); + + gtk_accel_group_connect(s->accel_group, GDK_KEY_f, HOTKEY_MODIFIERS, 0, + g_cclosure_new_swap(G_CALLBACK(gd_accel_full_screen), s, NULL)); +#if GTK_CHECK_VERSION(3, 8, 0) + gtk_accel_label_set_accel( + GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->full_screen_item))), + GDK_KEY_f, HOTKEY_MODIFIERS); +#endif gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->full_screen_item); separator = gtk_separator_menu_item_new(); @@ -1570,16 +1793,17 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, GtkAccelGroup *accel_g s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs")); gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item); + s->untabify_item = gtk_menu_item_new_with_mnemonic(_("Detach Tab")); + gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->untabify_item); + return view_menu; } static void gd_create_menus(GtkDisplayState *s) { - GtkAccelGroup *accel_group; - - accel_group = gtk_accel_group_new(); - s->machine_menu = gd_create_menu_machine(s, accel_group); - s->view_menu = gd_create_menu_view(s, accel_group); + s->accel_group = gtk_accel_group_new(); + s->machine_menu = gd_create_menu_machine(s); + s->view_menu = gd_create_menu_view(s); s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine")); gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item), @@ -1590,9 +1814,40 @@ static void gd_create_menus(GtkDisplayState *s) gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu); gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item); - g_object_set_data(G_OBJECT(s->window), "accel_group", accel_group); - gtk_window_add_accel_group(GTK_WINDOW(s->window), accel_group); - s->accel_group = accel_group; + g_object_set_data(G_OBJECT(s->window), "accel_group", s->accel_group); + gtk_window_add_accel_group(GTK_WINDOW(s->window), s->accel_group); +} + +static void gd_set_keycode_type(GtkDisplayState *s) +{ +#ifdef GDK_WINDOWING_X11 + GdkDisplay *display = gtk_widget_get_display(s->window); + if (GDK_IS_X11_DISPLAY(display)) { + Display *x11_display = gdk_x11_display_get_xdisplay(display); + XkbDescPtr desc = XkbGetKeyboard(x11_display, XkbGBN_AllComponentsMask, + XkbUseCoreKbd); + char *keycodes = NULL; + + if (desc && desc->names) { + keycodes = XGetAtomName(x11_display, desc->names->keycodes); + } + if (keycodes == NULL) { + fprintf(stderr, "could not lookup keycode name\n"); + } else if (strstart(keycodes, "evdev", NULL)) { + s->has_evdev = true; + } else if (!strstart(keycodes, "xfree86", NULL)) { + fprintf(stderr, "unknown keycodes `%s', please report to " + "qemu-devel@nongnu.org\n", keycodes); + } + + if (desc) { + XkbFreeKeyboard(desc, XkbGBN_AllComponentsMask, True); + } + if (keycodes) { + XFree(keycodes); + } + } +#endif } void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover) @@ -1651,6 +1906,23 @@ void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover) gtk_widget_show_all(s->window); +#ifdef VTE_RESIZE_HACK + { + VirtualConsole *cur = gd_vc_find_current(s); + if (cur) { + int i; + + for (i = 0; i < s->nb_vcs; i++) { + VirtualConsole *vc = &s->vc[i]; + if (vc && vc->type == GD_VC_VTE && vc != cur) { + gtk_widget_hide(vc->vte.terminal); + } + } + gd_update_windowsize(cur); + } + } +#endif + if (full_screen) { gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item)); } @@ -1658,7 +1930,7 @@ void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover) gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item)); } - global_state = s; + gd_set_keycode_type(s); } void early_gtk_display_init(void)