@samitouri / QOSamiQemu / commits / b1e8bf9238

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),