mod error;
use {nix::errno::Errno, std::os::fd::BorrowedFd};
pub use error::*;
pub fn query<MS: Into<u64>>(query: &str, timeout_ms: MS) -> Result<String, XQError> {
const N: usize = 100;
let mut response = [0; N];
let n = query_buffer(query, &mut response, timeout_ms.into())?;
let s = std::str::from_utf8(&response[..n])?;
Ok(s.to_string())
}
pub fn query_osc<MS: Into<u64>>(query: &str, timeout_ms: MS) -> Result<String, XQError> {
const N: usize = 100;
let mut response = [0; N];
let resp = query_osc_buffer(query, &mut response, timeout_ms.into())?;
let s = std::str::from_utf8(resp)?;
Ok(s.to_string())
}
#[cfg(unix)]
pub fn query_buffer<MS: Into<u64>>(
query: &str,
buffer: &mut [u8],
timeout_ms: MS,
) -> Result<usize, XQError> {
use std::{
fs::File,
io::{self, Read, Write},
os::fd::AsFd,
};
let stdout = io::stdout();
let mut stdout = stdout.lock();
write!(stdout, "{}", query)?;
stdout.flush()?;
let mut stdin = File::open("/dev/tty")?;
let stdin_fd = stdin.as_fd();
match wait_for_input(stdin_fd, timeout_ms) {
Ok(0) => Err(XQError::Timeout),
Ok(_) => {
let bytes_written = stdin.read(buffer)?;
Ok(bytes_written)
}
Err(e) => Err(XQError::IO(e.into())),
}
}
#[cfg(unix)]
pub fn query_osc_buffer<'b, MS: Into<u64> + Copy>(
query: &str,
buffer: &'b mut [u8],
timeout_ms: MS,
) -> Result<&'b [u8], XQError> {
use std::{
fs::File,
io::{self, Read, Write},
os::fd::AsFd,
};
const ESC: char = '\x1b';
const BEL: char = '\x07';
let term = std::env::var("TERM").map_err(|_| XQError::Unsupported)?;
if term == "dumb" {
return Err(XQError::Unsupported);
}
let is_screen = term.starts_with("screen");
let stdout = io::stdout();
let mut stdout = stdout.lock();
if is_screen {
write!(stdout, "{ESC}P")?;
}
write!(stdout, "{}", query)?;
write!(stdout, "{ESC}[5n")?;
if is_screen {
write!(stdout, "{ESC}\\")?;
}
stdout.flush()?;
let mut stdin = File::open("/dev/tty")?;
let mut osc_start_idx = None;
let mut osc_end_idx = None;
let mut bytes_written = 0;
while bytes_written < buffer.len() {
let stdin_fd = stdin.as_fd();
match wait_for_input(stdin_fd, timeout_ms) {
Ok(0) => {
return Err(XQError::Timeout);
}
Ok(_) => {
let bytes_read = stdin.read(&mut buffer[bytes_written..])?;
if bytes_read == 0 {
return Err(XQError::NotAnOSCResponse); }
for i in bytes_written..bytes_written + bytes_read {
let b = buffer[i];
match osc_start_idx {
None => {
if b == ESC as u8 {
osc_start_idx = Some(i);
}
}
Some(start_idx) => {
if b == ESC as u8 || b == BEL as u8 {
if osc_end_idx.is_none() {
osc_end_idx = Some(i);
}
} else if b == b'n' {
match osc_end_idx {
None => return Err(XQError::NotAnOSCResponse),
Some(end_idx) => {
return Ok(&buffer[start_idx + 1..=end_idx]);
}
}
}
}
}
}
bytes_written += bytes_read;
}
Err(e) => {
return Err(XQError::IO(e.into()));
}
}
}
Err(XQError::BufferOverflow)
}
#[cfg(not(unix))]
pub fn query_buffer(_query: &str, _buffer: &mut [u8], _timeout_ms: u64) -> Result<usize, XQError> {
Err(XQError::Unsupported)
}
#[cfg(not(target_os = "macos"))]
fn wait_for_input<MS: Into<u64>>(fd: BorrowedFd<'_>, timeout_ms: MS) -> Result<i32, Errno> {
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
let poll_fd = PollFd::new(fd, PollFlags::POLLIN);
let timeout = PollTimeout::try_from(timeout_ms.into()).map_err(|_| Errno::EOVERFLOW)?;
poll(&mut [poll_fd], timeout)
}
#[cfg(target_os = "macos")]
fn wait_for_input<MS: Into<u64>>(fd: BorrowedFd<'_>, timeout_ms: MS) -> Result<i32, Errno> {
use {
nix::sys::{
select::{select, FdSet},
time::TimeVal,
},
std::{os::fd::AsRawFd, time::Duration},
};
let mut fd_set = FdSet::new();
fd_set.insert(fd);
let mut dur = Duration::from_millis(timeout_ms.into());
let timeout_s = dur.as_secs() as _;
let timeout_us = dur.subsec_micros() as _;
let mut tv = TimeVal::new(timeout_s, timeout_us);
select(
fd.as_raw_fd() + 1,
Some(&mut fd_set),
None,
None,
Some(&mut tv),
)
}