Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/server/headless/surface_interest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
141 changes: 141 additions & 0 deletions src/server/headless/tests/surface_interest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -243,6 +259,131 @@ async fn metadata_only_shell_is_isolated_until_surface_activation() {
shutdown_test_runtimes(&mut server);
}

#[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();
Expand Down
Loading