aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/input.rs3
-rw-r--r--src/app/mod.rs24
-rw-r--r--src/ghostty/mod.rs1
-rw-r--r--src/pane.rs71
4 files changed, 99 insertions, 0 deletions
diff --git a/src/app/input.rs b/src/app/input.rs
index d077909f..688a2a05 100644
--- a/src/app/input.rs
+++ b/src/app/input.rs
@@ -3366,6 +3366,7 @@ mod tests {
alternate_screen: true,
application_cursor: false,
bracketed_paste: false,
+ focus_reporting: false,
mouse_protocol_mode: vt100::MouseProtocolMode::ButtonMotion,
mouse_protocol_encoding: vt100::MouseProtocolEncoding::Sgr,
mouse_alternate_scroll: true,
@@ -3380,6 +3381,7 @@ mod tests {
alternate_screen: true,
application_cursor: false,
bracketed_paste: false,
+ focus_reporting: false,
mouse_protocol_mode: vt100::MouseProtocolMode::None,
mouse_protocol_encoding: vt100::MouseProtocolEncoding::Default,
mouse_alternate_scroll: true,
@@ -3394,6 +3396,7 @@ mod tests {
alternate_screen: false,
application_cursor: false,
bracketed_paste: false,
+ focus_reporting: false,
mouse_protocol_mode: vt100::MouseProtocolMode::None,
mouse_protocol_encoding: vt100::MouseProtocolEncoding::Default,
mouse_alternate_scroll: true,
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 914c6ca4..b53024b1 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -738,7 +738,13 @@ impl App {
return;
}
+ #[cfg(feature = "ghostty-vt")]
+ if let Some((ws_idx, pane_id)) = self.last_focus {
+ self.send_pane_focus_event(ws_idx, pane_id, crate::ghostty::FocusEvent::Lost);
+ }
if let Some((ws_idx, pane_id)) = current_focus {
+ #[cfg(feature = "ghostty-vt")]
+ self.send_pane_focus_event(ws_idx, pane_id, crate::ghostty::FocusEvent::Gained);
self.emit_event(crate::api::schema::EventEnvelope {
event: crate::api::schema::EventKind::WorkspaceFocused,
data: crate::api::schema::EventData::WorkspaceFocused {
@@ -770,6 +776,24 @@ impl App {
self.last_focus = current_focus;
}
+ #[cfg(feature = "ghostty-vt")]
+ fn send_pane_focus_event(
+ &self,
+ ws_idx: usize,
+ pane_id: crate::layout::PaneId,
+ event: crate::ghostty::FocusEvent,
+ ) {
+ let Some(runtime) = self
+ .state
+ .workspaces
+ .get(ws_idx)
+ .and_then(|ws| ws.runtime(pane_id))
+ else {
+ return;
+ };
+ runtime.try_send_focus_event(event);
+ }
+
fn find_pane(
&self,
pane_id: crate::layout::PaneId,
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs
index 8c5e591d..718abd0c 100644
--- a/src/ghostty/mod.rs
+++ b/src/ghostty/mod.rs
@@ -121,6 +121,7 @@ pub const MOUSE_BUTTON_WHEEL_RIGHT: ffi::GhosttyMouseButton =
ffi::GhosttyMouseButton_GHOSTTY_MOUSE_BUTTON_SEVEN;
pub const MODE_APPLICATION_CURSOR_KEYS: u16 = 1;
+pub const MODE_FOCUS_EVENT: u16 = 1004;
pub const MODE_BRACKETED_PASTE: u16 = 2004;
pub const MODE_MOUSE_UTF8: u16 = 1005;
pub const MODE_MOUSE_SGR: u16 = 1006;
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();