-
Notifications
You must be signed in to change notification settings - Fork 245
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
299 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
use lazy_static::lazy_static; | ||
|
||
#[cfg(unix)] | ||
lazy_static! { | ||
static ref SUPPORTS_SYNCHRONIZED_OUTPUT: bool = supports_synchronized_output_uncached(); | ||
} | ||
|
||
#[cfg(not(unix))] | ||
pub(crate) fn supports_synchronized_output() -> bool { | ||
false | ||
} | ||
|
||
#[cfg(unix)] | ||
pub(crate) fn supports_synchronized_output() -> bool { | ||
*SUPPORTS_SYNCHRONIZED_OUTPUT | ||
} | ||
|
||
/// Specification: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 | ||
#[cfg(unix)] | ||
fn supports_synchronized_output_uncached() -> bool { | ||
use std::io::{Read as _, Write as _}; | ||
use std::os::fd::AsRawFd as _; | ||
use std::time::Duration; | ||
|
||
const TIMEOUT_DURATION: Duration = Duration::from_millis(10); | ||
|
||
#[derive(PartialEq)] | ||
enum ParserState { | ||
None, | ||
CsiOne, | ||
CsiTwo, | ||
QuestionMark, | ||
ModeDigit1, | ||
ModeDigit2, | ||
ModeDigit3, | ||
ModeDigit4, | ||
Semicolon, | ||
Response, | ||
DollarSign, | ||
Ypsilon, | ||
} | ||
|
||
struct Parser { | ||
state: ParserState, | ||
response: u8, | ||
} | ||
|
||
impl Parser { | ||
fn process_byte(&mut self, byte: u8) { | ||
match byte { | ||
b'\x1b' => { | ||
self.state = ParserState::CsiOne; | ||
} | ||
b'[' => { | ||
self.state = if self.state == ParserState::CsiOne { | ||
ParserState::CsiTwo | ||
} else { | ||
ParserState::None | ||
}; | ||
} | ||
b'?' => { | ||
self.state = if self.state == ParserState::CsiTwo { | ||
ParserState::QuestionMark | ||
} else { | ||
ParserState::None | ||
}; | ||
} | ||
byte @ b'0' => { | ||
self.state = if self.state == ParserState::Semicolon { | ||
self.response = byte; | ||
ParserState::Response | ||
} else if self.state == ParserState::ModeDigit1 { | ||
ParserState::ModeDigit2 | ||
} else { | ||
ParserState::None | ||
}; | ||
} | ||
byte @ b'2' => { | ||
self.state = if self.state == ParserState::Semicolon { | ||
self.response = byte; | ||
ParserState::Response | ||
} else if self.state == ParserState::QuestionMark { | ||
ParserState::ModeDigit1 | ||
} else if self.state == ParserState::ModeDigit2 { | ||
ParserState::ModeDigit3 | ||
} else { | ||
ParserState::None | ||
}; | ||
} | ||
byte @ b'1' | byte @ b'3' | byte @ b'4' => { | ||
self.state = if self.state == ParserState::Semicolon { | ||
self.response = byte; | ||
ParserState::Response | ||
} else { | ||
ParserState::None | ||
}; | ||
} | ||
b'6' => { | ||
self.state = if self.state == ParserState::ModeDigit3 { | ||
ParserState::ModeDigit4 | ||
} else { | ||
ParserState::None | ||
}; | ||
} | ||
b';' => { | ||
self.state = if self.state == ParserState::ModeDigit4 { | ||
ParserState::Semicolon | ||
} else { | ||
ParserState::None | ||
}; | ||
} | ||
b'$' => { | ||
self.state = if self.state == ParserState::Response { | ||
ParserState::DollarSign | ||
} else { | ||
ParserState::None | ||
}; | ||
} | ||
b'y' => { | ||
self.state = if self.state == ParserState::DollarSign { | ||
ParserState::Ypsilon | ||
} else { | ||
ParserState::None | ||
}; | ||
} | ||
_ => { | ||
self.state = ParserState::None; | ||
} | ||
} | ||
} | ||
|
||
fn get_response(&self) -> Option<u8> { | ||
if self.state == ParserState::Ypsilon { | ||
Some(self.response - b'0') | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
with_raw_terminal(|stdin_lock, stdout_lock, _| { | ||
write!(stdout_lock, "\x1b[?2026$p").ok()?; | ||
stdout_lock.flush().ok()?; | ||
|
||
let stdin_fd = libc::pollfd { | ||
fd: stdin_lock.as_raw_fd(), | ||
events: libc::POLLIN, | ||
revents: 0, | ||
}; | ||
let mut fds = [stdin_fd]; | ||
let mut buf = [0u8; 256]; | ||
let mut parser = Parser { | ||
state: ParserState::None, | ||
response: u8::MAX, | ||
}; | ||
let deadline = std::time::Instant::now() + TIMEOUT_DURATION; | ||
|
||
loop { | ||
let remaining_time = deadline | ||
.saturating_duration_since(std::time::Instant::now()) | ||
.as_millis() | ||
.try_into() | ||
.ok()?; | ||
|
||
if remaining_time == 0 { | ||
// Timeout | ||
return Some(false); | ||
} | ||
|
||
match unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as _, remaining_time) } { | ||
0 => { | ||
// Timeout | ||
return Some(false); | ||
} | ||
1.. => { | ||
'read: loop { | ||
match stdin_lock.read(&mut buf) { | ||
Ok(0) => { | ||
// Reached EOF | ||
return Some(false); | ||
} | ||
Ok(size) => { | ||
for byte in &buf[..size] { | ||
parser.process_byte(*byte); | ||
|
||
match parser.get_response() { | ||
Some(1 | 2) => return Some(true), | ||
Some(_) => return Some(false), | ||
None => {} | ||
} | ||
} | ||
|
||
break 'read; | ||
} | ||
Err(err) if err.kind() == std::io::ErrorKind::Interrupted => { | ||
// Got interrupted, retry read | ||
continue 'read; | ||
} | ||
Err(_) => { | ||
return Some(false); | ||
} | ||
} | ||
} | ||
|
||
// Reuse the pollfd for the next poll call | ||
fds[0].revents = 0; | ||
} | ||
_ => { | ||
// Error | ||
return Some(false); | ||
} | ||
} | ||
} | ||
}) | ||
.ok() | ||
.flatten() | ||
.unwrap_or(false) | ||
} | ||
|
||
#[cfg(unix)] | ||
fn with_raw_terminal<R>( | ||
f: impl FnOnce(&mut std::io::StdinLock, &mut std::io::StdoutLock, &mut std::io::StderrLock) -> R, | ||
) -> std::io::Result<R> { | ||
use std::os::fd::AsRawFd as _; | ||
|
||
unsafe { | ||
let fd = std::io::stdin().as_raw_fd(); | ||
let mut ptr = std::mem::MaybeUninit::uninit(); | ||
|
||
if libc::tcgetattr(fd, ptr.as_mut_ptr()) == 0 { | ||
let mut termios = ptr.assume_init(); | ||
let old_iflag = termios.c_iflag; | ||
let old_oflag = termios.c_oflag; | ||
let old_cflag = termios.c_cflag; | ||
let old_lflag = termios.c_lflag; | ||
|
||
libc::cfmakeraw(&mut termios); | ||
|
||
// Lock the standard streams, so no output gets lost while in raw mode | ||
let mut stdin_lock = std::io::stdin().lock(); | ||
let mut stdout_lock = std::io::stdout().lock(); | ||
let mut stderr_lock = std::io::stderr().lock(); | ||
|
||
// Go into raw mode | ||
if libc::tcsetattr(fd, libc::TCSADRAIN, &termios) == 0 { | ||
let result = f(&mut stdin_lock, &mut stdout_lock, &mut stderr_lock); | ||
|
||
// Reset to previous mode | ||
termios.c_iflag = old_iflag; | ||
termios.c_oflag = old_oflag; | ||
termios.c_cflag = old_cflag; | ||
termios.c_lflag = old_lflag; | ||
|
||
if libc::tcsetattr(fd, libc::TCSADRAIN, &termios) == 0 { | ||
return Ok(result); | ||
} | ||
} | ||
} | ||
} | ||
|
||
Err(std::io::Error::last_os_error()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters