ui/gtk.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 4 deletions(-)
From: Dongwon Kim <dongwon.kim@intel.com>
Currently, the fullscreen toggle hotkey (Ctrl+Alt+f by default) and zoom
hotkeys (Ctrl+Alt++, Ctrl+Alt+-, Ctrl+Alt+0) only function for the main
QEMU window. When a VC is detached (untabified), it loses these capabilities.
Implement toggle helpers, gd_win_full_screen and zoom helpers
(gd_win_zoom_in, gd_win_zoom_out, gd_win_zoom_fixed) and connect them
to the detached window's accelerator group in gd_menu_untabify.
Also remove a leftover debug fprintf in gd_win_grab.
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Dongwon Kim <dongwon.kim@intel.com>
---
ui/gtk.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 98 insertions(+), 4 deletions(-)
diff --git a/ui/gtk.c b/ui/gtk.c
index a723c8215d..632e302ed7 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -1547,7 +1547,6 @@ 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 {
@@ -1556,6 +1555,86 @@ static gboolean gd_win_grab(void *opaque)
return TRUE;
}
+static gboolean gd_win_full_screen(void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ GdkWindow *window;
+ GdkWindowState state;
+
+ if (!vc->window || !gtk_widget_get_realized(vc->window)) {
+ return TRUE;
+ }
+
+ window = gtk_widget_get_window(vc->window);
+ if (!window) {
+ return TRUE;
+ }
+
+ state = gdk_window_get_state(window);
+
+ if (state & GDK_WINDOW_STATE_FULLSCREEN) {
+ gtk_window_unfullscreen(GTK_WINDOW(vc->window));
+
+ if (vc->type == GD_VC_GFX) {
+ vc->gfx.scale_x = vc->gfx.preferred_scale;
+ vc->gfx.scale_y = vc->gfx.preferred_scale;
+ gd_update_windowsize(vc);
+ }
+ } else {
+ if (vc->type == GD_VC_GFX) {
+ gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
+ }
+ gtk_window_fullscreen(GTK_WINDOW(vc->window));
+ }
+
+ gd_update_cursor(vc);
+ return TRUE;
+}
+
+static gboolean gd_win_zoom_in(void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
+ FALSE);
+
+ vc->gfx.scale_x += VC_SCALE_STEP;
+ vc->gfx.scale_y += VC_SCALE_STEP;
+
+ gd_update_windowsize(vc);
+ return TRUE;
+}
+
+static gboolean gd_win_zoom_out(void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
+ FALSE);
+
+ vc->gfx.scale_x -= VC_SCALE_STEP;
+ vc->gfx.scale_y -= VC_SCALE_STEP;
+
+ 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);
+ return TRUE;
+}
+
+static gboolean gd_win_zoom_fixed(void *opaque)
+{
+ VirtualConsole *vc = opaque;
+
+ vc->gfx.scale_x = vc->gfx.preferred_scale;
+ vc->gfx.scale_y = vc->gfx.preferred_scale;
+
+ gd_update_windowsize(vc);
+ return TRUE;
+}
+
static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
{
GtkDisplayState *s = opaque;
@@ -1590,9 +1669,24 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
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);
+ gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_win_grab),
+ vc, NULL));
+ gtk_accel_group_connect(ag, GDK_KEY_f, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_win_full_screen),
+ vc, NULL));
+ gtk_accel_group_connect(ag, GDK_KEY_plus, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_in),
+ vc, NULL));
+ gtk_accel_group_connect(ag, GDK_KEY_equal, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_in),
+ vc, NULL));
+ gtk_accel_group_connect(ag, GDK_KEY_minus, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_out),
+ vc, NULL));
+ gtk_accel_group_connect(ag, GDK_KEY_0, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_fixed),
+ vc, NULL));
}
gd_rebuild_vc_menu(s);
--
2.43.0
Hi
On Tue, Sep 15, 2026 at 11:02 PM <dongwon.kim@intel.com> wrote:
>
> From: Dongwon Kim <dongwon.kim@intel.com>
>
> Currently, the fullscreen toggle hotkey (Ctrl+Alt+f by default) and zoom
> hotkeys (Ctrl+Alt++, Ctrl+Alt+-, Ctrl+Alt+0) only function for the main
> QEMU window. When a VC is detached (untabified), it loses these capabilities.
>
> Implement toggle helpers, gd_win_full_screen and zoom helpers
> (gd_win_zoom_in, gd_win_zoom_out, gd_win_zoom_fixed) and connect them
> to the detached window's accelerator group in gd_menu_untabify.
>
> Also remove a leftover debug fprintf in gd_win_grab.
>
> Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
> Signed-off-by: Dongwon Kim <dongwon.kim@intel.com>
> ---
> ui/gtk.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 98 insertions(+), 4 deletions(-)
>
> diff --git a/ui/gtk.c b/ui/gtk.c
> index a723c8215d..632e302ed7 100644
> --- a/ui/gtk.c
> +++ b/ui/gtk.c
> @@ -1547,7 +1547,6 @@ 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 {
> @@ -1556,6 +1555,86 @@ static gboolean gd_win_grab(void *opaque)
> return TRUE;
> }
>
> +static gboolean gd_win_full_screen(void *opaque)
> +{
> + VirtualConsole *vc = opaque;
> + GdkWindow *window;
> + GdkWindowState state;
> +
> + if (!vc->window || !gtk_widget_get_realized(vc->window)) {
> + return TRUE;
> + }
> +
> + window = gtk_widget_get_window(vc->window);
> + if (!window) {
> + return TRUE;
> + }
> +
> + state = gdk_window_get_state(window);
> +
> + if (state & GDK_WINDOW_STATE_FULLSCREEN) {
> + gtk_window_unfullscreen(GTK_WINDOW(vc->window));
> +
> + if (vc->type == GD_VC_GFX) {
> + vc->gfx.scale_x = vc->gfx.preferred_scale;
> + vc->gfx.scale_y = vc->gfx.preferred_scale;
> + gd_update_windowsize(vc);
> + }
> + } else {
> + if (vc->type == GD_VC_GFX) {
> + gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
> + }
> + gtk_window_fullscreen(GTK_WINDOW(vc->window));
> + }
> +
> + gd_update_cursor(vc);
> + return TRUE;
> +}
> +
> +static gboolean gd_win_zoom_in(void *opaque)
> +{
> + VirtualConsole *vc = opaque;
> + GtkDisplayState *s = vc->s;
> +
> + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
> + FALSE);
> +
> + vc->gfx.scale_x += VC_SCALE_STEP;
> + vc->gfx.scale_y += VC_SCALE_STEP;
> +
> + gd_update_windowsize(vc);
> + return TRUE;
> +}
> +
> +static gboolean gd_win_zoom_out(void *opaque)
> +{
> + VirtualConsole *vc = opaque;
> + GtkDisplayState *s = vc->s;
> +
> + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
> + FALSE);
> +
> + vc->gfx.scale_x -= VC_SCALE_STEP;
> + vc->gfx.scale_y -= VC_SCALE_STEP;
> +
> + 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);
> + return TRUE;
> +}
> +
> +static gboolean gd_win_zoom_fixed(void *opaque)
> +{
> + VirtualConsole *vc = opaque;
> +
> + vc->gfx.scale_x = vc->gfx.preferred_scale;
> + vc->gfx.scale_y = vc->gfx.preferred_scale;
> +
> + gd_update_windowsize(vc);
> + return TRUE;
> +}
> +
This duplicates existing code. Can you refactor it so we avoid logic
duplication?
btw, it would make sense to send a patch series now that you have 2
ui/gtk patches and more to come.
thanks
> static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
> {
> GtkDisplayState *s = opaque;
> @@ -1590,9 +1669,24 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
> 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);
> + gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0,
> + g_cclosure_new_swap(G_CALLBACK(gd_win_grab),
> + vc, NULL));
> + gtk_accel_group_connect(ag, GDK_KEY_f, HOTKEY_MODIFIERS, 0,
> + g_cclosure_new_swap(G_CALLBACK(gd_win_full_screen),
> + vc, NULL));
> + gtk_accel_group_connect(ag, GDK_KEY_plus, HOTKEY_MODIFIERS, 0,
> + g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_in),
> + vc, NULL));
> + gtk_accel_group_connect(ag, GDK_KEY_equal, HOTKEY_MODIFIERS, 0,
> + g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_in),
> + vc, NULL));
> + gtk_accel_group_connect(ag, GDK_KEY_minus, HOTKEY_MODIFIERS, 0,
> + g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_out),
> + vc, NULL));
> + gtk_accel_group_connect(ag, GDK_KEY_0, HOTKEY_MODIFIERS, 0,
> + g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_fixed),
> + vc, NULL));
> }
>
> gd_rebuild_vc_menu(s);
> --
> 2.43.0
>
>
--
Marc-André Lureau
Hi Marc-André,
> Subject: Re: [PATCH] ui/gtk: Enable fullscreen and zoom hotkeys for detached
> windows
>
> Hi
>
> On Tue, Sep 15, 2026 at 11:02 PM <dongwon.kim@intel.com> wrote:
> >
> > From: Dongwon Kim <dongwon.kim@intel.com>
> >
> > Currently, the fullscreen toggle hotkey (Ctrl+Alt+f by default) and
> > zoom hotkeys (Ctrl+Alt++, Ctrl+Alt+-, Ctrl+Alt+0) only function for
> > the main QEMU window. When a VC is detached (untabified), it loses these
> capabilities.
> >
> > Implement toggle helpers, gd_win_full_screen and zoom helpers
> > (gd_win_zoom_in, gd_win_zoom_out, gd_win_zoom_fixed) and connect
> them
> > to the detached window's accelerator group in gd_menu_untabify.
> >
> > Also remove a leftover debug fprintf in gd_win_grab.
> >
> > Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
> > Signed-off-by: Dongwon Kim <dongwon.kim@intel.com>
> > ---
> > ui/gtk.c | 102
> > ++++++++++++++++++++++++++++++++++++++++++++++++++++---
> > 1 file changed, 98 insertions(+), 4 deletions(-)
> >
> > diff --git a/ui/gtk.c b/ui/gtk.c
> > index a723c8215d..632e302ed7 100644
> > --- a/ui/gtk.c
> > +++ b/ui/gtk.c
> > @@ -1547,7 +1547,6 @@ 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 {
> > @@ -1556,6 +1555,86 @@ static gboolean gd_win_grab(void *opaque)
> > return TRUE;
> > }
> >
> > +static gboolean gd_win_full_screen(void *opaque) {
> > + VirtualConsole *vc = opaque;
> > + GdkWindow *window;
> > + GdkWindowState state;
> > +
> > + if (!vc->window || !gtk_widget_get_realized(vc->window)) {
> > + return TRUE;
> > + }
> > +
> > + window = gtk_widget_get_window(vc->window);
> > + if (!window) {
> > + return TRUE;
> > + }
> > +
> > + state = gdk_window_get_state(window);
> > +
> > + if (state & GDK_WINDOW_STATE_FULLSCREEN) {
> > + gtk_window_unfullscreen(GTK_WINDOW(vc->window));
> > +
> > + if (vc->type == GD_VC_GFX) {
> > + vc->gfx.scale_x = vc->gfx.preferred_scale;
> > + vc->gfx.scale_y = vc->gfx.preferred_scale;
> > + gd_update_windowsize(vc);
> > + }
> > + } else {
> > + if (vc->type == GD_VC_GFX) {
> > + gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
> > + }
> > + gtk_window_fullscreen(GTK_WINDOW(vc->window));
> > + }
> > +
> > + gd_update_cursor(vc);
> > + return TRUE;
> > +}
> > +
> > +static gboolean gd_win_zoom_in(void *opaque) {
> > + VirtualConsole *vc = opaque;
> > + GtkDisplayState *s = vc->s;
> > +
> > + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s-
> >zoom_fit_item),
> > + FALSE);
> > +
> > + vc->gfx.scale_x += VC_SCALE_STEP;
> > + vc->gfx.scale_y += VC_SCALE_STEP;
> > +
> > + gd_update_windowsize(vc);
> > + return TRUE;
> > +}
> > +
> > +static gboolean gd_win_zoom_out(void *opaque) {
> > + VirtualConsole *vc = opaque;
> > + GtkDisplayState *s = vc->s;
> > +
> > + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s-
> >zoom_fit_item),
> > + FALSE);
> > +
> > + vc->gfx.scale_x -= VC_SCALE_STEP;
> > + vc->gfx.scale_y -= VC_SCALE_STEP;
> > +
> > + 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);
> > + return TRUE;
> > +}
> > +
> > +static gboolean gd_win_zoom_fixed(void *opaque) {
> > + VirtualConsole *vc = opaque;
> > +
> > + vc->gfx.scale_x = vc->gfx.preferred_scale;
> > + vc->gfx.scale_y = vc->gfx.preferred_scale;
> > +
> > + gd_update_windowsize(vc);
> > + return TRUE;
> > +}
> > +
>
> This duplicates existing code. Can you refactor it so we avoid logic
> duplication?
Yes, we can do that. I will fix it in v2.
>
> btw, it would make sense to send a patch series now that you have 2 ui/gtk
> patches and more to come.
I do have a couple of other patches that I am working on in gtk-ui but it would take
more time to finalize those. Can we complete these two patches first? I will submit
a series from next time.
>
> thanks
>
> > static void gd_menu_untabify(GtkMenuItem *item, void *opaque) {
> > GtkDisplayState *s = opaque;
> > @@ -1590,9 +1669,24 @@ static void gd_menu_untabify(GtkMenuItem
> *item, void *opaque)
> > 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);
> > + gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0,
> > + g_cclosure_new_swap(G_CALLBACK(gd_win_grab),
> > + vc, NULL));
> > + gtk_accel_group_connect(ag, GDK_KEY_f, HOTKEY_MODIFIERS, 0,
> > + g_cclosure_new_swap(G_CALLBACK(gd_win_full_screen),
> > + vc, NULL));
> > + gtk_accel_group_connect(ag, GDK_KEY_plus, HOTKEY_MODIFIERS,
> 0,
> > + g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_in),
> > + vc, NULL));
> > + gtk_accel_group_connect(ag, GDK_KEY_equal, HOTKEY_MODIFIERS,
> 0,
> > + g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_in),
> > + vc, NULL));
> > + gtk_accel_group_connect(ag, GDK_KEY_minus,
> HOTKEY_MODIFIERS, 0,
> > + g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_out),
> > + vc, NULL));
> > + gtk_accel_group_connect(ag, GDK_KEY_0, HOTKEY_MODIFIERS, 0,
> > + g_cclosure_new_swap(G_CALLBACK(gd_win_zoom_fixed),
> > + vc, NULL));
> > }
> >
> > gd_rebuild_vc_menu(s);
> > --
> > 2.43.0
> >
> >
>
>
> --
> Marc-André Lureau
From: Dongwon Kim <dongwon.kim@intel.com>
The "Grab Input" shortcut key currently fails to trigger when the
window is in fullscreen mode because the menubar is hidden, which
disables GtkAccelMap-based shortcuts associated with it.
To ensure the accelerator remains active in fullscreen, connect it
directly to s->accel_group and explicitly set the hotkey display on
the menu item label.
v2: gd_accel_grab_input returns TRUE
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Dongwon Kim <dongwon.kim@intel.com>
---
ui/gtk.c | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/ui/gtk.c b/ui/gtk.c
index 632e302ed7..c07f275537 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -1932,6 +1932,14 @@ static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
gd_update_cursor(vc);
}
+static gboolean gd_accel_grab_input(void *opaque)
+{
+ GtkDisplayState *s = opaque;
+
+ gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_item));
+ return TRUE;
+}
+
static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
gpointer data)
{
@@ -2611,6 +2619,7 @@ static void gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
{
GtkWidget *view_menu;
GtkWidget *separator;
+ GtkWidget *child;
QemuConsole *con;
bool zoom_to_fit = false;
int vc, i;
@@ -2669,12 +2678,15 @@ static void gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
gtk_menu_shell_append(GTK_MENU_SHELL(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),
- "<QEMU>/View/Grab Input");
- gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g,
- HOTKEY_MODIFIERS);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_item);
-
+ gtk_accel_group_connect(s->accel_group, GDK_KEY_g, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_accel_grab_input),
+ s, NULL));
+ child = gtk_bin_get_child(GTK_BIN(s->grab_item));
+ if (GTK_IS_ACCEL_LABEL(child)) {
+ gtk_accel_label_set_accel(GTK_ACCEL_LABEL(child),
+ GDK_KEY_g, HOTKEY_MODIFIERS);
+ }
separator = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
--
2.43.0
© 2016 - 2026 Red Hat, Inc.