aboutsummaryrefslogtreecommitdiff
path: root/src/app/mod.rs
diff options
context:
space:
mode:
authorakbash <akbash@herdr.dev>2026-07-14 01:06:11 +0300
committerGitHub <noreply@github.com>2026-07-14 01:06:11 +0300
commit2e5da968ea3fb7105c56f7674caf822de41ec176 (patch)
treee6d5d7dab9f7262c641a275536b5adf6ada0957d /src/app/mod.rs
parent3a8490f6515dfea13292ae28e34f1174d2f68af1 (diff)
fix: forward outer focus events to panes (#1388)
* fix: forward outer focus events to panes refs #1337 * fix: preserve ordered focus forwarding refs #1337 --------- Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com> Co-authored-by: Ogulcan Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/app/mod.rs')
-rw-r--r--src/app/mod.rs120
1 files changed, 118 insertions, 2 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 16cfeafb..6a7470be 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -1617,8 +1617,12 @@ impl App {
}
}
}
- crate::raw_input::RawInputEvent::OuterFocusGained
- | crate::raw_input::RawInputEvent::OuterFocusLost => {}
+ crate::raw_input::RawInputEvent::OuterFocusGained => {
+ self.send_outer_focus_event(crate::ghostty::FocusEvent::Gained);
+ }
+ crate::raw_input::RawInputEvent::OuterFocusLost => {
+ self.send_outer_focus_event(crate::ghostty::FocusEvent::Lost);
+ }
crate::raw_input::RawInputEvent::HostDefaultColor { kind, color } => {
if apply_host_terminal_theme {
self.update_host_terminal_theme(kind, color);
@@ -3263,6 +3267,118 @@ mod tests {
}
#[tokio::test]
+ async fn monolithic_outer_focus_events_reach_reporting_pane() {
+ let mut app = test_app();
+ let mut workspace = Workspace::test_new("focus-reporting");
+ let pane_id = workspace.tabs[0].root_pane;
+ let (runtime, mut input_rx) =
+ crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes(
+ 80,
+ 24,
+ 0,
+ b"\x1b[?1004h",
+ 4,
+ );
+ workspace.insert_test_runtime(pane_id, runtime);
+ app.state.workspaces = vec![workspace];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.mode = Mode::Terminal;
+
+ assert!(
+ app.handle_raw_input_event(crate::raw_input::RawInputEvent::OuterFocusGained)
+ .await
+ );
+ assert_eq!(
+ input_rx
+ .recv()
+ .await
+ .expect("forwarded focus gained report"),
+ bytes::Bytes::from_static(b"\x1b[I")
+ );
+
+ assert!(
+ !app.handle_raw_input_event(crate::raw_input::RawInputEvent::OuterFocusLost)
+ .await
+ );
+ assert_eq!(
+ input_rx.recv().await.expect("forwarded focus lost report"),
+ bytes::Bytes::from_static(b"\x1b[O")
+ );
+ }
+
+ #[tokio::test]
+ async fn outer_focus_events_reconcile_pending_pane_focus() {
+ let mut app = test_app();
+ let mut workspace = Workspace::test_new("focus-transition");
+ let previous_pane = workspace.tabs[0].root_pane;
+ let next_pane = workspace.test_split(ratatui::layout::Direction::Horizontal);
+ workspace.tabs[0].layout.focus_pane(previous_pane);
+ let (runtime, mut input_rx) =
+ crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes(
+ 80,
+ 24,
+ 0,
+ b"\x1b[?1004h",
+ 4,
+ );
+ workspace.insert_test_runtime(next_pane, runtime);
+ app.state.workspaces = vec![workspace];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.mode = Mode::Terminal;
+ app.last_focus = Some((0, previous_pane));
+
+ assert!(app.state.focus_pane_in_workspace(0, next_pane));
+ assert!(
+ !app.handle_raw_input_event(crate::raw_input::RawInputEvent::OuterFocusLost)
+ .await
+ );
+ app.sync_focus_events();
+ assert_eq!(
+ input_rx.try_recv().unwrap(),
+ bytes::Bytes::from_static(b"\x1b[O")
+ );
+ assert!(input_rx.try_recv().is_err());
+
+ assert!(app.state.focus_pane_in_workspace(0, previous_pane));
+ app.sync_focus_events();
+ assert_eq!(
+ input_rx.try_recv().unwrap(),
+ bytes::Bytes::from_static(b"\x1b[O")
+ );
+
+ assert!(app.state.focus_pane_in_workspace(0, next_pane));
+ app.sync_focus_events();
+ assert_eq!(
+ input_rx.try_recv().unwrap(),
+ bytes::Bytes::from_static(b"\x1b[O")
+ );
+
+ assert!(
+ app.handle_raw_input_event(crate::raw_input::RawInputEvent::OuterFocusGained)
+ .await
+ );
+ assert_eq!(
+ input_rx.try_recv().unwrap(),
+ bytes::Bytes::from_static(b"\x1b[I")
+ );
+
+ assert!(app.state.focus_pane_in_workspace(0, previous_pane));
+ app.sync_focus_events();
+ assert_eq!(
+ input_rx.try_recv().unwrap(),
+ bytes::Bytes::from_static(b"\x1b[O")
+ );
+ assert!(app.state.focus_pane_in_workspace(0, next_pane));
+ app.sync_focus_events();
+ assert_eq!(
+ input_rx.try_recv().unwrap(),
+ bytes::Bytes::from_static(b"\x1b[I")
+ );
+ }
+
+ #[tokio::test]
async fn repeat_key_events_are_ignored_outside_terminal_mode() {
let mut app = test_app();
app.state.mode = Mode::ReleaseNotes;