From 3b572b88b6ee1b7f2c4fe863892a73c1df922c45 Mon Sep 17 00:00:00 2001 From: sedax90 <5001801+sedax90@users.noreply.github.com> Date: Sat, 31 Oct 2026 08:43:36 +0100 Subject: [PATCH 1/4] fix(linux-workspace): offer New Connection in the primary menu A window showing sessions had no way to add a connection: New Connection lived only on the picker and the welcome view. The primary menu now leads with it, as the File menu does on macOS. --- .../app/src/ui/workspace/tests/shell.rs | 21 ++++++++++++++++++- linux/data/resources/gtk/menus.ui | 6 ++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/linux/crates/app/src/ui/workspace/tests/shell.rs b/linux/crates/app/src/ui/workspace/tests/shell.rs index 8197dfbeb4..6783f11afd 100644 --- a/linux/crates/app/src/ui/workspace/tests/shell.rs +++ b/linux/crates/app/src/ui/workspace/tests/shell.rs @@ -12,7 +12,7 @@ use libadwaita::prelude::*; use super::{served_table, shell_of}; use crate::test_support::{descendants, wait_until}; use crate::ui::SessionWindowInput; -use crate::ui::session_window::tests::{Script, TestWindow}; +use crate::ui::session_window::tests::{Script, TestWindow, WAIT}; use crate::ui::workspace::{WorkspaceShell, WorkspaceTab}; fn overview_new_tab_button(overview: &adw::TabOverview) -> gtk::Button { @@ -418,3 +418,22 @@ fn the_overview_opens_a_tab_in_a_session_that_came_forward_from_its_own_input() "the new tab went to a session behind" ); } + +#[gtk4::test] +fn the_primary_menu_opens_a_new_connection_from_a_session() { + let test = TestWindow::connected("PrimaryMenuNewConnection"); + let menu = test.app.menu_by_id("primary_menu").expect("the primary menu"); + let first = menu.item_link(0, "section").expect("a first section"); + let action = first + .item_attribute_value(0, "action", None) + .and_then(|value| value.get::()); + assert_eq!( + action.as_deref(), + Some("win.new-connection"), + "New Connection does not lead the menu" + ); + + WidgetExt::activate_action(&test.window, "win.new-connection", None).expect("win.new-connection"); + + wait_until(WAIT, || test.window.visible_dialog().is_some()).expect("the connection dialog"); +} diff --git a/linux/data/resources/gtk/menus.ui b/linux/data/resources/gtk/menus.ui index cf47648688..a42497d601 100644 --- a/linux/data/resources/gtk/menus.ui +++ b/linux/data/resources/gtk/menus.ui @@ -4,6 +4,12 @@ startup; the windows read the menus with menu_by_id. -->
+ + + New Connection… + win.new-connection + New Window app.new-window From cb1601013b958da49747a2a0e518eeda782b8341 Mon Sep 17 00:00:00 2001 From: sedax90 <5001801+sedax90@users.noreply.github.com> Date: Sat, 31 Oct 2026 08:56:59 +0100 Subject: [PATCH 2/4] docs(linux-workspace): add the changelog fragment for New Connection --- linux/changelog.d/3931.fixed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 linux/changelog.d/3931.fixed.md diff --git a/linux/changelog.d/3931.fixed.md b/linux/changelog.d/3931.fixed.md new file mode 100644 index 0000000000..af4b0e0d39 --- /dev/null +++ b/linux/changelog.d/3931.fixed.md @@ -0,0 +1 @@ +A window showing a connection had no way to add a new connection. From 0f17183b9d154ece0ada40936501e2763c6ea605 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Sat, 31 Oct 2026 15:41:38 +0700 Subject: [PATCH 3/4] test(linux-workspace): choose New Connection from the main menu with and without a session --- .../app/src/ui/workspace/tests/shell.rs | 50 +++++++++++++++---- linux/data/resources/gtk/menus.ui | 2 - 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/linux/crates/app/src/ui/workspace/tests/shell.rs b/linux/crates/app/src/ui/workspace/tests/shell.rs index 6783f11afd..fe2f863936 100644 --- a/linux/crates/app/src/ui/workspace/tests/shell.rs +++ b/linux/crates/app/src/ui/workspace/tests/shell.rs @@ -419,21 +419,51 @@ fn the_overview_opens_a_tab_in_a_session_that_came_forward_from_its_own_input() ); } +/// Choose New Connection… in the main menu of `test`'s window, as a +/// click does, and wait for the connection dialog. +fn choose_new_connection(test: &TestWindow) { + let content = test.window.content().expect("the window's content"); + let button = descendants(&content) + .into_iter() + .filter_map(|widget| widget.downcast::().ok()) + .find(gtk::MenuButton::is_primary) + .expect("the main menu button"); + button.popup(); + let menu = button.popover().expect("the main menu"); + wait_until(WAIT, || menu.is_visible()).expect("the main menu to open"); + // GTK keeps a menu item private and gives it no action name. + let item = descendants(&menu) + .into_iter() + .find(|widget| { + widget.type_().name() == "GtkModelButton" + && widget.property::>("text").as_deref() == Some("New Connection…") + }) + .expect("New Connection… in the main menu"); + wait_until(WAIT, || item.is_sensitive()).expect("New Connection… to be enabled"); + + item.emit_by_name::<()>("clicked", &[]); + + wait_until(WAIT, || test.window.visible_dialog().is_some()).expect("the connection dialog"); +} + #[gtk4::test] -fn the_primary_menu_opens_a_new_connection_from_a_session() { - let test = TestWindow::connected("PrimaryMenuNewConnection"); +fn the_main_menu_adds_a_connection_beside_an_open_one() { + choose_new_connection(&TestWindow::connected("MainMenuNewConnection")); +} + +#[gtk4::test] +fn the_main_menu_adds_a_connection_with_none_open() { + let test = TestWindow::picker("PickerMenuNewConnection"); let menu = test.app.menu_by_id("primary_menu").expect("the primary menu"); let first = menu.item_link(0, "section").expect("a first section"); - let action = first - .item_attribute_value(0, "action", None) - .and_then(|value| value.get::()); assert_eq!( - action.as_deref(), + first + .item_attribute_value(0, "action", None) + .and_then(|value| value.get::()) + .as_deref(), Some("win.new-connection"), - "New Connection does not lead the menu" + "New Connection… does not lead the menu" ); - WidgetExt::activate_action(&test.window, "win.new-connection", None).expect("win.new-connection"); - - wait_until(WAIT, || test.window.visible_dialog().is_some()).expect("the connection dialog"); + choose_new_connection(&test); } diff --git a/linux/data/resources/gtk/menus.ui b/linux/data/resources/gtk/menus.ui index a42497d601..ba7067cd3f 100644 --- a/linux/data/resources/gtk/menus.ui +++ b/linux/data/resources/gtk/menus.ui @@ -4,8 +4,6 @@ startup; the windows read the menus with menu_by_id. -->
- New Connection… win.new-connection From d8349d76a66c33567c273cc2fb98c6b6c1485099 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Sat, 31 Oct 2026 15:43:27 +0700 Subject: [PATCH 4/4] test(linux-workspace): open the sidebar's main menu, not the overview's --- linux/crates/app/src/ui/workspace/tests/shell.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/linux/crates/app/src/ui/workspace/tests/shell.rs b/linux/crates/app/src/ui/workspace/tests/shell.rs index fe2f863936..33d5447b98 100644 --- a/linux/crates/app/src/ui/workspace/tests/shell.rs +++ b/linux/crates/app/src/ui/workspace/tests/shell.rs @@ -419,11 +419,10 @@ fn the_overview_opens_a_tab_in_a_session_that_came_forward_from_its_own_input() ); } -/// Choose New Connection… in the main menu of `test`'s window, as a -/// click does, and wait for the connection dialog. -fn choose_new_connection(test: &TestWindow) { - let content = test.window.content().expect("the window's content"); - let button = descendants(&content) +/// Choose New Connection… in the main menu under `header`, as a click +/// does, and wait for the connection dialog. +fn choose_new_connection(test: &TestWindow, header: &impl IsA) { + let button = descendants(header) .into_iter() .filter_map(|widget| widget.downcast::().ok()) .find(gtk::MenuButton::is_primary) @@ -448,7 +447,10 @@ fn choose_new_connection(test: &TestWindow) { #[gtk4::test] fn the_main_menu_adds_a_connection_beside_an_open_one() { - choose_new_connection(&TestWindow::connected("MainMenuNewConnection")); + let test = TestWindow::connected("MainMenuNewConnection"); + // The overview's own menu button is a primary one too. + let header = shell_of(&test.workspace()).imp().sidebar_header.get(); + choose_new_connection(&test, &header); } #[gtk4::test] @@ -465,5 +467,5 @@ fn the_main_menu_adds_a_connection_with_none_open() { "New Connection… does not lead the menu" ); - choose_new_connection(&test); + choose_new_connection(&test, &test.window.content().expect("the picker")); }