diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-18 02:52:35 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-18 02:53:31 +0300 |
| commit | 36b4001f4526b60f1c9b67a4a34596e5ad2bcca1 (patch) | |
| tree | 520ab28a0a3f3f798e6e655fc83529e8617720e9 /src/client | |
| parent | aca35ea76f3df15ec68fd07959d939f5e7a78a6c (diff) | |
feat: sync theme with host appearance
refs #675
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/input.rs | 17 | ||||
| -rw-r--r-- | src/client/mod.rs | 112 |
2 files changed, 119 insertions, 10 deletions
diff --git a/src/client/input.rs b/src/client/input.rs index add4990c..ba1e353f 100644 --- a/src/client/input.rs +++ b/src/client/input.rs @@ -59,6 +59,7 @@ fn unix_stdin_reader_loop( let mut framer = crate::raw_input::RawInputByteFramer::default(); if host_color_query_sent { framer.host_color_query_sent(); + framer.enable_host_color_scheme_change_tracking(); } while !should_quit.load(Ordering::Acquire) { @@ -75,7 +76,10 @@ fn unix_stdin_reader_loop( } if stdin_read_ready(&reader, 10) == Some(false) { - for data in framer.flush_timeout() { + let had_pending = framer.has_pending_input(); + let chunks = framer.flush_timeout(); + let held_escape = had_pending && chunks.is_empty(); + for data in chunks { if event_tx .blocking_send(ClientLoopEvent::StdinInput(data)) .is_err() @@ -83,6 +87,16 @@ fn unix_stdin_reader_loop( return; } } + if held_escape && stdin_read_ready(&reader, 10) == Some(false) { + for data in framer.flush_timeout() { + if event_tx + .blocking_send(ClientLoopEvent::StdinInput(data)) + .is_err() + { + return; + } + } + } } } Err(err) => { @@ -268,6 +282,7 @@ fn windows_client_input_event_from_raw( Some(crate::protocol::ClientInputEvent::FocusLost) } crate::raw_input::RawInputEvent::HostDefaultColor { .. } + | crate::raw_input::RawInputEvent::HostColorSchemeChanged(_) | crate::raw_input::RawInputEvent::Unsupported => None, } } diff --git a/src/client/mod.rs b/src/client/mod.rs index 70d39559..834edd2b 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -329,6 +329,8 @@ fn setup_terminal_with_capabilities( mouse_capture: bool, ) -> io::Result<TerminalGuard> { ratatui::init(); + let host_color_scheme_reports = + should_enable_host_color_scheme_reports(enable_client_protocols); if enable_client_protocols { if mouse_capture { @@ -337,11 +339,19 @@ fn setup_terminal_with_capabilities( set_mouse_capture(false)?; } execute!(io::stdout(), EnableBracketedPaste, EnableFocusChange)?; + if host_color_scheme_reports { + write_host_color_scheme_report_mode(&mut io::stdout(), true)?; + } push_keyboard_enhancement_flags()?; - } else if mouse_capture { - set_mouse_capture(true)?; } else { - set_mouse_capture(false)?; + if should_query_host_terminal_theme() { + write_host_color_scheme_report_mode(&mut io::stdout(), false)?; + } + if mouse_capture { + set_mouse_capture(true)?; + } else { + set_mouse_capture(false)?; + } } let modify_other_keys_mode = enable_client_protocols @@ -360,15 +370,42 @@ fn setup_terminal_with_capabilities( Ok(TerminalGuard { reset_modify_other_keys: modify_other_keys_mode.is_some(), + reset_host_color_scheme_reports: host_color_scheme_reports, }) } +fn should_enable_host_color_scheme_reports(enable_client_protocols: bool) -> bool { + enable_client_protocols && should_query_host_terminal_theme() +} + /// Guard that restores the terminal when dropped. struct TerminalGuard { reset_modify_other_keys: bool, + reset_host_color_scheme_reports: bool, +} + +fn write_host_color_scheme_report_mode( + writer: &mut impl io::Write, + enabled: bool, +) -> io::Result<()> { + let sequence = if enabled { + crate::terminal_theme::HOST_COLOR_SCHEME_REPORT_ENABLE_SEQUENCE + } else { + crate::terminal_theme::HOST_COLOR_SCHEME_REPORT_DISABLE_SEQUENCE + }; + writer.write_all(sequence.as_bytes())?; + writer.flush() } -fn write_terminal_restore_postlude(writer: &mut impl io::Write) -> io::Result<()> { +fn write_terminal_restore_postlude( + writer: &mut impl io::Write, + reset_host_color_scheme_reports: bool, +) -> io::Result<()> { + if reset_host_color_scheme_reports { + writer.write_all( + crate::terminal_theme::HOST_COLOR_SCHEME_REPORT_DISABLE_SEQUENCE.as_bytes(), + )?; + } // Restore a visible cursor and reset DECSCUSR back to the terminal default. writer.write_all(b"\x1b[?25h\x1b[0 q")?; writer.flush() @@ -387,7 +424,7 @@ fn set_mouse_capture(enabled: bool) -> io::Result<()> { } } -fn restore_terminal_state(reset_modify_other_keys: bool) { +fn restore_terminal_state(reset_modify_other_keys: bool, reset_host_color_scheme_reports: bool) { let _ = clear_received_kitty_graphics(&mut io::stdout()); // Reset modifyOtherKeys if we enabled it. @@ -404,7 +441,7 @@ fn restore_terminal_state(reset_modify_other_keys: bool) { DisableMouseCapture ); ratatui::restore(); - let _ = write_terminal_restore_postlude(&mut io::stdout()); + let _ = write_terminal_restore_postlude(&mut io::stdout(), reset_host_color_scheme_reports); } #[cfg(not(windows))] @@ -432,7 +469,10 @@ fn pop_keyboard_enhancement_flags() -> io::Result<()> { impl Drop for TerminalGuard { fn drop(&mut self) { - restore_terminal_state(self.reset_modify_other_keys); + restore_terminal_state( + self.reset_modify_other_keys, + self.reset_host_color_scheme_reports, + ); } } @@ -704,9 +744,13 @@ fn run_client_with_mode( // Install a panic hook to restore the terminal on panic (same as monolithic). let panic_resets_modify_other_keys = terminal_guard.reset_modify_other_keys; + let panic_resets_host_color_scheme_reports = terminal_guard.reset_host_color_scheme_reports; let original_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { - restore_terminal_state(panic_resets_modify_other_keys); + restore_terminal_state( + panic_resets_modify_other_keys, + panic_resets_host_color_scheme_reports, + ); original_hook(info); })); @@ -898,6 +942,9 @@ async fn run_client_loop( ) { state.request_full_redraw(); } + if crate::raw_input::events_require_host_terminal_theme_query(&events) { + query_host_terminal_theme(); + } data }; if should_bridge_clipboard_image_paste(&data, state.remote_image_paste_key) { @@ -1601,17 +1648,64 @@ mod tests { } #[test] + fn write_host_color_scheme_report_mode_emits_mode_sequences() { + let mut output = Vec::new(); + write_host_color_scheme_report_mode(&mut output, true).unwrap(); + write_host_color_scheme_report_mode(&mut output, false).unwrap(); + + let mut expected = Vec::new(); + expected.extend_from_slice( + crate::terminal_theme::HOST_COLOR_SCHEME_REPORT_ENABLE_SEQUENCE.as_bytes(), + ); + expected.extend_from_slice( + crate::terminal_theme::HOST_COLOR_SCHEME_REPORT_DISABLE_SEQUENCE.as_bytes(), + ); + assert_eq!(output, expected); + } + + #[test] + fn color_scheme_change_event_requests_host_theme_query() { + let events = crate::raw_input::parse_raw_input_bytes_sync(b"\x1b[?997;1n"); + + assert!(crate::raw_input::events_require_host_terminal_theme_query( + &events + )); + } + + #[test] fn host_terminal_theme_query_is_disabled_on_windows() { assert_eq!(should_query_host_terminal_theme(), !cfg!(windows)); } #[test] + fn color_scheme_reports_are_enabled_only_for_full_clients() { + assert_eq!( + should_enable_host_color_scheme_reports(true), + !cfg!(windows) + ); + assert!(!should_enable_host_color_scheme_reports(false)); + } + + #[test] fn terminal_restore_postlude_restores_visible_default_cursor() { let mut output = Vec::new(); - write_terminal_restore_postlude(&mut output).unwrap(); + write_terminal_restore_postlude(&mut output, false).unwrap(); assert_eq!(output, b"\x1b[?25h\x1b[0 q"); } + #[test] + fn terminal_restore_postlude_disables_color_scheme_reports_when_enabled() { + let mut output = Vec::new(); + write_terminal_restore_postlude(&mut output, true).unwrap(); + + let mut expected = Vec::new(); + expected.extend_from_slice( + crate::terminal_theme::HOST_COLOR_SCHEME_REPORT_DISABLE_SEQUENCE.as_bytes(), + ); + expected.extend_from_slice(b"\x1b[?25h\x1b[0 q"); + assert_eq!(output, expected); + } + #[cfg(unix)] #[test] fn attach_escape_detaches_on_prefix_q() { |
