use crate::error::{ClientError, IroshError, Result};
pub use portable_pty::PtySize;
use std::fmt;
use tokio::sync::mpsc;
use windows_sys::Win32::System::Console::*;
pub struct RawTerminal {
in_handle: windows_sys::Win32::Foundation::HANDLE,
in_original_mode: u32,
out_handle: windows_sys::Win32::Foundation::HANDLE,
out_original_mode: u32,
}
impl fmt::Debug for RawTerminal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawTerminal").finish()
}
}
unsafe impl Send for RawTerminal {}
unsafe impl Sync for RawTerminal {}
impl RawTerminal {
pub fn new(_fd: i32) -> Result<Self> {
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
unsafe {
let in_handle = GetStdHandle(STD_INPUT_HANDLE);
let out_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if in_handle == INVALID_HANDLE_VALUE || out_handle == INVALID_HANDLE_VALUE {
return Err(IroshError::Client(ClientError::TerminalIo {
source: std::io::Error::last_os_error(),
}));
}
let mut in_mode = 0;
if GetConsoleMode(in_handle, &mut in_mode) == 0 {
return Err(IroshError::Client(ClientError::TerminalIo {
source: std::io::Error::last_os_error(),
}));
}
let in_original_mode = in_mode;
let mut out_mode = 0;
let out_original_mode = if GetConsoleMode(out_handle, &mut out_mode) != 0 {
let out_original = out_mode;
let new_out_mode =
out_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
let _ = SetConsoleMode(out_handle, new_out_mode);
out_original
} else {
0
};
let raw_in_mode = (in_mode
& !(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT))
| ENABLE_VIRTUAL_TERMINAL_INPUT;
if SetConsoleMode(in_handle, raw_in_mode) == 0 {
let raw_mode_basic =
in_mode & !(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
if SetConsoleMode(in_handle, raw_mode_basic) == 0 {
return Err(IroshError::Client(ClientError::TerminalIo {
source: std::io::Error::last_os_error(),
}));
}
}
Ok(Self {
in_handle,
in_original_mode,
out_handle,
out_original_mode,
})
}
}
}
impl Drop for RawTerminal {
fn drop(&mut self) {
unsafe {
let _ = SetConsoleMode(self.in_handle, self.in_original_mode);
if self.out_original_mode != 0 {
let _ = SetConsoleMode(self.out_handle, self.out_original_mode);
}
}
}
}
pub fn current_terminal_size() -> PtySize {
unsafe {
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
let mut info = std::mem::zeroed::<CONSOLE_SCREEN_BUFFER_INFO>();
if GetConsoleScreenBufferInfo(handle, &mut info) != 0 {
return PtySize {
rows: (info.srWindow.Bottom - info.srWindow.Top + 1) as u16,
cols: (info.srWindow.Right - info.srWindow.Left + 1) as u16,
pixel_width: 0,
pixel_height: 0,
};
}
}
crate::session::pty::default_pty_size()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TerminalEvent {
Data(Vec<u8>),
Resize(PtySize),
}
pub struct AsyncStdin {
rx: mpsc::UnboundedReceiver<TerminalEvent>,
}
impl AsyncStdin {
pub fn new() -> Result<Self> {
let (tx, rx) = mpsc::unbounded_channel();
std::thread::Builder::new()
.name("irosh-win-input".to_string())
.spawn(move || {
let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
let mut buffer = [unsafe { std::mem::zeroed::<INPUT_RECORD>() }; 128];
loop {
let mut read = 0;
if unsafe { ReadConsoleInputW(handle, buffer.as_mut_ptr(), 128, &mut read) }
== 0
{
break;
}
for i in 0..read {
let record = buffer[i as usize];
match record.EventType as u32 {
KEY_EVENT => {
let key = unsafe { record.Event.KeyEvent };
if key.bKeyDown != 0 {
let c = unsafe { key.uChar.UnicodeChar };
if c != 0 {
let mut utf8 = [0u8; 4];
let s = char::from_u32(c as u32)
.unwrap_or(' ')
.encode_utf8(&mut utf8);
if tx
.send(TerminalEvent::Data(s.as_bytes().to_vec()))
.is_err()
{
return;
}
}
}
}
WINDOW_BUFFER_SIZE_EVENT => {
let _ = tx.send(TerminalEvent::Resize(current_terminal_size()));
}
_ => {}
}
}
}
})
.map_err(|e| IroshError::Client(ClientError::TerminalIo { source: e }))?;
Ok(Self { rx })
}
pub fn poll_next(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<TerminalEvent>> {
self.rx.poll_recv(cx)
}
}
pub fn map_sig(_signal: russh::Sig) -> Option<i32> {
None
}