1mod error;
2
3use {nix::errno::Errno, std::os::fd::BorrowedFd};
4
5pub use error::*;
6
7pub fn query<MS: Into<u64>>(query: &str, timeout_ms: MS) -> Result<String, XQError> {
10 const N: usize = 100;
14 let mut response = [0; N];
15 let n = query_buffer(query, &mut response, timeout_ms.into())?;
16 let s = std::str::from_utf8(&response[..n])?;
17 Ok(s.to_string())
18}
19pub fn query_osc<MS: Into<u64>>(query: &str, timeout_ms: MS) -> Result<String, XQError> {
26 const N: usize = 100;
30 let mut response = [0; N];
31 let resp = query_osc_buffer(query, &mut response, timeout_ms.into())?;
32 let s = std::str::from_utf8(resp)?;
33 Ok(s.to_string())
34}
35
36#[cfg(unix)]
41pub fn query_buffer<MS: Into<u64>>(
42 query: &str,
43 buffer: &mut [u8],
44 timeout_ms: MS,
45) -> Result<usize, XQError> {
46 use std::{
47 fs::File,
48 io::{self, Read, Write},
49 os::fd::AsFd,
50 };
51 let stdout = io::stdout();
52 let mut stdout = stdout.lock();
53 write!(stdout, "{}", query)?;
54 stdout.flush()?;
55 let mut stdin = File::open("/dev/tty")?;
56 let stdin_fd = stdin.as_fd();
57 match wait_for_input(stdin_fd, timeout_ms) {
58 Ok(0) => Err(XQError::Timeout),
59 Ok(_) => {
60 let bytes_written = stdin.read(buffer)?;
61 Ok(bytes_written)
62 }
63 Err(e) => Err(XQError::IO(e.into())),
64 }
65}
66
67#[cfg(unix)]
79pub fn query_osc_buffer<'b, MS: Into<u64> + Copy>(
80 query: &str,
81 buffer: &'b mut [u8],
82 timeout_ms: MS,
83) -> Result<&'b [u8], XQError> {
84 use std::{
85 fs::File,
86 io::{self, Read, Write},
87 os::fd::AsFd,
88 };
89 const ESC: char = '\x1b';
90 const BEL: char = '\x07';
91
92 let term = std::env::var("TERM").map_err(|_| XQError::Unsupported)?;
94 if term == "dumb" {
95 return Err(XQError::Unsupported);
96 }
97 let is_screen = term.starts_with("screen");
98
99 let stdout = io::stdout();
100 let mut stdout = stdout.lock();
101
102 if is_screen {
106 write!(stdout, "{ESC}P")?;
107 }
108
109 write!(stdout, "{}", query)?;
110 write!(stdout, "{ESC}[5n")?;
115
116 if is_screen {
117 write!(stdout, "{ESC}\\")?;
118 }
119
120 stdout.flush()?;
121 let mut stdin = File::open("/dev/tty")?;
122 let mut osc_start_idx = None;
123 let mut osc_end_idx = None;
124 let mut bytes_written = 0;
125 while bytes_written < buffer.len() {
126 let stdin_fd = stdin.as_fd();
127 match wait_for_input(stdin_fd, timeout_ms) {
128 Ok(0) => {
129 return Err(XQError::Timeout);
130 }
131 Ok(_) => {
132 let bytes_read = stdin.read(&mut buffer[bytes_written..])?;
133 if bytes_read == 0 {
134 return Err(XQError::NotAnOSCResponse); }
136 for i in bytes_written..bytes_written + bytes_read {
139 let b = buffer[i];
140 match osc_start_idx {
141 None => {
142 if b == ESC as u8 {
143 osc_start_idx = Some(i);
144 }
145 }
146 Some(start_idx) => {
147 if b == ESC as u8 || b == BEL as u8 {
148 if osc_end_idx.is_none() {
149 osc_end_idx = Some(i);
150 }
151 } else if b == b'n' {
152 match osc_end_idx {
153 None => return Err(XQError::NotAnOSCResponse),
154 Some(end_idx) => {
155 return Ok(&buffer[start_idx + 1..=end_idx]);
156 }
157 }
158 }
159 }
160 }
161 }
162 bytes_written += bytes_read;
163 }
164 Err(e) => {
165 return Err(XQError::IO(e.into()));
166 }
167 }
168 }
169 Err(XQError::BufferOverflow)
170}
171
172#[cfg(not(unix))]
173pub fn query_buffer(_query: &str, _buffer: &mut [u8], _timeout_ms: u64) -> Result<usize, XQError> {
174 Err(XQError::Unsupported)
175}
176
177#[cfg(not(target_os = "macos"))]
178fn wait_for_input<MS: Into<u64>>(fd: BorrowedFd<'_>, timeout_ms: MS) -> Result<i32, Errno> {
179 use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
180
181 let poll_fd = PollFd::new(fd, PollFlags::POLLIN);
182 let timeout = PollTimeout::try_from(timeout_ms.into()).map_err(|_| Errno::EOVERFLOW)?;
183
184 poll(&mut [poll_fd], timeout)
185}
186
187#[cfg(target_os = "macos")]
191fn wait_for_input<MS: Into<u64>>(fd: BorrowedFd<'_>, timeout_ms: MS) -> Result<i32, Errno> {
192 use {
193 nix::sys::{
194 select::{select, FdSet},
195 time::TimeVal,
196 },
197 std::{os::fd::AsRawFd, time::Duration},
198 };
199 let mut fd_set = FdSet::new();
200 fd_set.insert(fd);
201 let mut dur = Duration::from_millis(timeout_ms.into());
202 let timeout_s = dur.as_secs() as _;
203 let timeout_us = dur.subsec_micros() as _;
204 let mut tv = TimeVal::new(timeout_s, timeout_us);
205
206 select(
207 fd.as_raw_fd() + 1,
208 Some(&mut fd_set),
209 None,
210 None,
211 Some(&mut tv),
212 )
213}