Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: standardize usage of /dev/tty where possible #957

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
13 changes: 7 additions & 6 deletions src/cursor/sys/unix.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::{
io::{self, Error, ErrorKind, Write},
io::{self, Error, ErrorKind},
time::Duration,
};

use crate::{
event::{filter::CursorPositionFilter, poll_internal, read_internal, InternalEvent},
terminal::{disable_raw_mode, enable_raw_mode, sys::is_raw_mode_enabled},
terminal::{
disable_raw_mode, enable_raw_mode,
sys::{file_descriptor::tty_fd_out, is_raw_mode_enabled},
},
};

/// Returns the cursor position (column, row).
Expand All @@ -31,10 +34,8 @@ fn read_position() -> io::Result<(u16, u16)> {

fn read_position_raw() -> io::Result<(u16, u16)> {
// Use `ESC [ 6 n` to and retrieve the cursor position.
let mut stdout = io::stdout();
stdout.write_all(b"\x1B[6n")?;
stdout.flush()?;

let stdout = tty_fd_out()?;
stdout.write(b"\x1B[6n")?;
loop {
match poll_internal(Some(Duration::from_millis(2000)), &CursorPositionFilter) {
Ok(true) => {
Expand Down
42 changes: 21 additions & 21 deletions src/event/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::event::{filter::Filter, source::EventSource, timeout::PollTimeout, In
/// Can be used to read `InternalEvent`s.
pub(crate) struct InternalEventReader {
events: VecDeque<InternalEvent>,
source: Option<Box<dyn EventSource>>,
source: io::Result<Box<dyn EventSource>>,
skipped_events: Vec<InternalEvent>,
}

Expand All @@ -22,7 +22,7 @@ impl Default for InternalEventReader {
#[cfg(unix)]
let source = UnixInternalEventSource::new();

let source = source.ok().map(|x| Box::new(x) as Box<dyn EventSource>);
let source = source.map(|x| Box::new(x) as Box<dyn EventSource>);

InternalEventReader {
source,
Expand Down Expand Up @@ -50,11 +50,11 @@ impl InternalEventReader {
}

let event_source = match self.source.as_mut() {
Some(source) => source,
None => {
Ok(source) => source,
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to initialize input reader",
e.kind(),
format!("Failed to initialize input reader: {e:?}"),
))
}
};
Expand Down Expand Up @@ -147,7 +147,7 @@ mod tests {
fn test_poll_fails_without_event_source() {
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: None,
source: Err(io::Error::new(io::ErrorKind::Other, "not initialized")),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -164,7 +164,7 @@ mod tests {
fn test_poll_returns_true_for_matching_event_in_queue_at_front() {
let mut reader = InternalEventReader {
events: vec![InternalEvent::Event(Event::Resize(10, 10))].into(),
source: None,
source: Err(io::Error::new(io::ErrorKind::Other, "not initialized")),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -180,7 +180,7 @@ mod tests {
InternalEvent::CursorPosition(10, 20),
]
.into(),
source: None,
source: Err(io::Error::new(io::ErrorKind::Other, "not initialized")),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -193,7 +193,7 @@ mod tests {

let mut reader = InternalEventReader {
events: vec![EVENT].into(),
source: None,
source: Err(io::Error::new(io::ErrorKind::Other, "not initialized")),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -207,7 +207,7 @@ mod tests {

let mut reader = InternalEventReader {
events: vec![InternalEvent::Event(Event::Resize(10, 10)), CURSOR_EVENT].into(),
source: None,
source: Err(io::Error::new(io::ErrorKind::Other, "not initialized")),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -222,7 +222,7 @@ mod tests {

let mut reader = InternalEventReader {
events: vec![SKIPPED_EVENT, CURSOR_EVENT].into(),
source: None,
source: Err(io::Error::new(io::ErrorKind::Other, "not initialized")),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -236,7 +236,7 @@ mod tests {

let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
source: Ok(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -251,7 +251,7 @@ mod tests {

let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
source: Ok(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -269,7 +269,7 @@ mod tests {

let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
source: Ok(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -284,7 +284,7 @@ mod tests {

let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
source: Ok(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -301,7 +301,7 @@ mod tests {

let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
source: Ok(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -317,7 +317,7 @@ mod tests {
fn test_poll_propagates_error() {
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(FakeSource::new(&[]))),
source: Ok(Box::new(FakeSource::new(&[]))),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -334,7 +334,7 @@ mod tests {
fn test_read_propagates_error() {
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(FakeSource::new(&[]))),
source: Ok(Box::new(FakeSource::new(&[]))),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -355,7 +355,7 @@ mod tests {

let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
source: Ok(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};

Expand All @@ -374,7 +374,7 @@ mod tests {

let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
source: Ok(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};

Expand Down
26 changes: 24 additions & 2 deletions src/event/source/unix/mio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::event::sys::Waker;
use crate::event::{
source::EventSource, sys::unix::parse::parse_event, timeout::PollTimeout, Event, InternalEvent,
};
use crate::terminal::sys::file_descriptor::{tty_fd, FileDesc};
use crate::terminal::sys::file_descriptor::FileDesc;

// Tokens to identify file descriptor
const TTY_TOKEN: Token = Token(0);
Expand All @@ -33,8 +33,30 @@ pub(crate) struct UnixInternalEventSource {
}

impl UnixInternalEventSource {
#[cfg(target_os = "macos")]
pub fn new() -> io::Result<Self> {
UnixInternalEventSource::from_file_descriptor(tty_fd()?)
// Polling /dev/tty with mio is unsupported on MacOS so we must explicitly use stdin
use crate::tty::IsTty;
#[cfg(feature = "libc")]
let fd = FileDesc::new(libc::STDIN_FILENO, false);
#[cfg(not(feature = "libc"))]
let fd = FileDesc::Borrowed(rustix::stdio::stdin());

if !fd.is_tty() {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"The 'use-dev-tty' feature must be enabled to read terminal events on MacOS when stdin is not a tty.",
));
}

UnixInternalEventSource::from_file_descriptor(fd)
}

#[cfg(not(target_os = "macos"))]
pub fn new() -> io::Result<Self> {
UnixInternalEventSource::from_file_descriptor(
crate::terminal::sys::file_descriptor::tty_fd_in()?,
)
}

pub(crate) fn from_file_descriptor(input_fd: FileDesc<'static>) -> io::Result<Self> {
Expand Down
4 changes: 2 additions & 2 deletions src/event/source/unix/tty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use filedescriptor::{poll, pollfd, POLLIN};
#[cfg(feature = "event-stream")]
use crate::event::sys::Waker;
use crate::event::{source::EventSource, sys::unix::parse::parse_event, InternalEvent};
use crate::terminal::sys::file_descriptor::{tty_fd, FileDesc};
use crate::terminal::sys::file_descriptor::{tty_fd_in, FileDesc};

/// Holds a prototypical Waker and a receiver we can wait on when doing select().
#[cfg(feature = "event-stream")]
Expand Down Expand Up @@ -57,7 +57,7 @@ fn nonblocking_unix_pair() -> io::Result<(UnixStream, UnixStream)> {

impl UnixInternalEventSource {
pub fn new() -> io::Result<Self> {
UnixInternalEventSource::from_file_descriptor(tty_fd()?)
UnixInternalEventSource::from_file_descriptor(tty_fd_in()?)
}

pub(crate) fn from_file_descriptor(input_fd: FileDesc<'static>) -> io::Result<Self> {
Expand Down
93 changes: 76 additions & 17 deletions src/terminal/sys/file_descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@
}
}

pub fn write(&self, buffer: &[u8]) -> io::Result<usize> {
let result = unsafe {
libc::write(
self.fd,
buffer.as_ptr() as *const libc::c_void,
buffer.len() as size_t,
)
};

if result < 0 {
Err(io::Error::last_os_error())
} else {
Ok(result as usize)
}
}

/// Returns the underlying file descriptor.
pub fn raw_fd(&self) -> RawFd {
self.fd
Expand All @@ -72,7 +88,7 @@

#[cfg(not(feature = "libc"))]
impl FileDesc<'_> {
pub fn read(&self, buffer: &mut [u8]) -> io::Result<usize> {

Check warning on line 91 in src/terminal/sys/file_descriptor.rs

View workflow job for this annotation

GitHub Actions / stable on ubuntu-latest

methods `read` and `write` are never used

Check warning on line 91 in src/terminal/sys/file_descriptor.rs

View workflow job for this annotation

GitHub Actions / nightly on ubuntu-latest

methods `read` and `write` are never used

Check warning on line 91 in src/terminal/sys/file_descriptor.rs

View workflow job for this annotation

GitHub Actions / stable on macOS-latest

methods `read` and `write` are never used

Check warning on line 91 in src/terminal/sys/file_descriptor.rs

View workflow job for this annotation

GitHub Actions / nightly on macOS-latest

methods `read` and `write` are never used
let fd = match self {
FileDesc::Owned(fd) => fd.as_fd(),
FileDesc::Borrowed(fd) => fd.as_fd(),
Expand All @@ -81,6 +97,15 @@
Ok(result)
}

pub fn write(&self, buffer: &[u8]) -> io::Result<usize> {
let fd = match self {
FileDesc::Owned(fd) => fd.as_fd(),
FileDesc::Borrowed(fd) => fd.as_fd(),
};
let result = rustix::io::write(fd, buffer)?;
Ok(result)
}

pub fn raw_fd(&self) -> RawFd {
match self {
FileDesc::Owned(fd) => fd.as_raw_fd(),
Expand Down Expand Up @@ -121,34 +146,68 @@

#[cfg(feature = "libc")]
/// Creates a file descriptor pointing to the standard input or `/dev/tty`.
pub fn tty_fd() -> io::Result<FileDesc<'static>> {
let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
(libc::STDIN_FILENO, false)
pub fn tty_fd_in() -> io::Result<FileDesc<'static>> {
let (fd, close_on_drop) = if let Ok(file) = fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")
{
(file.into_raw_fd(), true)
} else {
(
fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?
.into_raw_fd(),
true,
)
(libc::STDIN_FILENO, false)
};
Ok(FileDesc::new(fd, close_on_drop))
}

#[cfg(feature = "libc")]
/// Creates a file descriptor pointing to the standard output or `/dev/tty`.
pub fn tty_fd_out() -> io::Result<FileDesc<'static>> {
let (fd, close_on_drop) = if let Ok(file) = fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")
{
(file.into_raw_fd(), true)
} else {
(libc::STDOUT_FILENO, false)
};
Ok(FileDesc::new(fd, close_on_drop))
}

#[cfg(not(feature = "libc"))]
/// Creates a file descriptor pointing to the standard input or `/dev/tty`.
pub fn tty_fd() -> io::Result<FileDesc<'static>> {
pub fn tty_fd_in() -> io::Result<FileDesc<'static>> {
use std::fs::File;

let file = File::options()
.read(true)
.write(true)
.open("/dev/tty")
.map(|file| (FileDesc::Owned(file.into())));
let fd = if let Ok(file) = file {
file
} else {
// Fallback to stdin if /dev/tty is missing
FileDesc::Borrowed(rustix::stdio::stdin())
};
Ok(fd)
}

#[cfg(not(feature = "libc"))]
/// Creates a file descriptor pointing to the standard output or `/dev/tty`.
pub fn tty_fd_out() -> io::Result<FileDesc<'static>> {
use std::fs::File;

let stdin = rustix::stdio::stdin();
let fd = if rustix::termios::isatty(stdin) {
FileDesc::Borrowed(stdin)
let file = File::options()
.read(true)
.write(true)
.open("/dev/tty")
.map(|file| (FileDesc::Owned(file.into())));
let fd = if let Ok(file) = file {
file
} else {
let dev_tty = File::options().read(true).write(true).open("/dev/tty")?;
FileDesc::Owned(dev_tty.into())
// Fallback to stdout if /dev/tty is missing
FileDesc::Borrowed(rustix::stdio::stdout())
};
Ok(fd)
}
Loading
Loading