aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/headless.rs131
-rw-r--r--src/server/render_stream.rs29
2 files changed, 157 insertions, 3 deletions
diff --git a/src/server/headless.rs b/src/server/headless.rs
index 720b43f2..9c21aa1b 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -3462,6 +3462,7 @@ impl HeadlessServer {
fn retained_pty_update_allowed_by_app_state(&self) -> bool {
self.app.state.mode == app::Mode::Terminal
+ && self.app.state.popup_pane.is_none()
&& self.app.state.selection.is_none()
&& self.app.state.copy_mode.is_none()
&& self.app.state.context_menu.is_none()
@@ -7801,6 +7802,136 @@ next_tab = ""
}
#[tokio::test]
+ async fn retained_pty_update_declines_while_popup_is_visible() {
+ let (mut server, client_rx, _) = retained_test_server(b"tiled");
+ let popup_runtime =
+ crate::terminal::TerminalRuntime::test_with_screen_bytes(40, 12, b"popup-aaaa");
+ let (_, terminal_id) = server.app.install_test_popup_runtime(popup_runtime);
+
+ server.render_and_stream();
+ let initial = read_server_frame(
+ client_rx
+ .recv_timeout(Duration::from_millis(100))
+ .expect("initial popup frame"),
+ );
+ assert!(frame_text(&initial).contains("popup-aaaa"));
+ server
+ .app
+ .terminal_runtimes
+ .get(&terminal_id)
+ .unwrap()
+ .test_process_pty_bytes(b"\rZ");
+
+ assert!(!server.render_retained_pty_update_and_stream());
+ server.render_and_stream();
+ let updated = read_server_frame(
+ client_rx
+ .recv_timeout(Duration::from_millis(100))
+ .expect("full popup fallback frame"),
+ );
+ assert!(frame_text(&updated).contains("Zopup-aaaa"));
+ }
+
+ #[tokio::test]
+ async fn popup_forces_host_mouse_capture_for_headless_client() {
+ let mut server = test_headless_server();
+ let (client_tx, client_control_rx, _client_rx) = test_client_writer();
+ server.clients.insert(
+ 1,
+ ClientConnection::new(
+ (80, 24),
+ crate::kitty_graphics::HostCellSize::default(),
+ crate::terminal_theme::TerminalTheme::default(),
+ None,
+ 1,
+ RenderEncoding::SemanticFrame,
+ Some(client_tx),
+ ),
+ );
+ server.app.state.mouse_capture = false;
+ let popup_runtime =
+ crate::terminal::TerminalRuntime::test_with_screen_bytes(40, 12, b"popup");
+ server.app.install_test_popup_runtime(popup_runtime);
+
+ server.stream_host_mouse_capture_mode();
+
+ assert!(matches!(
+ read_server_message(
+ client_control_rx
+ .recv_timeout(Duration::from_millis(100))
+ .expect("mouse capture message")
+ ),
+ ServerMessage::MouseCapture { enabled: true }
+ ));
+ }
+
+ #[tokio::test]
+ async fn virtual_render_uses_popup_cursor() {
+ let (mut server, _client_rx, _) = retained_test_server(b"\x1b[2;2H");
+ let popup_runtime =
+ crate::terminal::TerminalRuntime::test_with_screen_bytes(40, 12, b"\x1b[4;5H");
+ let (_, terminal_id) = server.app.install_test_popup_runtime(popup_runtime);
+
+ let (_, cursor) = crate::server::render_stream::render_virtual_with_runtime_registry(
+ &mut server.app.state,
+ &server.app.terminal_runtimes,
+ ratatui::layout::Rect::new(0, 0, 80, 24),
+ true,
+ crate::kitty_graphics::HostCellSize::default(),
+ );
+ let (_, inner) =
+ crate::ui::popup_pane_rects(&server.app.state, server.app.state.view.terminal_area)
+ .unwrap();
+ let expected = server
+ .app
+ .terminal_runtimes
+ .get(&terminal_id)
+ .unwrap()
+ .cursor_state(inner, true)
+ .unwrap();
+
+ assert_eq!(
+ cursor,
+ Some(crate::protocol::CursorState {
+ x: expected.x,
+ y: expected.y,
+ visible: expected.visible,
+ shape: expected.shape,
+ })
+ );
+ }
+
+ #[tokio::test]
+ async fn virtual_render_does_not_resize_directly_attached_popup() {
+ let (mut server, _client_rx, _) = retained_test_server(b"tiled");
+ let popup_runtime = crate::terminal::TerminalRuntime::test_with_screen_bytes(50, 13, b"");
+ let (_, terminal_id) = server.app.install_test_popup_runtime(popup_runtime);
+ server
+ .app
+ .state
+ .direct_attach_resize_locks
+ .insert(terminal_id.clone());
+
+ let _ = crate::server::render_stream::render_virtual_with_runtime_registry(
+ &mut server.app.state,
+ &server.app.terminal_runtimes,
+ ratatui::layout::Rect::new(0, 0, 80, 24),
+ true,
+ crate::kitty_graphics::HostCellSize::default(),
+ );
+
+ assert_eq!(
+ server
+ .app
+ .terminal_runtimes
+ .get(&terminal_id)
+ .unwrap()
+ .current_size(),
+ (13, 50)
+ );
+ }
+
+ #[tokio::test]
async fn retained_pty_update_declines_while_toast_is_visible() {
let (mut server, client_rx, pane_id) = retained_test_server(b"aaaa");
server.app.state.toast = Some(crate::app::state::ToastNotification {
diff --git a/src/server/render_stream.rs b/src/server/render_stream.rs
index d21dea56..facebd2c 100644
--- a/src/server/render_stream.rs
+++ b/src/server/render_stream.rs
@@ -290,15 +290,17 @@ pub(crate) fn render_virtual_with_runtime_registry(
resize_panes: bool,
cell_size: crate::kitty_graphics::HostCellSize,
) -> (ratatui::buffer::Buffer, Option<CursorState>) {
+ let popup_visible = app_state.popup_pane.is_some();
let pre_compute_suppresses_focused_terminal_cursor =
- focused_terminal_suppresses_host_cursor(app_state, terminal_runtimes);
+ !popup_visible && focused_terminal_suppresses_host_cursor(app_state, terminal_runtimes);
if resize_panes {
crate::ui::compute_view_with_cell_size(app_state, terminal_runtimes, area, cell_size);
} else {
crate::ui::compute_view_without_resizing_panes(app_state, terminal_runtimes, area);
}
let suppress_focused_terminal_cursor = pre_compute_suppresses_focused_terminal_cursor
- || focused_terminal_suppresses_host_cursor(app_state, terminal_runtimes);
+ || (!popup_visible
+ && focused_terminal_suppresses_host_cursor(app_state, terminal_runtimes));
let backend = CursorTrackingBackend::new(area.width, area.height);
let mut terminal = ratatui::Terminal::new(backend).expect("TestBackend::new should never fail");
@@ -310,7 +312,9 @@ pub(crate) fn render_virtual_with_runtime_registry(
.expect("render to TestBackend should never fail");
let buffer = terminal.backend().buffer().clone();
- let cursor = if suppress_focused_terminal_cursor {
+ let cursor = if popup_visible {
+ popup_terminal_cursor(app_state, terminal_runtimes)
+ } else if suppress_focused_terminal_cursor {
None
} else {
focused_terminal_cursor(app_state, terminal_runtimes).or_else(|| {
@@ -323,6 +327,25 @@ pub(crate) fn render_virtual_with_runtime_registry(
(buffer, cursor)
}
+fn popup_terminal_cursor(
+ app_state: &AppState,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+) -> Option<CursorState> {
+ let popup = app_state.popup_pane.as_ref()?;
+ let runtime = terminal_runtimes.get(&popup.terminal_id)?;
+ if runtime.synchronized_output_active() {
+ return None;
+ }
+ let (_, inner) = crate::ui::popup_pane_rects(app_state, app_state.view.terminal_area)?;
+ let cursor = runtime.cursor_state(inner, true)?;
+ Some(CursorState {
+ x: cursor.x,
+ y: cursor.y,
+ visible: cursor.visible && !crate::ui::pane_is_scrolled_back(runtime),
+ shape: cursor.shape,
+ })
+}
+
/// Renders one server-owned terminal directly for `terminal attach` clients.
pub(crate) fn render_terminal_virtual(
runtime: &crate::terminal::TerminalRuntime,