This commit is contained in:
sxyazi 2025-02-16 21:57:20 +08:00
parent 52c69c11d4
commit 7b348ad96e
No known key found for this signature in database
4 changed files with 105 additions and 80 deletions

View file

@ -1,13 +1,13 @@
use std::{io::{LineWriter, stderr}, time::Duration}; use std::{io::{LineWriter, stderr}, time::Duration};
use anyhow::{Result, bail}; use anyhow::Result;
use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}}; use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};
use scopeguard::defer; use scopeguard::defer;
use tokio::{io::{AsyncReadExt, BufReader}, time::{sleep, timeout}}; use tokio::time::sleep;
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
use yazi_shared::Either; use yazi_shared::Either;
use crate::{Adapter, Brand, Mux, TMUX, Unknown}; use crate::{Adapter, AsyncStdin, Brand, Mux, TMUX, Unknown};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Emulator { pub struct Emulator {
@ -43,7 +43,7 @@ impl Emulator {
RestorePosition RestorePosition
)?; )?;
let resp = futures::executor::block_on(Self::read_until_da1()); let resp = Self::read_until_da1();
Mux::tmux_drain()?; Mux::tmux_drain()?;
let kind = if let Some(b) = Brand::from_csi(&resp).or(resort) { let kind = if let Some(b) = Brand::from_csi(&resp).or(resort) {
@ -105,63 +105,37 @@ impl Emulator {
result result
} }
pub async fn read_until_da1() -> String { pub fn read_until_da1() -> String {
let mut buf: Vec<u8> = Vec::with_capacity(200);
let read = async {
let mut stdin = BufReader::new(tokio::io::stdin());
loop {
let mut c = [0; 1];
if stdin.read(&mut c).await? == 0 {
bail!("unexpected EOF");
}
buf.push(c[0]);
if c[0] != b'c' || !buf.contains(&0x1b) {
continue;
}
if buf.rsplitn(2, |&b| b == 0x1b).next().is_some_and(|s| s.starts_with(b"[?")) {
break;
}
}
Ok(())
};
let h = tokio::spawn(async move { let h = tokio::spawn(async move {
sleep(Duration::from_millis(300)).await; sleep(Duration::from_millis(300)).await;
Self::error_to_user().ok(); Self::error_to_user().ok();
}); });
match timeout(Duration::from_secs(2), read).await { let (buf, result) = AsyncStdin::default().read_until(Duration::from_secs(5), |b, buf| {
Ok(Ok(())) => debug!("read_until_da1: {buf:?}"), b == b'c'
Err(e) => error!("read_until_da1 timed out: {buf:?}, error: {e:?}"), && buf.contains(&0x1b)
Ok(Err(e)) => error!("read_until_da1 failed: {buf:?}, error: {e:?}"), && buf.rsplitn(2, |&b| b == 0x1b).next().is_some_and(|s| s.starts_with(b"[?"))
});
match result {
Ok(()) => debug!("read_until_da1: {buf:?}"),
Err(e) => error!("read_until_da1 failed: {buf:?}, error: {e:?}"),
} }
h.abort(); h.abort();
String::from_utf8_lossy(&buf).into_owned() String::from_utf8_lossy(&buf).into_owned()
} }
pub async fn read_until_dsr() -> String { pub fn read_until_dsr() -> String {
let mut buf: Vec<u8> = Vec::with_capacity(200); let (buf, result) = AsyncStdin::default().read_until(Duration::from_millis(500), |b, buf| {
let read = async { b == b'n' && (buf.ends_with(b"\x1b[0n") || buf.ends_with(b"\x1b[3n"))
let mut stdin = BufReader::new(tokio::io::stdin()); });
loop {
let mut c = [0; 1];
if stdin.read(&mut c).await? == 0 {
bail!("unexpected EOF");
}
buf.push(c[0]);
if c[0] == b'n' && (buf.ends_with(b"\x1b[0n") || buf.ends_with(b"\x1b[3n")) {
break;
}
}
Ok(())
};
match timeout(Duration::from_millis(500), read).await { match result {
Ok(Ok(())) => debug!("read_until_dsr: {buf:?}"), Ok(()) => debug!("read_until_dsr: {buf:?}"),
Err(e) => error!("read_until_dsr timed out: {buf:?}, error: {e:?}"), Err(e) => error!("read_until_dsr failed: {buf:?}, error: {e:?}"),
Ok(Err(e)) => error!("read_until_dsr failed: {buf:?}, error: {e:?}"),
} }
String::from_utf8_lossy(&buf).into_owned() String::from_utf8_lossy(&buf).into_owned()
} }

View file

@ -43,7 +43,7 @@ impl Mux {
pub fn tmux_drain() -> Result<()> { pub fn tmux_drain() -> Result<()> {
if TMUX.get() { if TMUX.get() {
crossterm::execute!(std::io::stderr(), crossterm::style::Print(Mux::csi("\x1b[5n")))?; crossterm::execute!(std::io::stderr(), crossterm::style::Print(Mux::csi("\x1b[5n")))?;
_ = futures::executor::block_on(Emulator::read_until_dsr()); _ = Emulator::read_until_dsr();
} }
Ok(()) Ok(())
} }

View file

@ -1,32 +1,53 @@
use std::{ops::{Deref, DerefMut}, time::Duration}; use std::{io::{Error, ErrorKind}, time::{Duration, Instant}};
pub struct AsyncStdin { pub struct AsyncStdin {
inner: std::io::StdinLock<'static>,
#[cfg(unix)] #[cfg(unix)]
fds: libc::fd_set, fds: libc::fd_set,
} }
impl Deref for AsyncStdin { impl AsyncStdin {
type Target = std::io::StdinLock<'static>; pub fn read_until<P>(&mut self, timeout: Duration, predicate: P) -> (Vec<u8>, std::io::Result<()>)
where
P: Fn(u8, &[u8]) -> bool,
{
let mut buf: Vec<u8> = Vec::with_capacity(200);
let now = Instant::now();
fn deref(&self) -> &Self::Target { &self.inner } let mut read = || {
loop {
if now.elapsed() > timeout {
return Err(Error::new(ErrorKind::TimedOut, "timed out"));
} else if !self.poll(Duration::from_millis(50))? {
continue;
}
let b = Self::read_u8()?;
buf.push(b);
if predicate(b, &buf) {
break;
}
}
Ok(())
};
let result = read();
(buf, result)
}
} }
impl DerefMut for AsyncStdin { #[cfg(unix)]
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } impl Default for AsyncStdin {
fn default() -> Self {
let mut me = Self { fds: unsafe { std::mem::MaybeUninit::zeroed().assume_init() } };
me.reset();
me
}
} }
#[cfg(unix)] #[cfg(unix)]
impl AsyncStdin { impl AsyncStdin {
pub fn new(inner: std::io::StdinLock<'static>) -> Self {
let mut me = Self { inner, fds: unsafe { std::mem::MaybeUninit::zeroed().assume_init() } };
me.reset();
me
}
pub fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> { pub fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> {
use std::os::unix::io::AsRawFd;
let mut tv = libc::timeval { let mut tv = libc::timeval {
tv_sec: timeout.as_secs() as libc::time_t, tv_sec: timeout.as_secs() as libc::time_t,
tv_usec: timeout.subsec_micros() as libc::suseconds_t, tv_usec: timeout.subsec_micros() as libc::suseconds_t,
@ -34,7 +55,7 @@ impl AsyncStdin {
let result = unsafe { let result = unsafe {
libc::select( libc::select(
self.inner.as_raw_fd() + 1, libc::STDIN_FILENO + 1,
&mut self.fds, &mut self.fds,
std::ptr::null_mut(), std::ptr::null_mut(),
std::ptr::null_mut(), std::ptr::null_mut(),
@ -43,7 +64,7 @@ impl AsyncStdin {
}; };
match result { match result {
-1 => Err(std::io::Error::last_os_error()), -1 => Err(Error::last_os_error()),
0 => Ok(false), 0 => Ok(false),
_ => { _ => {
self.reset(); self.reset();
@ -52,30 +73,59 @@ impl AsyncStdin {
} }
} }
fn reset(&mut self) { pub fn read_u8() -> std::io::Result<u8> {
use std::os::unix::io::AsRawFd; let mut b = 0;
match unsafe { libc::read(libc::STDIN_FILENO, &mut b as *mut _ as *mut _, 1) } {
-1 => Err(Error::last_os_error()),
0 => Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF")),
_ => Ok(b),
}
}
fn reset(&mut self) {
unsafe { unsafe {
libc::FD_ZERO(&mut self.fds); libc::FD_ZERO(&mut self.fds);
libc::FD_SET(self.inner.as_raw_fd(), &mut self.fds); libc::FD_SET(libc::STDIN_FILENO, &mut self.fds);
} }
} }
} }
#[cfg(windows)] #[cfg(windows)]
impl AsyncStdin { impl Default for AsyncStdin {
pub fn new(inner: std::io::StdinLock<'static>) -> Self { Self { inner } } fn default() -> Self { Self {} }
}
#[cfg(windows)]
impl AsyncStdin {
pub fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> { pub fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> {
use std::os::windows::io::AsRawHandle; use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::{Foundation::WAIT_TIMEOUT, System::Threading::WaitForSingleObject}; use windows_sys::Win32::{Foundation::{WAIT_FAILED, WAIT_OBJECT_0}, System::Threading::WaitForSingleObject};
let handle = self.inner.as_raw_handle(); let handle = std::io::stdin().as_raw_handle();
let millis = timeout.as_millis(); let millis = timeout.as_millis();
match unsafe { WaitForSingleObject(handle, millis as u32) } { match unsafe { WaitForSingleObject(handle, millis as u32) } {
WAIT_TIMEOUT => Ok(false), WAIT_FAILED => Err(Error::last_os_error()),
_ => Ok(true), WAIT_OBJECT_0 => Ok(true),
_ => Ok(false),
} }
} }
pub fn read_u8() -> std::io::Result<u8> {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Storage::FileSystem::ReadFile;
let mut buf = 0;
let mut bytes = 0;
let success = unsafe {
ReadFile(std::io::stdin().as_raw_handle(), &mut buf, 1, &mut bytes, std::ptr::null_mut())
};
if success == 0 {
return Err(Error::last_os_error());
}
Ok(buf)
}
} }

View file

@ -42,13 +42,14 @@ impl Term {
mouse::SetMouse(true), mouse::SetMouse(true),
)?; )?;
let da = futures::executor::block_on(Emulator::read_until_da1()); let resp = Emulator::read_until_da1();
Mux::tmux_drain()?; Mux::tmux_drain()?;
CSI_U.store(da.contains("\x1b[?0u"), Ordering::Relaxed); CSI_U.store(resp.contains("\x1b[?0u"), Ordering::Relaxed);
BLINK.store(da.contains("\x1b[?12;1$y"), Ordering::Relaxed); BLINK.store(resp.contains("\x1b[?12;1$y"), Ordering::Relaxed);
SHAPE.store( SHAPE.store(
da.split_once("\x1bP1$r") resp
.split_once("\x1bP1$r")
.and_then(|(_, s)| s.bytes().next()) .and_then(|(_, s)| s.bytes().next())
.filter(|&b| matches!(b, b'0'..=b'6')) .filter(|&b| matches!(b, b'0'..=b'6'))
.map_or(u8::MAX, |b| b - b'0'), .map_or(u8::MAX, |b| b - b'0'),