aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/mod.rs10
-rw-r--r--src/app/runtime.rs4
-rw-r--r--src/client/mod.rs10
-rw-r--r--src/raw_input.rs15
-rw-r--r--src/server/headless.rs67
5 files changed, 98 insertions, 8 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 7c3bae76..a332503f 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -80,6 +80,7 @@ pub struct App {
HashSet<(crossterm::event::KeyCode, crossterm::event::KeyModifiers)>,
pub render_notify: Arc<Notify>,
pub render_dirty: Arc<AtomicBool>,
+ pub(crate) full_redraw_pending: bool,
pub(crate) overlay_panes: HashMap<crate::layout::PaneId, OverlayPaneState>,
pub(crate) local_terminal_notifications: bool,
}
@@ -441,11 +442,16 @@ impl App {
last_terminal_size: terminal::size().ok(),
render_notify,
render_dirty,
+ full_redraw_pending: false,
overlay_panes: HashMap::new(),
local_terminal_notifications: true,
}
}
+ fn request_full_redraw(&mut self) {
+ self.full_redraw_pending = true;
+ }
+
pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
if self.input_rx.is_none() {
self.input_rx = Some(crate::raw_input::spawn_input_reader());
@@ -507,6 +513,10 @@ impl App {
if needs_render && self.can_render_now(now) {
self.render_dirty.swap(false, Ordering::AcqRel);
let _sync_output = SyncOutputGuard::begin()?;
+ if self.full_redraw_pending {
+ terminal.clear()?;
+ self.full_redraw_pending = false;
+ }
let kitty_graphics_enabled = self.state.kitty_graphics_enabled;
let mut cell_size = crate::kitty_graphics::HostCellSize::default();
terminal.draw(|frame| {
diff --git a/src/app/runtime.rs b/src/app/runtime.rs
index 6cdb6b43..4d2f6c47 100644
--- a/src/app/runtime.rs
+++ b/src/app/runtime.rs
@@ -95,8 +95,10 @@ impl App {
true
}
crate::raw_input::RawInputEvent::OuterFocusGained => {
+ self.request_full_redraw();
self.state.outer_terminal_focus = Some(true);
- self.state.mark_active_tab_seen()
+ self.state.mark_active_tab_seen();
+ true
}
crate::raw_input::RawInputEvent::OuterFocusLost => {
self.state.outer_terminal_focus = Some(false);
diff --git a/src/client/mod.rs b/src/client/mod.rs
index 1758ffdc..5d52f8b3 100644
--- a/src/client/mod.rs
+++ b/src/client/mod.rs
@@ -56,6 +56,12 @@ struct ClientState {
kitty_graphics_enabled: bool,
}
+impl ClientState {
+ fn request_full_redraw(&mut self) {
+ self.blit_encoder = blit::BlitEncoder::new();
+ }
+}
+
// ---------------------------------------------------------------------------
// Error types
// ---------------------------------------------------------------------------
@@ -497,6 +503,10 @@ async fn run_client_loop(
match event {
ClientLoopEvent::StdinInput(data) => {
+ let events = crate::raw_input::parse_raw_input_bytes_sync(&data);
+ if crate::raw_input::events_require_host_surface_redraw(&events) {
+ state.request_full_redraw();
+ }
let msg = ClientMessage::Input { data };
if let Err(e) = write_to_server(&mut write_stream, &msg) {
return Err(ClientError::ConnectionLost(e));
diff --git a/src/raw_input.rs b/src/raw_input.rs
index 3ebd9554..28a2e561 100644
--- a/src/raw_input.rs
+++ b/src/raw_input.rs
@@ -128,6 +128,12 @@ pub enum RawInputEvent {
Unsupported,
}
+pub(crate) fn events_require_host_surface_redraw(events: &[RawInputEvent]) -> bool {
+ events
+ .iter()
+ .any(|event| matches!(event, RawInputEvent::OuterFocusGained))
+}
+
pub fn spawn_input_reader() -> mpsc::Receiver<RawInputEvent> {
let (tx, rx) = mpsc::channel(256);
@@ -649,6 +655,15 @@ mod tests {
}
#[test]
+ fn outer_focus_gained_requests_host_surface_redraw() {
+ let events = parse_raw_input_bytes_sync(b"\x1b[I");
+ assert!(events_require_host_surface_redraw(&events));
+
+ let events = parse_raw_input_bytes_sync(b"\x1b[O");
+ assert!(!events_require_host_surface_redraw(&events));
+ }
+
+ #[test]
fn parses_xterm_alt_up_arrow() {
let (RawInputEvent::Key(key), consumed) = extract_one_event(b"\x1b[1;3A").unwrap() else {
panic!("expected key");
diff --git a/src/server/headless.rs b/src/server/headless.rs
index 9bb3181f..29c8de6e 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -244,6 +244,14 @@ impl ClientConnection {
writer,
}
}
+
+ fn request_full_redraw(&mut self) {
+ self.render_state.reset_baseline();
+ }
+
+ fn request_semantic_redraw_after_input(&mut self) {
+ self.render_state.reset_semantic_input_baseline();
+ }
}
// ---------------------------------------------------------------------------
@@ -529,7 +537,7 @@ impl HeadlessServer {
// rendering semantics. Force one fresh frame to every remaining client
// even if the next rendered buffer compares equal to its cached frame.
for client in self.clients.values_mut() {
- client.render_state.reset_baseline();
+ client.request_full_redraw();
}
}
@@ -1154,14 +1162,20 @@ impl HeadlessServer {
}
ServerEvent::ClientInput { client_id, data } => {
debug!(client_id, len = data.len(), "client input received");
+ let events = crate::raw_input::parse_raw_input_bytes_sync(&data);
+ let host_surface_redraw =
+ crate::raw_input::events_require_host_surface_redraw(&events);
if let Some(client) = self.clients.get_mut(&client_id) {
- // Ensure semantic clients receive one post-input frame even if the
- // semantic buffer compares equal. Terminal-ANSI clients must keep their
- // server-side blit baseline; resetting it here forces a full redraw on
- // every keypress and makes remote sessions feel extremely slow.
- client.render_state.reset_semantic_input_baseline();
+ if host_surface_redraw {
+ client.request_full_redraw();
+ } else {
+ // Ensure semantic clients receive one post-input frame even if the
+ // semantic buffer compares equal. Terminal-ANSI clients must keep their
+ // server-side blit baseline; resetting it here forces a full redraw on
+ // every keypress and makes remote sessions feel extremely slow.
+ client.request_semantic_redraw_after_input();
+ }
}
- let events = crate::raw_input::parse_raw_input_bytes_sync(&data);
self.update_client_outer_focus_from_events(client_id, &events);
let interaction = Self::events_include_interaction(&events);
let foreground_changed = if interaction {
@@ -2620,6 +2634,45 @@ mod tests {
}
#[test]
+ fn outer_focus_gained_forces_terminal_ansi_full_redraw() {
+ 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::TerminalAnsi,
+ Some(client_tx),
+ ),
+ );
+ server.foreground_client_id = Some(1);
+
+ server.render_and_stream();
+ let _ = client_rx
+ .recv_timeout(Duration::from_millis(100))
+ .expect("initial terminal frame");
+
+ assert!(server.handle_server_event(ServerEvent::ClientInput {
+ client_id: 1,
+ data: b"\x1b[I".to_vec(),
+ }));
+ server.render_and_stream();
+
+ match read_server_message(client_rx.recv_timeout(Duration::from_millis(100)).unwrap()) {
+ ServerMessage::Terminal(frame) => {
+ assert_eq!(frame.seq, 2);
+ assert!(frame.full);
+ }
+ other => panic!("expected terminal frame, got {other:?}"),
+ }
+ }
+
+ #[test]
fn full_render_queue_does_not_advance_terminal_ansi_baseline() {
let mut server = test_headless_server();
let (client_tx, _client_control_rx, client_rx) = test_client_writer();