fix: force redraw on terminal focus gain
This commit is contained in:
parent
73dba97521
commit
e1a84a8e34
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
@ -648,6 +654,15 @@ mod tests {
|
|||
assert!(matches!(event, RawInputEvent::OuterFocusLost));
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
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();
|
||||
}
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
self.update_client_outer_focus_from_events(client_id, &events);
|
||||
let interaction = Self::events_include_interaction(&events);
|
||||
let foreground_changed = if interaction {
|
||||
|
|
@ -2619,6 +2633,45 @@ mod tests {
|
|||
assert!(client_rx.recv_timeout(Duration::from_millis(50)).is_err());
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue