Skip to content

Commit 07b8bdf

Browse files
tui: patch crossterm for better color queries (#5935)
See crossterm-rs/crossterm@master...nornagon:crossterm:nornagon/color-query This patches crossterm to add support for querying fg/bg color as part of the crossterm event loop, which fixes some issues where this query would fight with other input. - dragging screenshots into the cli would sometimes paste half of the pathname instead of being recognized as an image (#5603) - Fixes #4945
1 parent 0f22067 commit 07b8bdf

File tree

4 files changed

+15
-121
lines changed

4 files changed

+15
-121
lines changed

codex-rs/Cargo.lock

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ opt-level = 0
276276
# Uncomment to debug local changes.
277277
# ratatui = { path = "../../ratatui" }
278278
ratatui = { git = "https://github.com/nornagon/ratatui", branch = "nornagon-v0.29.0-patch" }
279+
crossterm = { git = "https://github.com/nornagon/crossterm", branch = "nornagon/color-query" }
279280

280281
# Uncomment to debug local changes.
281282
# rmcp = { path = "../../rust-sdk/crates/rmcp" }

codex-rs/tui/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ codex-protocol = { workspace = true }
4141
codex-app-server-protocol = { workspace = true }
4242
codex-feedback = { workspace = true }
4343
color-eyre = { workspace = true }
44-
crossterm = { workspace = true, features = ["bracketed-paste", "event-stream"] }
44+
crossterm = { workspace = true, features = [
45+
"bracketed-paste",
46+
"event-stream",
47+
] }
4548
diffy = { workspace = true }
4649
dirs = { workspace = true }
4750
dunce = { workspace = true }

codex-rs/tui/src/terminal_palette.rs

Lines changed: 9 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ pub fn default_bg() -> Option<(u8, u8, u8)> {
5050
#[cfg(all(unix, not(test)))]
5151
mod imp {
5252
use super::DefaultColors;
53+
use crossterm::style::Color as CrosstermColor;
54+
use crossterm::style::query_background_color;
55+
use crossterm::style::query_foreground_color;
5356
use std::sync::Mutex;
5457
use std::sync::OnceLock;
5558

@@ -105,128 +108,16 @@ mod imp {
105108
}
106109

107110
fn query_default_colors() -> std::io::Result<Option<DefaultColors>> {
108-
use std::fs::OpenOptions;
109-
use std::io::ErrorKind;
110-
use std::io::IsTerminal;
111-
use std::io::Read;
112-
use std::io::Write;
113-
use std::os::fd::AsRawFd;
114-
use std::time::Duration;
115-
use std::time::Instant;
116-
117-
let mut stdout_handle = std::io::stdout();
118-
if !stdout_handle.is_terminal() {
119-
return Ok(None);
120-
}
121-
122-
let mut tty = match OpenOptions::new().read(true).open("/dev/tty") {
123-
Ok(file) => file,
124-
Err(_) => return Ok(None),
125-
};
126-
127-
let fd = tty.as_raw_fd();
128-
unsafe {
129-
let flags = libc::fcntl(fd, libc::F_GETFL);
130-
if flags >= 0 {
131-
libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
132-
}
133-
}
134-
135-
stdout_handle.write_all(b"\x1b]10;?\x07\x1b]11;?\x07")?;
136-
stdout_handle.flush()?;
137-
138-
let mut deadline = Instant::now() + Duration::from_millis(200);
139-
let mut buffer = Vec::new();
140-
let mut fg = None;
141-
let mut bg = None;
142-
143-
while Instant::now() < deadline {
144-
let mut chunk = [0u8; 128];
145-
match tty.read(&mut chunk) {
146-
Ok(0) => break,
147-
Ok(n) => {
148-
deadline = Instant::now() + Duration::from_millis(200);
149-
buffer.extend_from_slice(&chunk[..n]);
150-
if fg.is_none() {
151-
fg = parse_osc_color(&buffer, 10);
152-
}
153-
if bg.is_none() {
154-
bg = parse_osc_color(&buffer, 11);
155-
}
156-
if let (Some(fg), Some(bg)) = (fg, bg) {
157-
return Ok(Some(DefaultColors { fg, bg }));
158-
}
159-
}
160-
Err(err) if err.kind() == ErrorKind::WouldBlock => {
161-
std::thread::sleep(Duration::from_millis(5));
162-
}
163-
Err(err) if err.kind() == ErrorKind::Interrupted => continue,
164-
Err(_) => break,
165-
}
166-
}
167-
168-
if fg.is_none() {
169-
fg = parse_osc_color(&buffer, 10);
170-
}
171-
if bg.is_none() {
172-
bg = parse_osc_color(&buffer, 11);
173-
}
174-
111+
let fg = query_foreground_color()?.and_then(color_to_tuple);
112+
let bg = query_background_color()?.and_then(color_to_tuple);
175113
Ok(fg.zip(bg).map(|(fg, bg)| DefaultColors { fg, bg }))
176114
}
177115

178-
fn parse_component(component: &str) -> Option<u8> {
179-
let trimmed = component.trim();
180-
if trimmed.is_empty() {
181-
return None;
182-
}
183-
let bits = trimmed.len().checked_mul(4)?;
184-
if bits == 0 || bits > 64 {
185-
return None;
186-
}
187-
let max = if bits == 64 {
188-
u64::MAX
189-
} else {
190-
(1u64 << bits) - 1
191-
};
192-
let value = u64::from_str_radix(trimmed, 16).ok()?;
193-
Some(((value * 255 + max / 2) / max) as u8)
194-
}
195-
196-
fn parse_osc_color(buffer: &[u8], code: u8) -> Option<(u8, u8, u8)> {
197-
let text = std::str::from_utf8(buffer).ok()?;
198-
let prefix = match code {
199-
10 => "\u{1b}]10;",
200-
11 => "\u{1b}]11;",
201-
_ => return None,
202-
};
203-
let start = text.rfind(prefix)?;
204-
let after_prefix = &text[start + prefix.len()..];
205-
let end_bel = after_prefix.find('\u{7}');
206-
let end_st = after_prefix.find("\u{1b}\\");
207-
let end_idx = match (end_bel, end_st) {
208-
(Some(bel), Some(st)) => bel.min(st),
209-
(Some(bel), None) => bel,
210-
(None, Some(st)) => st,
211-
(None, None) => return None,
212-
};
213-
let payload = after_prefix[..end_idx].trim();
214-
parse_color_payload(payload)
215-
}
216-
217-
fn parse_color_payload(payload: &str) -> Option<(u8, u8, u8)> {
218-
if payload.is_empty() || payload == "?" {
219-
return None;
220-
}
221-
let (model, values) = payload.split_once(':')?;
222-
if model != "rgb" && model != "rgba" {
223-
return None;
116+
fn color_to_tuple(color: CrosstermColor) -> Option<(u8, u8, u8)> {
117+
match color {
118+
CrosstermColor::Rgb { r, g, b } => Some((r, g, b)),
119+
_ => None,
224120
}
225-
let mut parts = values.split('/');
226-
let r = parse_component(parts.next()?)?;
227-
let g = parse_component(parts.next()?)?;
228-
let b = parse_component(parts.next()?)?;
229-
Some((r, g, b))
230121
}
231122
}
232123

0 commit comments

Comments
 (0)