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>
Marc-André Lureau committed
Jun 23, 2026 at 11:44 UTC
b1e8bf92386dc9868303963aec8b8624ce571f96
2 files changed
+85
-43
include/ui/gtk.h
+1
@@ -120,6 +120,7 @@ struct GtkDisplayState {
120
121
GPtrArray *vcs;
122
123
+ GtkWidget *vc_menu_separator;
124
GtkWidget *show_tabs_item;
125
GtkWidget *untabify_item;
126
GtkWidget *show_menubar_item;
ui/gtk.c
+84
-43
@@ -142,6 +142,7 @@ static void gd_grab_pointer(VirtualConsole *vc, const char *reason);
142
static void gd_ungrab_pointer(GtkDisplayState *s);
143
static void gd_grab_keyboard(VirtualConsole *vc, const char *reason);
144
static void gd_ungrab_keyboard(GtkDisplayState *s);
145
+static void gd_rebuild_vc_menu(GtkDisplayState *s);
146
147
/** Utility Functions **/
148
@@ -1488,7 +1489,6 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
1489
GtkDisplayState *s = vc->s;
1490
int page;
1491
1491
- gtk_widget_set_sensitive(vc->menu_item, true);
1492
g_object_ref(vc->tab_item);
1493
gtk_container_remove(GTK_CONTAINER(vc->window), vc->tab_item);
1494
page = gd_vc_notebook_pos(s, vc);
@@ -1508,6 +1508,8 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
1508
}
1509
#endif
1510
1511
+ gd_rebuild_vc_menu(s);
1512
+
1513
if (vc == gd_vc_find_by_menu(s)) {
1514
gtk_widget_grab_focus(vc->focus);
1515
}
@@ -1539,7 +1541,6 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
1541
FALSE);
1542
}
1543
if (!vc->window) {
1542
- gtk_widget_set_sensitive(vc->menu_item, false);
1544
vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1545
#if defined(CONFIG_OPENGL)
1546
if (vc->gfx.esurface) {
@@ -1566,6 +1567,7 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
1567
gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
1568
}
1569
1570
+ gd_rebuild_vc_menu(s);
1571
gd_update_geometry_hints(vc);
1572
gd_update_caption(s);
1573
}
@@ -1906,22 +1908,73 @@ static gboolean gd_configure(GtkWidget *widget,
1908
1909
/** Virtual Console Callbacks **/
1910
1909
-static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
1910
- int idx, GSList *group, GtkWidget *view_menu)
1911
+static void gd_rebuild_vc_menu(GtkDisplayState *s)
1912
{
1912
- vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label);
1913
- gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx,
1914
- HOTKEY_MODIFIERS, 0,
1915
- g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL));
1916
- gtk_accel_label_set_accel(
1917
- GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
1918
- GDK_KEY_1 + idx, HOTKEY_MODIFIERS);
1913
+ GSList *group = NULL;
1914
+ VirtualConsole *vc;
1915
+ GList *children;
1916
+ gint insert_pos;
1917
+ int shortcut_idx = 0;
1918
+ guint i;
1919
+
1920
+ for (i = 0; i < s->vcs->len; i++) {
1921
+ vc = g_ptr_array_index(s->vcs, i);
1922
+ if (vc->menu_item) {
1923
+ gtk_widget_destroy(vc->menu_item);
1924
+ vc->menu_item = NULL;
1925
+ }
1926
+ }
1927
+
1928
+ for (i = 0; i < 9; i++) {
1929
+ gtk_accel_group_disconnect_key(s->accel_group,
1930
+ GDK_KEY_1 + i, HOTKEY_MODIFIERS);
1931
+ }
1932
+
1933
+ /* find insertion position (just before vc_menu_separator) */
1934
+ children = gtk_container_get_children(GTK_CONTAINER(s->view_menu));
1935
+ insert_pos = g_list_index(children, s->vc_menu_separator);
1936
+ g_list_free(children);
1937
+
1938
+ /* create new menu items for each console */
1939
+ for (i = 0; i < s->vcs->len; i++) {
1940
+ vc = g_ptr_array_index(s->vcs, i);
1941
1920
- g_signal_connect(vc->menu_item, "activate",
1921
- G_CALLBACK(gd_menu_switch_vc), s);
1922
- gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item);
1942
+ vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group,
1943
+ vc->label);
1944
+ group = gtk_radio_menu_item_get_group(
1945
+ GTK_RADIO_MENU_ITEM(vc->menu_item));
1946
+
1947
+ if (vc->window) {
1948
+ gtk_widget_set_sensitive(vc->menu_item, false);
1949
+ } else if (shortcut_idx < 9) {
1950
+ guint key = GDK_KEY_1 + shortcut_idx;
1951
+ gtk_accel_group_connect(s->accel_group, key,
1952
+ HOTKEY_MODIFIERS, 0,
1953
+ g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc),
1954
+ vc, NULL));
1955
+ gtk_accel_label_set_accel(
1956
+ GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
1957
+ key, HOTKEY_MODIFIERS);
1958
+ shortcut_idx++;
1959
+ }
1960
1924
- return gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
1961
+ g_signal_connect(vc->menu_item, "activate",
1962
+ G_CALLBACK(gd_menu_switch_vc), s);
1963
+ gtk_menu_shell_insert(GTK_MENU_SHELL(s->view_menu),
1964
+ vc->menu_item, insert_pos + i);
1965
+ gtk_widget_show(vc->menu_item);
1966
+ }
1967
+
1968
+ /* sync active menu item with current notebook page */
1969
+ vc = gd_vc_find_current(s);
1970
+ if (vc && vc->menu_item) {
1971
+ g_signal_handlers_block_by_func(vc->menu_item,
1972
+ gd_menu_switch_vc, s);
1973
+ gtk_check_menu_item_set_active(
1974
+ GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
1975
+ g_signal_handlers_unblock_by_func(vc->menu_item,
1976
+ gd_menu_switch_vc, s);
1977
+ }
1978
}
1979
1980
#if defined(CONFIG_VTE)
@@ -2064,9 +2117,8 @@ static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
2117
return TRUE;
2118
}
2119
2067
-static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
2068
- Chardev *chr, int idx,
2069
- GSList *group, GtkWidget *view_menu)
2120
+static void gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
2121
+ Chardev *chr, int idx)
2122
{
2123
char buffer[32];
2124
GtkWidget *box;
@@ -2082,7 +2134,6 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
2134
2135
snprintf(buffer, sizeof(buffer), "vc%d", idx);
2136
vc->label = g_strdup(vc->vte.chr->label ? : buffer);
2085
- group = gd_vc_menu_init(s, vc, idx, group, view_menu);
2137
2138
vc->vte.terminal = vte_terminal_new();
2139
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,
2179
gtk_label_new(vc->label));
2180
2181
qemu_chr_be_event(vc->vte.chr, CHR_EVENT_OPENED);
2131
-
2132
- return group;
2182
}
2183
2135
-static void gd_vcs_init(GtkDisplayState *s, GSList *group,
2136
- GtkWidget *view_menu)
2184
+static void gd_vcs_init(GtkDisplayState *s)
2185
{
2186
int i;
2187
2188
for (i = 0; i < nb_vcs; i++) {
2189
VirtualConsole *vc = g_new0(VirtualConsole, 1);
2190
g_ptr_array_add(s->vcs, vc);
2143
- group = gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1,
2144
- group, view_menu);
2191
+ gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1);
2192
}
2193
}
2194
#endif /* CONFIG_VTE */
@@ -2285,12 +2332,12 @@ static bool gd_scale_valid(double scale)
2332
return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX;
2333
}
2334
2288
-static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
2289
- QemuConsole *con, int idx,
2290
- GSList *group, GtkWidget *view_menu)
2335
+static void add_gfx_console(GtkDisplayState *s, QemuConsole *con)
2336
{
2337
+ VirtualConsole *vc = g_new0(VirtualConsole, 1);
2338
const DisplayChangeListenerOps *ops = &dcl_ops;
2339
2340
+ g_ptr_array_add(s->vcs, vc);
2341
vc->label = qemu_console_get_label(con);
2342
vc->s = s;
2343
vc->gfx.preferred_scale = 1.0;
@@ -2367,14 +2414,10 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
2414
qemu_console_register_listener(con, &vc->gfx.dcl, ops);
2415
2416
gd_connect_vc_gfx_signals(vc);
2370
- group = gd_vc_menu_init(s, vc, idx, group, view_menu);
2371
-
2372
- return group;
2417
}
2418
2375
-static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
2419
+static void gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
2420
{
2377
- GSList *group = NULL;
2421
GtkWidget *view_menu;
2422
GtkWidget *separator;
2423
QemuConsole *con;
@@ -2382,6 +2425,7 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
2425
int vc, i;
2426
2427
view_menu = gtk_menu_new();
2428
+ s->view_menu = view_menu;
2429
gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
2430
2431
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)
2489
2490
/* gfx */
2491
for (vc = 0;; vc++) {
2448
- VirtualConsole *v;
2492
con = qemu_console_lookup_by_index(vc);
2493
if (!con) {
2494
break;
2495
}
2453
- v = g_new0(VirtualConsole, 1);
2454
- g_ptr_array_add(s->vcs, v);
2455
- group = gd_vc_gfx_init(s, v, con, vc, group, view_menu);
2496
+ add_gfx_console(s, con);
2497
if (qemu_console_ui_info_supported(con)) {
2498
zoom_to_fit = true;
2499
}
@@ -2478,11 +2519,13 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
2519
2520
#if defined(CONFIG_VTE)
2521
/* vte */
2481
- gd_vcs_init(s, group, view_menu);
2522
+ gd_vcs_init(s);
2523
#endif
2524
2484
- separator = gtk_separator_menu_item_new();
2485
- gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
2525
+ s->vc_menu_separator = gtk_separator_menu_item_new();
2526
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->vc_menu_separator);
2527
+
2528
+ gd_rebuild_vc_menu(s);
2529
2530
s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
2531
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)
2544
GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->show_menubar_item))),
2545
GDK_KEY_m, HOTKEY_MODIFIERS);
2546
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_menubar_item);
2504
-
2505
- return view_menu;
2547
}
2548
2549
static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
@@ -2511,7 +2552,7 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
2552
2553
s->accel_group = gtk_accel_group_new();
2554
s->machine_menu = gd_create_menu_machine(s);
2514
- s->view_menu = gd_create_menu_view(s, opts);
2555
+ gd_create_menu_view(s, opts);
2556
2557
s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
2558
gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),