Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ impl App {
changes_view
}
crate::raw_input::RawInputEvent::OuterFocusGained => {
#[cfg(not(windows))]
self.query_host_terminal_appearance();
self.send_outer_focus_event(crate::ghostty::FocusEvent::Gained);
if self.state.redraw_on_focus_gained {
self.request_repaint();
Expand Down
9 changes: 9 additions & 0 deletions src/app/theme_sync.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
use super::App;

impl App {
#[cfg(not(windows))]
pub(super) fn query_host_terminal_appearance(&self) {
use std::io::Write;

let _ = std::io::stdout()
.write_all(crate::terminal_theme::HOST_COLOR_SCHEME_QUERY_SEQUENCE.as_bytes());
let _ = std::io::stdout().flush();
}

pub(super) fn query_host_terminal_theme(&self) {
use std::io::Write;

Expand Down
1 change: 1 addition & 0 deletions src/client/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ fn unix_stdin_reader_loop(
if host_color_query_sent {
framer.host_color_query_sent();
framer.enable_host_color_scheme_change_tracking();
framer.enable_host_appearance_query_on_focus();
}
if host_cell_size_query_sent {
framer.host_cell_size_query_sent();
Expand Down
25 changes: 25 additions & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,9 @@ async fn run_client_loop(
) {
state.request_repaint();
}
if crate::raw_input::events_require_host_terminal_appearance_query(&events) {
query_host_terminal_appearance();
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}
if crate::raw_input::events_require_host_terminal_theme_query(&events) {
query_host_terminal_theme();
}
Expand Down Expand Up @@ -2281,6 +2284,17 @@ fn resize_poll_loop(
// Logging
// ---------------------------------------------------------------------------

#[cfg(any(not(windows), test))]
fn query_host_terminal_appearance() {
let _ = write_host_terminal_appearance_query(io::stdout());
}

#[cfg(any(not(windows), test))]
fn write_host_terminal_appearance_query(mut writer: impl io::Write) -> io::Result<()> {
writer.write_all(crate::terminal_theme::HOST_COLOR_SCHEME_QUERY_SEQUENCE.as_bytes())?;
writer.flush()
}

/// Initialize logging for the client process.
fn query_host_terminal_theme() {
let _ = write_host_terminal_theme_query(io::stdout());
Expand Down Expand Up @@ -2672,6 +2686,13 @@ mod tests {
assert!(!text.contains("d=A"));
}

#[test]
fn write_host_terminal_appearance_query_emits_mode_2031_query() {
let mut output = Vec::new();
write_host_terminal_appearance_query(&mut output).unwrap();
assert_eq!(output, b"\x1b[?996n");
}

#[test]
fn write_host_terminal_theme_query_emits_osc_queries() {
let mut output = Vec::new();
Expand All @@ -2680,6 +2701,10 @@ mod tests {
output,
crate::terminal_theme::host_terminal_theme_query_sequence().as_bytes()
);
assert!(!output
.windows(crate::terminal_theme::HOST_COLOR_SCHEME_QUERY_SEQUENCE.len())
.any(|window| window
== crate::terminal_theme::HOST_COLOR_SCHEME_QUERY_SEQUENCE.as_bytes()));
}

#[test]
Expand Down
87 changes: 85 additions & 2 deletions src/raw_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ impl RawInputFramer {
self.byte_framer.enable_host_color_scheme_change_tracking();
}

#[cfg(any(not(windows), test))]
pub(crate) fn enable_host_appearance_query_on_focus(&mut self) {
self.byte_framer.enable_host_appearance_query_on_focus();
}

pub(crate) fn has_pending_input(&self) -> bool {
self.byte_framer.has_pending_input()
}
Expand Down Expand Up @@ -218,6 +223,7 @@ pub(crate) struct RawInputByteFramer {
host_cell_size_replies_awaited: u16,
held_pending_host_reply_esc: bool,
host_color_scheme_change_tracking: bool,
host_appearance_query_on_focus: bool,
split_coalesced_escape: bool,
}

Expand Down Expand Up @@ -268,6 +274,13 @@ impl RawInputByteFramer {
self.host_color_scheme_change_tracking = true;
}

/// Arm the bounded host-reply window when focus gain will emit an appearance query.
/// If the write or reply fails, a lone Escape is delayed for only one extra flush.
#[cfg(any(not(windows), test))]
pub(crate) fn enable_host_appearance_query_on_focus(&mut self) {
self.host_appearance_query_on_focus = true;
}

pub(crate) fn has_pending_input(&self) -> bool {
!self.buffer.is_empty()
}
Expand Down Expand Up @@ -522,8 +535,10 @@ impl RawInputByteFramer {
} else if matches!(event, RawInputEvent::HostCellSizeReport { .. }) {
self.host_cell_size_replies_awaited =
self.host_cell_size_replies_awaited.saturating_sub(1);
} else if self.host_color_scheme_change_tracking
&& matches!(event, RawInputEvent::HostColorSchemeChanged(_))
} else if (self.host_appearance_query_on_focus
&& matches!(event, RawInputEvent::OuterFocusGained))
|| (self.host_color_scheme_change_tracking
&& matches!(event, RawInputEvent::HostColorSchemeChanged(_)))
{
self.host_color_query_sent();
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
}
Expand Down Expand Up @@ -579,6 +594,13 @@ pub(crate) fn events_require_host_surface_redraw(
.any(|event| matches!(event, RawInputEvent::OuterFocusGained))
}

#[cfg(any(not(windows), test))]
pub(crate) fn events_require_host_terminal_appearance_query(events: &[RawInputEvent]) -> bool {
events
.iter()
.any(|event| matches!(event, RawInputEvent::OuterFocusGained))
}

#[cfg(any(not(windows), test))]
pub(crate) fn events_require_host_terminal_theme_query(events: &[RawInputEvent]) -> bool {
events
Expand All @@ -604,6 +626,8 @@ pub fn spawn_input_reader() -> mpsc::Receiver<RawInputEvent> {
let mut framer = RawInputFramer::for_host_input();
framer.host_color_query_sent();
framer.enable_host_color_scheme_change_tracking();
#[cfg(not(windows))]
framer.enable_host_appearance_query_on_focus();
let mut pending_palette = Vec::new();

loop {
Expand Down Expand Up @@ -1544,6 +1568,20 @@ mod tests {
assert!(!events_require_host_surface_redraw(&events, true));
}

#[test]
fn outer_focus_gained_requests_host_appearance_query() {
let gained = parse_raw_input_bytes_sync(b"\x1b[I");
let lost = parse_raw_input_bytes_sync(b"\x1b[O");
let scheme_report = parse_raw_input_bytes_sync(b"\x1b[?997;1n");

assert!(events_require_host_terminal_appearance_query(&gained));
assert!(!events_require_host_terminal_appearance_query(&lost));
assert!(!events_require_host_terminal_appearance_query(
&scheme_report
));
assert!(events_require_host_terminal_theme_query(&scheme_report));
}

#[test]
fn parses_ghostty_color_scheme_reports() {
for bytes in [
Expand Down Expand Up @@ -2863,6 +2901,51 @@ mod tests {
assert_eq!(framer.flush_timeout(), vec![b"\x1b".to_vec()]);
}

#[test]
fn opted_in_byte_framer_rearms_after_outer_focus_gained() {
let mut framer = RawInputByteFramer::default();
framer.enable_host_color_scheme_change_tracking();
framer.enable_host_appearance_query_on_focus();

assert_eq!(framer.push(b"\x1b[I"), vec![b"\x1b[I".to_vec()]);
assert!(framer.push(b"\x1b").is_empty());
assert!(framer.flush_timeout().is_empty());
assert_eq!(
framer.push(b"[?997;2n"),
vec![GHOSTTY_COLOR_SCHEME_LIGHT_REPORT.to_vec()]
);
}

#[test]
fn disabled_focus_query_does_not_rearm_byte_framer() {
let mut framer = RawInputByteFramer::default();
framer.enable_host_color_scheme_change_tracking();

assert_eq!(framer.push(b"\x1b[I"), vec![b"\x1b[I".to_vec()]);
assert!(framer.push(b"\x1b").is_empty());
assert_eq!(framer.flush_timeout(), vec![b"\x1b".to_vec()]);
}

#[test]
fn focus_query_policy_does_not_delay_plain_escape_without_focus() {
let mut framer = RawInputByteFramer::default();
framer.enable_host_appearance_query_on_focus();

assert!(framer.push(b"\x1b").is_empty());
assert_eq!(framer.flush_timeout(), vec![b"\x1b".to_vec()]);
}

#[test]
fn focus_query_without_reply_holds_escape_for_only_one_flush() {
let mut framer = RawInputByteFramer::default();
framer.enable_host_appearance_query_on_focus();

assert_eq!(framer.push(b"\x1b[I"), vec![b"\x1b[I".to_vec()]);
assert!(framer.push(b"\x1b").is_empty());
assert!(framer.flush_timeout().is_empty());
assert_eq!(framer.flush_timeout(), vec![b"\x1b".to_vec()]);
}

#[test]
fn opted_in_byte_framer_rearms_after_color_scheme_report() {
let mut framer = RawInputByteFramer::default();
Expand Down
2 changes: 2 additions & 0 deletions src/terminal_theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub enum DefaultColorKind {
}

pub const HOST_COLOR_QUERY_SEQUENCE: &str = "\x1b]10;?\x1b\\\x1b]11;?\x1b\\";
#[cfg(any(not(windows), test))]
pub const HOST_COLOR_SCHEME_QUERY_SEQUENCE: &str = "\x1b[?996n";
pub const HOST_COLOR_SCHEME_REPORT_ENABLE_SEQUENCE: &str = "\x1b[?2031h";
pub const HOST_COLOR_SCHEME_REPORT_DISABLE_SEQUENCE: &str = "\x1b[?2031l";

Expand Down
Loading