diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-02 21:57:59 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-02 21:59:06 +0300 |
| commit | d1471e64834d0c7daf072f11c01c30db35324850 (patch) | |
| tree | 5763e6da098d3aabe5932decf26e5cf1d31c814c /src/client | |
| parent | 848554517ae6473caf61f738d60def90f77e1dc9 (diff) | |
fix: draw host cursor on windows
refs #930
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/mod.rs | 63 |
1 files changed, 60 insertions, 3 deletions
diff --git a/src/client/mod.rs b/src/client/mod.rs index 4e0a54ec..92edb900 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -55,6 +55,7 @@ struct ClientLoopConfig { sound_config: crate::config::SoundConfig, mouse_scroll_lines: usize, redraw_on_focus_gained: bool, + host_cursor: crate::config::HostCursorModeConfig, kitty_graphics_enabled: bool, mouse_capture_active: bool, #[cfg(unix)] @@ -83,6 +84,8 @@ struct ClientState { remote_image_paste_key: Option<(crossterm::event::KeyCode, crossterm::event::KeyModifiers)>, /// Whether outer focus gain should force a full host-terminal redraw. redraw_on_focus_gained: bool, + /// Whether this client draws the cursor into frame cells instead of using the host cursor. + draw_host_cursor: bool, } #[derive(Debug, Default)] @@ -440,6 +443,14 @@ fn write_terminal_restore_postlude( writer.flush() } +fn should_draw_host_cursor(mode: crate::config::HostCursorModeConfig) -> bool { + match mode { + crate::config::HostCursorModeConfig::Auto => cfg!(windows), + crate::config::HostCursorModeConfig::Native => false, + crate::config::HostCursorModeConfig::Drawn => true, + } +} + #[cfg(windows)] #[derive(Default)] struct WindowsVirtualTerminalInputSetup { @@ -1092,6 +1103,7 @@ fn run_client_with_mode( let mouse_capture = loaded_config.config.ui.mouse_capture; let mouse_scroll_lines = loaded_config.config.ui.mouse_scroll_lines(); let redraw_on_focus_gained = loaded_config.config.ui.redraw_on_focus_gained; + let host_cursor = loaded_config.config.ui.host_cursor; let direct_attach_requested = attach_request.is_some(); #[cfg(unix)] let remote_image_paste_key = client_remote_image_paste_key(&loaded_config.config); @@ -1101,6 +1113,7 @@ fn run_client_with_mode( sound_config: loaded_config.config.ui.sound, mouse_scroll_lines, redraw_on_focus_gained, + host_cursor, kitty_graphics_enabled, mouse_capture_active: mouse_capture, #[cfg(unix)] @@ -1253,6 +1266,7 @@ async fn run_client_loop( ) -> Result<(), ClientError> { #[cfg(windows)] let _ = config.mouse_scroll_lines; + let draw_host_cursor = attach_escape.is_none() && should_draw_host_cursor(config.host_cursor); let mut state = ClientState { blit_encoder: render_ansi::BlitEncoder::new(), @@ -1266,6 +1280,7 @@ async fn run_client_loop( #[cfg(unix)] remote_image_paste_key: config.remote_image_paste_key, redraw_on_focus_gained: config.redraw_on_focus_gained, + draw_host_cursor, }; debug!(?negotiated_encoding, "client render encoding active"); @@ -1443,7 +1458,18 @@ async fn run_client_loop( } ClientLoopEvent::ServerMessage(msg) => match msg { ServerMessage::Frame(frame_data) => { - let encoded = state.blit_encoder.encode(&frame_data, false); + let frame_data = if state.draw_host_cursor { + render_ansi::frame_with_drawn_cursor(frame_data) + } else { + frame_data + }; + let encoded = if state.draw_host_cursor { + state + .blit_encoder + .encode_with_suppressed_visible_cursor(&frame_data, false) + } else { + state.blit_encoder.encode(&frame_data, false) + }; let mut stdout = io::stdout(); let graphics = if state.kitty_graphics_enabled { frame_data.graphics.as_slice() @@ -1493,6 +1519,7 @@ async fn run_client_loop( reload_local_client_config( &mut state.sound_config, &mut state.redraw_on_focus_gained, + &mut state.draw_host_cursor, #[cfg(unix)] &mut state.remote_image_paste_key, ); @@ -1618,6 +1645,7 @@ fn client_remote_image_paste_key( fn reload_local_client_config( sound_config: &mut crate::config::SoundConfig, redraw_on_focus_gained: &mut bool, + draw_host_cursor: &mut bool, #[cfg(unix)] remote_image_paste_key: &mut Option<( crossterm::event::KeyCode, crossterm::event::KeyModifiers, @@ -1632,6 +1660,7 @@ fn reload_local_client_config( let loaded_remote_image_paste_key = client_remote_image_paste_key(&loaded.config); *sound_config = loaded.config.ui.sound; *redraw_on_focus_gained = loaded.config.ui.redraw_on_focus_gained; + *draw_host_cursor = should_draw_host_cursor(loaded.config.ui.host_cursor); #[cfg(unix)] { *remote_image_paste_key = loaded_remote_image_paste_key; @@ -2007,6 +2036,27 @@ mod tests { } } + #[test] + fn host_cursor_policy_auto_uses_drawn_cursor_on_windows() { + assert_eq!( + should_draw_host_cursor(crate::config::HostCursorModeConfig::Auto), + cfg!(windows) + ); + } + + #[test] + fn host_cursor_policy_native_and_drawn_override_auto_detection() { + let _guard = env_lock().lock().unwrap(); + let _env = EnvVarGuard::set("TERM_PROGRAM", "WezTerm"); + + assert!(!should_draw_host_cursor( + crate::config::HostCursorModeConfig::Native + )); + assert!(should_draw_host_cursor( + crate::config::HostCursorModeConfig::Drawn + )); + } + #[cfg(unix)] #[test] fn clipboard_image_paste_bridge_triggers_on_configured_key_and_empty_paste() { @@ -2436,7 +2486,7 @@ mod tests { } #[test] - fn reload_local_client_config_refreshes_redraw_on_focus_gained() { + fn reload_local_client_config_refreshes_local_client_presentation_state() { let _guard = crate::config::test_config_env_lock().lock().unwrap(); let path = std::env::temp_dir().join(format!( "herdr-client-config-reload-{}-{}.toml", @@ -2446,22 +2496,29 @@ mod tests { .unwrap() .as_nanos() )); - std::fs::write(&path, "[ui]\nredraw_on_focus_gained = false\n").unwrap(); + std::fs::write( + &path, + "[ui]\nredraw_on_focus_gained = false\nhost_cursor = \"drawn\"\n", + ) + .unwrap(); let path_string = path.to_string_lossy().to_string(); let _env = EnvVarGuard::set(crate::config::CONFIG_PATH_ENV_VAR, &path_string); let mut sound_config = crate::config::SoundConfig::default(); let mut redraw_on_focus_gained = true; + let mut draw_host_cursor = false; #[cfg(unix)] let mut remote_image_paste_key = None; reload_local_client_config( &mut sound_config, &mut redraw_on_focus_gained, + &mut draw_host_cursor, #[cfg(unix)] &mut remote_image_paste_key, ); assert!(!redraw_on_focus_gained); + assert!(draw_host_cursor); let _ = std::fs::remove_file(path); } |
