Skip to content

Commit

Permalink
Merge pull request #4 from zeenix/use-select-on-mac
Browse files Browse the repository at this point in the history
Use polling syscall directly to fix MacOS support

Fixes #3
  • Loading branch information
Canop committed Mar 11, 2024
2 parents 9055868 + 1cf5407 commit f9c5904
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 16 deletions.
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ readme = "README.md"
thiserror = "1.0"

[target.'cfg(unix)'.dependencies]
mio = { version = "0.8", features = ["os-ext"] }
nix = "0.22"
nix = { version = "0.28", features = ["poll"] }

[dev-dependencies]
crossterm = "0.21"
63 changes: 49 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
mod error;

use std::os::fd::BorrowedFd;

pub use error::*;

use nix::errno::Errno;

/// Query the xterm interface, assuming the terminal is in raw mode
/// (or we would block waiting for a newline).
pub fn query<MS: Into<u64>>(query: &str, timeout_ms: MS) -> Result<String, XQError> {
Expand All @@ -24,35 +28,66 @@ pub fn query_buffer<MS: Into<u64>>(
buffer: &mut [u8],
timeout_ms: MS,
) -> Result<usize, XQError> {
use mio::{unix::SourceFd, Events, Interest, Poll, Token};
use std::{
fs::File,
io::{self, Read, Write},
os::fd::AsRawFd,
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 mut poll = Poll::new()?;
let mut events = Events::with_capacity(1024);
let stdin_raw_fd = stdin.as_raw_fd();
let mut stdin_fd = SourceFd(&stdin_raw_fd); // fancy way to pass the 0 const
poll.registry()
.register(&mut stdin_fd, Token(0), Interest::READABLE)?;
let timeout = std::time::Duration::from_millis(timeout_ms.into());
poll.poll(&mut events, Some(timeout))?;
for event in &events {
if event.token() == Token(0) {
let stdin_fd = stdin.as_fd();

match wait_for_input(stdin_fd, timeout_ms) {
Ok(n) if n == 0 => Err(XQError::Timeout),
Ok(_) => {
let bytes_written = stdin.read(buffer)?;
return Ok(bytes_written);
Ok(bytes_written)
}
Err(e) => Err(XQError::IO(e.into())),
}
Err(XQError::Timeout) // no file descriptor was ready in time
}

#[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)
}

// On MacOS, we need to use the `select` instead of `poll` because it doesn't support poll with tty:
//
// https://github.com/tokio-rs/mio/issues/1377
#[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,
};
use std::{os::fd::AsRawFd, time::Duration};
let mut fd_set = FdSet::new();
fd_set.insert(fd);
let timeout_us = Duration::from_millis(timeout_ms.into())
.as_micros()
.try_into()
.map_err(|_| Errno::EOVERFLOW)?;
let mut tv = TimeVal::new(0, timeout_us);

select(
fd.as_raw_fd() + 1,
Some(&mut fd_set),
None,
None,
Some(&mut tv),
)
}

0 comments on commit f9c5904

Please sign in to comment.