diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 02:24:57 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 02:32:00 +0300 |
| commit | a08dfaef2f13873fc85dc9e1357235b8b0703a46 (patch) | |
| tree | a34a2ada492da80f5efe67eab08b34c3ca249a07 /src/pane.rs | |
| parent | 19239c6c87f190a38796b6d7a2e15620a97b70b7 (diff) | |
feat: forward ghostty pane focus events on focus changes
Diffstat (limited to 'src/pane.rs')
| -rw-r--r-- | src/pane.rs | 71 |
1 files changed, 71 insertions, 0 deletions
diff --git a/src/pane.rs b/src/pane.rs index bba39350..0ef46a8f 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -272,6 +272,7 @@ pub struct InputState { pub alternate_screen: bool, pub application_cursor: bool, pub bracketed_paste: bool, + pub focus_reporting: bool, pub mouse_protocol_mode: vt100::MouseProtocolMode, pub mouse_protocol_encoding: vt100::MouseProtocolEncoding, pub mouse_alternate_scroll: bool, @@ -593,6 +594,7 @@ impl VtPaneTerminal { alternate_screen: screen.alternate_screen(), application_cursor: screen.application_cursor(), bracketed_paste: screen.bracketed_paste(), + focus_reporting: false, mouse_protocol_mode: screen.mouse_protocol_mode(), mouse_protocol_encoding: screen.mouse_protocol_encoding(), mouse_alternate_scroll: self.mouse_alternate_scroll.load(Ordering::Relaxed), @@ -772,6 +774,10 @@ impl GhosttyPaneTerminal { .terminal .mode_get(crate::ghostty::MODE_BRACKETED_PASTE) .ok()?; + let focus_reporting = core + .terminal + .mode_get(crate::ghostty::MODE_FOCUS_EVENT) + .ok()?; let mouse_sgr = core.terminal.mode_get(crate::ghostty::MODE_MOUSE_SGR).ok()?; let mouse_utf8 = core.terminal.mode_get(crate::ghostty::MODE_MOUSE_UTF8).ok()?; let mouse_alternate_scroll = core @@ -800,6 +806,7 @@ impl GhosttyPaneTerminal { alternate_screen, application_cursor, bracketed_paste, + focus_reporting, mouse_protocol_mode, mouse_protocol_encoding, mouse_alternate_scroll, @@ -2008,6 +2015,25 @@ impl PaneRuntime { self.send_bytes(Bytes::from(payload)).await } + #[cfg(feature = "ghostty-vt")] + pub fn try_send_focus_event(&self, event: crate::ghostty::FocusEvent) -> bool { + if !self + .input_state() + .map(|state| state.focus_reporting) + .unwrap_or(false) + { + return false; + } + + let Ok(bytes) = crate::ghostty::encode_focus(event) else { + return false; + }; + if let Err(err) = self.try_send_bytes(Bytes::from(bytes)) { + warn!(err = %err, ?event, "failed to forward pane focus event"); + } + true + } + pub fn wheel_routing(&self) -> Option<WheelRouting> { let input_state = self.input_state()?; Some(if input_state.mouse_reporting_enabled() { @@ -2183,6 +2209,51 @@ mod tests { assert_eq!(encoded.as_deref(), Some(&b"\x1b[<36;5;7M"[..])); } + #[cfg(feature = "ghostty-vt")] + #[tokio::test] + async fn focus_events_are_forwarded_when_enabled() { + let (tx, mut rx) = mpsc::channel(4); + let (resize_tx, _resize_rx) = mpsc::channel(1); + let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + terminal.mode_set(crate::ghostty::MODE_FOCUS_EVENT, true).unwrap(); + let runtime = PaneRuntime { + terminal: Arc::new(PaneTerminal::Ghostty(GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap())), + sender: tx, + resize_tx, + current_size: Cell::new((80, 24)), + child_pid: Arc::new(AtomicU32::new(0)), + kitty_keyboard_flags: Arc::new(AtomicU16::new(0)), + detect_reset_notify: Arc::new(Notify::new()), + pending_release: Arc::new(Mutex::new(None)), + detect_handle: tokio::spawn(async {}).abort_handle(), + }; + + assert!(runtime.try_send_focus_event(crate::ghostty::FocusEvent::Gained)); + assert_eq!(rx.recv().await.unwrap(), Bytes::from_static(b"\x1b[I")); + } + + #[cfg(feature = "ghostty-vt")] + #[tokio::test] + async fn focus_events_are_suppressed_when_disabled() { + let (tx, mut rx) = mpsc::channel(4); + let (resize_tx, _resize_rx) = mpsc::channel(1); + let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + let runtime = PaneRuntime { + terminal: Arc::new(PaneTerminal::Ghostty(GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap())), + sender: tx, + resize_tx, + current_size: Cell::new((80, 24)), + child_pid: Arc::new(AtomicU32::new(0)), + kitty_keyboard_flags: Arc::new(AtomicU16::new(0)), + detect_reset_notify: Arc::new(Notify::new()), + pending_release: Arc::new(Mutex::new(None)), + detect_handle: tokio::spawn(async {}).abort_handle(), + }; + + assert!(!runtime.try_send_focus_event(crate::ghostty::FocusEvent::Gained)); + assert!(tokio::time::timeout(std::time::Duration::from_millis(10), rx.recv()).await.is_err()); + } + #[test] fn recent_text_reconstructs_scrollback_tail() { let responses = PtyResponses::new(); |
