Commit b1e8bf9238 for qemu.org
commit b1e8bf92386dc9868303963aec8b8624ce571f96
Author: Marc-André Lureau <marcandre.lureau@redhat.com>
Date: Tue Jun 23 11:44:44 2026 +0400
ui/gtk: centralize console menu and shortcut management
Replace the per-console gd_vc_menu_init() with gd_rebuild_vc_menu()
that tears down and rebuilds all console radio menu items and
Ctrl+Alt+N accelerators at once. This is called from initialization
and whenever consoles are detached or reattached.
Shortcuts now skip detached (windowed) consoles, so they always map
to reachable tabs. Rename gd_vc_gfx_init() to add_gfx_console()
and simplify the init function signatures now that menu creation is
decoupled.
Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260623-b4-ui-v4-29-4656aec3398d@redhat.com>
diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index fb60cbfda5..53d0e447a2 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -120,6 +120,7 @@ struct GtkDisplayState {
GPtrArray *vcs;
+ GtkWidget *vc_menu_separator;
GtkWidget *show_tabs_item;
GtkWidget *untabify_item;
GtkWidget *show_menubar_item;
diff --git a/ui/gtk.c b/ui/gtk.c
index 741f7bfe37..e7a9156f20 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -142,6 +142,7 @@ static void gd_grab_pointer(VirtualConsole *vc, const char *reason);
static void gd_ungrab_pointer(GtkDisplayState *s);
static void gd_grab_keyboard(VirtualConsole *vc, const char *reason);
static void gd_ungrab_keyboard(GtkDisplayState *s);
+static void gd_rebuild_vc_menu(GtkDisplayState *s);
/** Utility Functions **/
@@ -1488,7 +1489,6 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
GtkDisplayState *s = vc->s;
int page;
- gtk_widget_set_sensitive(vc->menu_item, true);
g_object_ref(vc->tab_item);
gtk_container_remove(GTK_CONTAINER(vc->window), vc->tab_item);
page = gd_vc_notebook_pos(s, vc);
@@ -1508,6 +1508,8 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
}
#endif
+ gd_rebuild_vc_menu(s);
+
if (vc == gd_vc_find_by_menu(s)) {
gtk_widget_grab_focus(vc->focus);
}
@@ -1539,7 +1541,6 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
FALSE);
}
if (!vc->window) {
- gtk_widget_set_sensitive(vc->menu_item, false);
vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
#if defined(CONFIG_OPENGL)
if (vc->gfx.esurface) {
@@ -1566,6 +1567,7 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
}
+ gd_rebuild_vc_menu(s);
gd_update_geometry_hints(vc);
gd_update_caption(s);
}
@@ -1906,22 +1908,73 @@ static gboolean gd_configure(GtkWidget *widget,
/** Virtual Console Callbacks **/
-static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
- int idx, GSList *group, GtkWidget *view_menu)
+static void gd_rebuild_vc_menu(GtkDisplayState *s)
{
- 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));
- gtk_accel_label_set_accel(
- GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
- GDK_KEY_1 + idx, HOTKEY_MODIFIERS);
+ GSList *group = NULL;
+ VirtualConsole *vc;
+ GList *children;
+ gint insert_pos;
+ int shortcut_idx = 0;
+ guint i;
+
+ for (i = 0; i < s->vcs->len; i++) {
+ vc = g_ptr_array_index(s->vcs, i);
+ if (vc->menu_item) {
+ gtk_widget_destroy(vc->menu_item);
+ vc->menu_item = NULL;
+ }
+ }
+
+ for (i = 0; i < 9; i++) {
+ gtk_accel_group_disconnect_key(s->accel_group,
+ GDK_KEY_1 + i, HOTKEY_MODIFIERS);
+ }
+
+ /* find insertion position (just before vc_menu_separator) */
+ children = gtk_container_get_children(GTK_CONTAINER(s->view_menu));
+ insert_pos = g_list_index(children, s->vc_menu_separator);
+ g_list_free(children);
+
+ /* create new menu items for each console */
+ for (i = 0; i < s->vcs->len; i++) {
+ vc = g_ptr_array_index(s->vcs, i);
- 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);
+ vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group,
+ vc->label);
+ group = gtk_radio_menu_item_get_group(
+ GTK_RADIO_MENU_ITEM(vc->menu_item));
+
+ if (vc->window) {
+ gtk_widget_set_sensitive(vc->menu_item, false);
+ } else if (shortcut_idx < 9) {
+ guint key = GDK_KEY_1 + shortcut_idx;
+ gtk_accel_group_connect(s->accel_group, key,
+ HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc),
+ vc, NULL));
+ gtk_accel_label_set_accel(
+ GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
+ key, HOTKEY_MODIFIERS);
+ shortcut_idx++;
+ }
- return gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
+ g_signal_connect(vc->menu_item, "activate",
+ G_CALLBACK(gd_menu_switch_vc), s);
+ gtk_menu_shell_insert(GTK_MENU_SHELL(s->view_menu),
+ vc->menu_item, insert_pos + i);
+ gtk_widget_show(vc->menu_item);
+ }
+
+ /* sync active menu item with current notebook page */
+ vc = gd_vc_find_current(s);
+ if (vc && vc->menu_item) {
+ g_signal_handlers_block_by_func(vc->menu_item,
+ gd_menu_switch_vc, s);
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
+ g_signal_handlers_unblock_by_func(vc->menu_item,
+ gd_menu_switch_vc, s);
+ }
}
#if defined(CONFIG_VTE)
@@ -2064,9 +2117,8 @@ static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
return TRUE;
}
-static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
- Chardev *chr, int idx,
- GSList *group, GtkWidget *view_menu)
+static void gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
+ Chardev *chr, int idx)
{
char buffer[32];
GtkWidget *box;
@@ -2082,7 +2134,6 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
snprintf(buffer, sizeof(buffer), "vc%d", idx);
vc->label = g_strdup(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);
@@ -2128,20 +2179,16 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
gtk_label_new(vc->label));
qemu_chr_be_event(vc->vte.chr, CHR_EVENT_OPENED);
-
- return group;
}
-static void gd_vcs_init(GtkDisplayState *s, GSList *group,
- GtkWidget *view_menu)
+static void gd_vcs_init(GtkDisplayState *s)
{
int i;
for (i = 0; i < nb_vcs; i++) {
VirtualConsole *vc = g_new0(VirtualConsole, 1);
g_ptr_array_add(s->vcs, vc);
- group = gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1,
- group, view_menu);
+ gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1);
}
}
#endif /* CONFIG_VTE */
@@ -2285,12 +2332,12 @@ static bool gd_scale_valid(double scale)
return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX;
}
-static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
- QemuConsole *con, int idx,
- GSList *group, GtkWidget *view_menu)
+static void add_gfx_console(GtkDisplayState *s, QemuConsole *con)
{
+ VirtualConsole *vc = g_new0(VirtualConsole, 1);
const DisplayChangeListenerOps *ops = &dcl_ops;
+ g_ptr_array_add(s->vcs, vc);
vc->label = qemu_console_get_label(con);
vc->s = s;
vc->gfx.preferred_scale = 1.0;
@@ -2367,14 +2414,10 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
qemu_console_register_listener(con, &vc->gfx.dcl, ops);
gd_connect_vc_gfx_signals(vc);
- group = gd_vc_menu_init(s, vc, idx, group, view_menu);
-
- return group;
}
-static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
+static void gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
{
- GSList *group = NULL;
GtkWidget *view_menu;
GtkWidget *separator;
QemuConsole *con;
@@ -2382,6 +2425,7 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
int vc, i;
view_menu = gtk_menu_new();
+ s->view_menu = view_menu;
gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen"));
@@ -2445,14 +2489,11 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
/* gfx */
for (vc = 0;; vc++) {
- VirtualConsole *v;
con = qemu_console_lookup_by_index(vc);
if (!con) {
break;
}
- v = g_new0(VirtualConsole, 1);
- g_ptr_array_add(s->vcs, v);
- group = gd_vc_gfx_init(s, v, con, vc, group, view_menu);
+ add_gfx_console(s, con);
if (qemu_console_ui_info_supported(con)) {
zoom_to_fit = true;
}
@@ -2478,11 +2519,13 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
#if defined(CONFIG_VTE)
/* vte */
- gd_vcs_init(s, group, view_menu);
+ gd_vcs_init(s);
#endif
- separator = gtk_separator_menu_item_new();
- gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
+ s->vc_menu_separator = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->vc_menu_separator);
+
+ gd_rebuild_vc_menu(s);
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);
@@ -2501,8 +2544,6 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->show_menubar_item))),
GDK_KEY_m, HOTKEY_MODIFIERS);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_menubar_item);
-
- return view_menu;
}
static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
@@ -2511,7 +2552,7 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
s->accel_group = gtk_accel_group_new();
s->machine_menu = gd_create_menu_machine(s);
- s->view_menu = gd_create_menu_view(s, opts);
+ gd_create_menu_view(s, opts);
s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),