diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-08-09 02:04:39 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-09 02:04:39 +0300 |
| commit | 1777e9bba32b953ed1ad203b4a16d01105539000 (patch) | |
| tree | 22096973812c9baba822453d8470bdcfe64fc2a6 /src/pane | |
| parent | 36a8d36a7258e08b40c01bf521b1f4006525f143 (diff) | |
feat(graphics): add direct pane frame streaming (#2523)
* feat(graphics): add direct pane frame streaming
* feat(graphics): expose pane visibility
* test(graphics): preserve legacy terminal image output
* fix(graphics): harden streaming lifecycle
* fix(input): preserve mouse releases outside panes
Diffstat (limited to 'src/pane')
| -rw-r--r-- | src/pane/input.rs | 38 | ||||
| -rw-r--r-- | src/pane/terminal.rs | 140 |
2 files changed, 105 insertions, 73 deletions
diff --git a/src/pane/input.rs b/src/pane/input.rs index 2e60bfc8..2ff299c9 100644 --- a/src/pane/input.rs +++ b/src/pane/input.rs @@ -60,23 +60,43 @@ pub(super) fn ghostty_mods_from_key_modifiers(modifiers: crossterm::event::KeyMo pub(super) fn ghostty_mouse_encoder_for_terminal( terminal: &crate::ghostty::Terminal, + position: crate::input::mouse::Position, ) -> Option<crate::ghostty::MouseEncoder> { let mut encoder = crate::ghostty::MouseEncoder::new().ok()?; encoder.set_from_terminal(terminal); - if terminal - .mode_get(crate::ghostty::MODE_MOUSE_SGR_PIXELS) - .ok()? - { - // Herdr receives host mouse positions in terminal cells. Downgrade - // SGR-pixels to normal SGR so forwarded coordinates stay cell-local. - encoder.set_format(crate::ghostty::MOUSE_FORMAT_SGR); - } let cols = terminal.cols().ok()? as u32; let rows = terminal.rows().ok()? as u32; - encoder.set_size(cols, rows, 1, 1); + match position { + crate::input::mouse::Position::Cell { .. } => { + if terminal + .mode_get(crate::ghostty::MODE_MOUSE_SGR_PIXELS) + .ok()? + { + encoder.set_format(crate::ghostty::MOUSE_FORMAT_SGR); + } + encoder.set_size(cols, rows, 1, 1); + } + crate::input::mouse::Position::Pixels { .. } => { + let width_px = terminal.width_px().ok()?; + let height_px = terminal.height_px().ok()?; + if width_px == 0 || height_px == 0 || cols == 0 || rows == 0 { + return None; + } + encoder.set_size(width_px, height_px, width_px / cols, height_px / rows); + } + } Some(encoder) } +pub(super) fn ghostty_mouse_position_for_terminal( + position: crate::input::mouse::Position, +) -> Option<(f32, f32)> { + match position { + crate::input::mouse::Position::Pixels { x, y } => Some((x as f32, y as f32)), + crate::input::mouse::Position::Cell { column, row } => Some((column as f32, row as f32)), + } +} + pub(super) fn ghostty_mouse_event_from_button_kind( kind: crossterm::event::MouseEventKind, column: u16, diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index 8de84af3..91fcb4fe 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -23,7 +23,8 @@ use super::{ input::{ ghostty_key_event_from_terminal_key, ghostty_mouse_encoder_for_terminal, ghostty_mouse_event_from_button_kind, ghostty_mouse_event_from_motion_kind, - ghostty_mouse_event_from_wheel_kind, ghostty_prefers_herdr_text_encoding, + ghostty_mouse_event_from_wheel_kind, ghostty_mouse_position_for_terminal, + ghostty_prefers_herdr_text_encoding, }, kitty_keyboard::KittyKeyboardTracker, osc::{ @@ -537,37 +538,31 @@ impl PaneTerminal { self.ghostty.encode_terminal_key(key, protocol) } - pub fn encode_mouse_button( + pub(crate) fn encode_mouse_button( &self, kind: crossterm::event::MouseEventKind, - column: u16, - row: u16, + position: crate::input::mouse::Position, modifiers: crossterm::event::KeyModifiers, ) -> Option<Vec<u8>> { - self.ghostty - .encode_mouse_button(kind, column, row, modifiers) + self.ghostty.encode_mouse_button(kind, position, modifiers) } - pub fn encode_mouse_motion( + pub(crate) fn encode_mouse_motion( &self, kind: crossterm::event::MouseEventKind, - column: u16, - row: u16, + position: crate::input::mouse::Position, modifiers: crossterm::event::KeyModifiers, ) -> Option<Vec<u8>> { - self.ghostty - .encode_mouse_motion(kind, column, row, modifiers) + self.ghostty.encode_mouse_motion(kind, position, modifiers) } - pub fn encode_mouse_wheel( + pub(crate) fn encode_mouse_wheel( &self, kind: crossterm::event::MouseEventKind, - column: u16, - row: u16, + position: crate::input::mouse::Position, modifiers: crossterm::event::KeyModifiers, ) -> Option<Vec<u8>> { - self.ghostty - .encode_mouse_wheel(kind, column, row, modifiers) + self.ghostty.encode_mouse_wheel(kind, position, modifiers) } } @@ -1488,6 +1483,9 @@ impl GhosttyPaneTerminal { let _ = core .terminal .mode_set(crate::ghostty::MODE_MOUSE_SGR, false); + let _ = core + .terminal + .mode_set(crate::ghostty::MODE_MOUSE_SGR_PIXELS, false); match input_state.mouse_protocol_encoding { crate::input::MouseProtocolEncoding::Default => {} crate::input::MouseProtocolEncoding::Utf8 => { @@ -1498,6 +1496,11 @@ impl GhosttyPaneTerminal { crate::input::MouseProtocolEncoding::Sgr => { let _ = core.terminal.mode_set(crate::ghostty::MODE_MOUSE_SGR, true); } + crate::input::MouseProtocolEncoding::SgrPixels => { + let _ = core + .terminal + .mode_set(crate::ghostty::MODE_MOUSE_SGR_PIXELS, true); + } } if input_state.modify_other_keys { @@ -1688,6 +1691,10 @@ impl GhosttyPaneTerminal { .terminal .mode_get(crate::ghostty::MODE_MOUSE_UTF8) .ok()?; + let mouse_sgr_pixels = core + .terminal + .mode_get(crate::ghostty::MODE_MOUSE_SGR_PIXELS) + .ok()?; let mouse_alternate_scroll = core .terminal .mode_get(crate::ghostty::MODE_MOUSE_ALTERNATE_SCROLL) @@ -1703,7 +1710,9 @@ impl GhosttyPaneTerminal { } else { crate::input::MouseProtocolMode::None }; - let mouse_protocol_encoding = if mouse_sgr { + let mouse_protocol_encoding = if mouse_sgr_pixels { + crate::input::MouseProtocolEncoding::SgrPixels + } else if mouse_sgr { crate::input::MouseProtocolEncoding::Sgr } else if mouse_utf8 { crate::input::MouseProtocolEncoding::Utf8 @@ -1838,57 +1847,58 @@ impl GhosttyPaneTerminal { } } - pub fn encode_mouse_button( + pub(crate) fn encode_mouse_button( &self, kind: crossterm::event::MouseEventKind, - column: u16, - row: u16, + position: crate::input::mouse::Position, modifiers: crossterm::event::KeyModifiers, ) -> Option<Vec<u8>> { - let Ok(core) = self.core.lock() else { - return None; - }; - let mut encoder = ghostty_mouse_encoder_for_terminal(&core.terminal)?; - let event = ghostty_mouse_event_from_button_kind(kind, column, row, modifiers)?; - encoder - .encode(&event) - .ok() - .filter(|bytes| !bytes.is_empty()) + self.encode_mouse_event( + ghostty_mouse_event_from_button_kind(kind, 0, 0, modifiers)?, + position, + false, + ) } - pub fn encode_mouse_motion( + pub(crate) fn encode_mouse_motion( &self, kind: crossterm::event::MouseEventKind, - column: u16, - row: u16, + position: crate::input::mouse::Position, modifiers: crossterm::event::KeyModifiers, ) -> Option<Vec<u8>> { - let Ok(core) = self.core.lock() else { - return None; - }; - if !core.terminal.mode_get(MODE_MOUSE_ANY_MOTION).ok()? { - return None; - } - let mut encoder = ghostty_mouse_encoder_for_terminal(&core.terminal)?; - let event = ghostty_mouse_event_from_motion_kind(kind, column, row, modifiers)?; - encoder - .encode(&event) - .ok() - .filter(|bytes| !bytes.is_empty()) + self.encode_mouse_event( + ghostty_mouse_event_from_motion_kind(kind, 0, 0, modifiers)?, + position, + true, + ) } - pub fn encode_mouse_wheel( + pub(crate) fn encode_mouse_wheel( &self, kind: crossterm::event::MouseEventKind, - column: u16, - row: u16, + position: crate::input::mouse::Position, modifiers: crossterm::event::KeyModifiers, ) -> Option<Vec<u8>> { - let Ok(core) = self.core.lock() else { + self.encode_mouse_event( + ghostty_mouse_event_from_wheel_kind(kind, 0, 0, modifiers)?, + position, + false, + ) + } + + fn encode_mouse_event( + &self, + mut event: crate::ghostty::MouseEvent, + position: crate::input::mouse::Position, + require_any_motion: bool, + ) -> Option<Vec<u8>> { + let core = self.core.lock().ok()?; + if require_any_motion && !core.terminal.mode_get(MODE_MOUSE_ANY_MOTION).ok()? { return None; - }; - let mut encoder = ghostty_mouse_encoder_for_terminal(&core.terminal)?; - let event = ghostty_mouse_event_from_wheel_kind(kind, column, row, modifiers)?; + } + let mut encoder = ghostty_mouse_encoder_for_terminal(&core.terminal, position)?; + let (x, y) = ghostty_mouse_position_for_terminal(position)?; + event.set_position(x, y); encoder .encode(&event) .ok() @@ -4670,8 +4680,7 @@ mod tests { let encoded = pane.encode_mouse_button( crossterm::event::MouseEventKind::Up(crossterm::event::MouseButton::Left), - 11, - 9, + crate::input::mouse::Position::Cell { column: 11, row: 9 }, crossterm::event::KeyModifiers::empty(), ); @@ -4687,8 +4696,7 @@ mod tests { let encoded = pane.encode_mouse_button( crossterm::event::MouseEventKind::Drag(crossterm::event::MouseButton::Left), - 4, - 6, + crate::input::mouse::Position::Cell { column: 4, row: 6 }, crossterm::event::KeyModifiers::SHIFT, ); @@ -4704,8 +4712,7 @@ mod tests { let encoded = pane.encode_mouse_button( crossterm::event::MouseEventKind::Drag(crossterm::event::MouseButton::Left), - 4, - 6, + crate::input::mouse::Position::Cell { column: 4, row: 6 }, crossterm::event::KeyModifiers::empty(), ); @@ -4721,8 +4728,7 @@ mod tests { let encoded = pane.encode_mouse_motion( crossterm::event::MouseEventKind::Moved, - 4, - 6, + crate::input::mouse::Position::Cell { column: 4, row: 6 }, crossterm::event::KeyModifiers::empty(), ); @@ -4730,20 +4736,26 @@ mod tests { } #[test] - fn ghostty_mouse_sgr_pixels_downgrades_to_cell_coordinates() { + fn ghostty_mouse_sgr_pixels_preserves_exact_and_downgrades_cell_input() { let (tx, _rx) = mpsc::channel(4); let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + terminal.resize(80, 24, 10, 20).unwrap(); terminal.write(b"\x1b[?1003h\x1b[?1006h\x1b[?1016h"); let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); - let encoded = pane.encode_mouse_motion( + let exact = pane.encode_mouse_motion( crossterm::event::MouseEventKind::Moved, - 4, - 6, + crate::input::mouse::Position::Pixels { x: 48, y: 139 }, + crossterm::event::KeyModifiers::empty(), + ); + let fallback = pane.encode_mouse_motion( + crossterm::event::MouseEventKind::Moved, + crate::input::mouse::Position::Cell { column: 4, row: 6 }, crossterm::event::KeyModifiers::empty(), ); - assert_eq!(encoded.as_deref(), Some(&b"\x1b[<35;5;7M"[..])); + assert_eq!(exact.as_deref(), Some(&b"\x1b[<35;48;139M"[..])); + assert_eq!(fallback.as_deref(), Some(&b"\x1b[<35;5;7M"[..])); } #[test] |
