aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorakbash <akbash@herdr.dev>2026-09-10 14:50:30 +0300
committerGitHub <noreply@github.com>2026-09-10 14:50:30 +0300
commit406c9e4feeacdb80dfbaf33145acd7ccc93695d5 (patch)
treea54f6efaeceb1e516276569dc7308309b0c4d529 /src/server
parent3813af0ffee325bf6bf6ee78ee952240524d0ee1 (diff)
fix: preserve focused viewer geometry during activation (#3746)
* fix: preserve focused viewer geometry during activation refs #3744 * fix: preserve geometry through activation sync refs #3744 --------- Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src/server')
-rw-r--r--src/server/headless/surface_interest.rs15
-rw-r--r--src/server/headless/tests/surface_interest.rs141
2 files changed, 155 insertions, 1 deletions
diff --git a/src/server/headless/surface_interest.rs b/src/server/headless/surface_interest.rs
index 7270be0d..9772fc03 100644
--- a/src/server/headless/surface_interest.rs
+++ b/src/server/headless/surface_interest.rs
@@ -69,7 +69,20 @@ impl HeadlessServer {
// explicitly requests the bounded replay after its coherent frame is visible.
self.sent_window_title = None;
self.resize_shared_runtime_to_effective_size_with_pending_agent_resumes(true);
- self.claim_shell_tab_geometry(client_id, true);
+ let focused_viewer_already_owns_tab = self
+ .shell_tab_id_for_client(client_id)
+ .is_some_and(|tab_id| {
+ self.clients.iter().any(|(&other_id, client)| {
+ other_id != client_id
+ && client.is_active_shell_client()
+ && client.outer_terminal_focus == Some(true)
+ && self.shell_tab_id_for_client(other_id).as_deref()
+ == Some(tab_id.as_str())
+ })
+ });
+ if !focused_viewer_already_owns_tab {
+ self.claim_shell_tab_geometry(client_id, true);
+ }
} else {
self.tab_geometry_controllers
.retain(|_, controller_id| *controller_id != client_id);
diff --git a/src/server/headless/tests/surface_interest.rs b/src/server/headless/tests/surface_interest.rs
index 591f6a8f..df06dc03 100644
--- a/src/server/headless/tests/surface_interest.rs
+++ b/src/server/headless/tests/surface_interest.rs
@@ -36,6 +36,22 @@ fn lifecycle_resize() -> crate::protocol::ClientMessage {
}
}
+fn request_active_surface(server: &mut HeadlessServer, client_id: u64, request_id: &str) {
+ let boot_id = server.client_shell_boot_id.clone();
+ assert!(
+ server.handle_server_event(ServerEvent::ClientShellEndpointRequest {
+ client_id,
+ boot_id,
+ request: Box::new(api::schema::Request {
+ id: request_id.into(),
+ method: api::schema::Method::ClientShellSurfaceSet(
+ api::schema::ClientShellSurfaceSetParams { active: true },
+ ),
+ }),
+ })
+ );
+}
+
#[tokio::test]
async fn metadata_only_shell_is_isolated_until_surface_activation() {
let mut server = test_headless_server();
@@ -244,6 +260,131 @@ async fn metadata_only_shell_is_isolated_until_surface_activation() {
}
#[tokio::test]
+async fn background_surface_activation_preserves_focused_viewer_geometry() {
+ let mut server = test_headless_server();
+ let pane_id = install_shared_view_test_runtime(&mut server);
+ let (focused_control, _) = connect_test_shell(&mut server, 7, 68, 17);
+ let _ = focused_control.recv().expect("focused client snapshot");
+ assert!(server.handle_server_event(ServerEvent::ClientShellFocus {
+ client_id: 7,
+ focused: true,
+ }));
+ let focused_size = server.app.state.workspaces[0].test_runtimes[&pane_id].current_size();
+ assert_eq!(focused_size, (17, 67));
+ let shared_tab_id = server.shell_tab_id_for_client(7).expect("focused tab");
+ assert_eq!(
+ server.tab_geometry_controllers.get(&shared_tab_id),
+ Some(&7)
+ );
+
+ let (writer, background_control, _) = test_client_writer();
+ assert!(
+ server.handle_server_event(ServerEvent::ClientShellConnected {
+ client_id: 8,
+ surface_cols: 100,
+ surface_rows: 35,
+ cell_width_px: 0,
+ cell_height_px: 0,
+ pixel_mouse: false,
+ direct_graphics: false,
+ endpoint_keybindings: false,
+ mouse_capture: false,
+ surface_active: false,
+ writer,
+ })
+ );
+ let _ = background_control
+ .recv()
+ .expect("background client snapshot");
+
+ request_active_surface(&mut server, 8, "activate-background-surface");
+ let _ = background_control
+ .recv()
+ .expect("background surface activation response");
+ assert_eq!(
+ server.shell_tab_id_for_client(8).as_deref(),
+ Some(shared_tab_id.as_str())
+ );
+ assert_eq!(server.clients[&7].outer_terminal_focus, Some(true));
+ assert_eq!(server.clients[&8].outer_terminal_focus, None);
+ assert_eq!(
+ server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(),
+ focused_size,
+ "surface activation must not transiently resize a focused viewer's tab"
+ );
+ assert_eq!(
+ server.tab_geometry_controllers.get(&shared_tab_id),
+ Some(&7)
+ );
+
+ assert!(server.handle_server_event(ServerEvent::ClientShellFocus {
+ client_id: 8,
+ focused: false,
+ }));
+ assert_eq!(server.clients[&7].outer_terminal_focus, Some(true));
+ assert_eq!(server.clients[&8].outer_terminal_focus, Some(false));
+ assert_eq!(
+ server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(),
+ focused_size
+ );
+ assert_eq!(
+ server.tab_geometry_controllers.get(&shared_tab_id),
+ Some(&7)
+ );
+
+ request_active_surface(&mut server, 8, "synchronize-background-surface");
+ let _ = background_control
+ .recv()
+ .expect("background presentation synchronization response");
+ assert_eq!(
+ server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(),
+ focused_size
+ );
+ assert_eq!(
+ server.tab_geometry_controllers.get(&shared_tab_id),
+ Some(&7)
+ );
+ shutdown_test_runtimes(&mut server);
+}
+
+#[tokio::test]
+async fn focused_surface_reassertion_reclaims_tab_geometry() {
+ let mut server = test_headless_server();
+ let pane_id = install_shared_view_test_runtime(&mut server);
+ let (focused_control, _) = connect_test_shell(&mut server, 8, 100, 35);
+ let _ = focused_control.recv().expect("focused client snapshot");
+ assert!(server.handle_server_event(ServerEvent::ClientShellFocus {
+ client_id: 8,
+ focused: true,
+ }));
+ let shared_tab_id = server.shell_tab_id_for_client(8).expect("focused tab");
+
+ let (other_control, _) = connect_test_shell(&mut server, 7, 68, 17);
+ let _ = other_control.recv().expect("other client snapshot");
+ assert!(server.claim_shell_tab_geometry(7, false));
+ assert_eq!(
+ server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(),
+ (17, 67)
+ );
+
+ request_active_surface(&mut server, 8, "reassert-focused-surface");
+ let _ = focused_control
+ .recv()
+ .expect("focused surface reassertion response");
+
+ assert_eq!(server.clients[&8].outer_terminal_focus, Some(true));
+ assert_eq!(
+ server.app.state.workspaces[0].test_runtimes[&pane_id].current_size(),
+ (35, 99)
+ );
+ assert_eq!(
+ server.tab_geometry_controllers.get(&shared_tab_id),
+ Some(&8)
+ );
+ shutdown_test_runtimes(&mut server);
+}
+
+#[tokio::test]
async fn presentation_sync_epoch_replays_modes_and_title() {
let mut server = test_headless_server();
let (writer, control_rx, _render_rx) = test_client_writer();