1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-11 14:25:57 +03:00

Now with something approximating windows support

I've run hello.rs under wine, but even though wine seems
to happily report that the windows 10 escapes are handled,
they are not.   So more work is needed.
This commit is contained in:
Wez Furlong 2018-07-20 20:39:16 -07:00
parent 439c0eba59
commit 7d83833e8c
7 changed files with 266 additions and 5 deletions

3
.cargo/config Normal file
View File

@ -0,0 +1,3 @@
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
ar = "x86_64-w64-mingw32-gcc-ar"

View File

@ -15,9 +15,14 @@ num = "0.2.0"
num-traits = "0.2.5"
derive_builder = "0.5.1"
semver = "0.9.0"
termios = "0.3.0"
libc = "0.2.42"
[target.'cfg(windows)'.dependencies]
winapi = {version = "~0.3", features = ["winuser", "consoleapi"]}
[target.'cfg(unix)'.dependencies]
termios = "0.3.0"
[dependencies.num-derive]
version = "~0.2"
features = ["full-syntax"]

19
README.md Normal file
View File

@ -0,0 +1,19 @@
## Windows
Testing via Wine:
```
sudo apt install gcc-mingw-w64-x86-64
rustup target add x86_64-pc-windows-gnu
cargo build --target=x86_64-pc-windows-gnu --example hello
```
Then, from an X session of some kind:
```
wineconsole cmd.exe
```
and from there you can launch the generated .exe files; they are found under `target/x86_64-pc-windows-gnu/debug`

View File

@ -8,13 +8,23 @@ use termwiz::color::AnsiColor;
use termwiz::render::terminfo::TerminfoRenderer;
use termwiz::render::Renderer;
use termwiz::screen::{Change, Screen};
use termwiz::terminal::{Terminal, UnixTerminal};
use termwiz::terminal::{self, Terminal};
#[cfg(unix)]
fn get_terminal() -> Result<impl Terminal, failure::Error> {
terminal::UnixTerminal::new()
}
#[cfg(windows)]
fn get_terminal() -> Result<impl Terminal, failure::Error> {
terminal::WindowsTerminal::new()
}
fn main() -> Result<(), Error> {
let caps = Capabilities::new_from_env()?;
let mut renderer = TerminfoRenderer::new(caps);
let mut terminal = UnixTerminal::new()?;
let mut terminal = get_terminal()?;
terminal.set_raw_mode()?;
let size = terminal.get_screen_size()?;

View File

@ -4,8 +4,12 @@
//! return true if the item represents a terminal.
//! The implementation at the time of writing is unix centric, but
//! the trait could also be ported to Windows and have meaning.
use libc;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
#[cfg(windows)]
use std::os::windows::io::AsRawHandle;
#[cfg(windows)]
use winapi::um::consoleapi::GetConsoleMode;
/// Adds the is_tty method to types that might represent a terminal
pub trait IsTty {
@ -17,9 +21,20 @@ pub trait IsTty {
/// On unix, the `isatty()` library function returns true if a file
/// descriptor is a terminal. Let's implement `IsTty` for anything
/// that has an associated raw file descriptor.
#[cfg(unix)]
impl<S: AsRawFd> IsTty for S {
fn is_tty(&self) -> bool {
use libc;
let fd = self.as_raw_fd();
unsafe { libc::isatty(fd) == 1 }
}
}
#[cfg(windows)]
impl<S: AsRawHandle> IsTty for S {
fn is_tty(&self) -> bool {
let mut mode = 0;
let ok = unsafe { GetConsoleMode(self.as_raw_handle(), &mut mode) };
ok == 1
}
}

View File

@ -5,7 +5,10 @@ extern crate palette;
extern crate semver;
extern crate serde;
extern crate terminfo;
#[cfg(unix)]
extern crate termios;
#[cfg(windows)]
extern crate winapi;
#[macro_use]
extern crate serde_derive;
extern crate num;

View File

@ -6,15 +6,28 @@
use failure::Error;
use istty::IsTty;
#[cfg(unix)]
use libc::{self, winsize};
use num::{self, NumCast};
use std::fmt::Display;
use std::fs::{File, OpenOptions};
use std::fs::File;
use std::io::{stdin, stdout, Stdin, Stdout};
use std::io::{Error as IOError, Read, Result as IOResult, Write};
use std::mem;
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
#[cfg(unix)]
use termios::{cfmakeraw, tcsetattr, Termios, TCSANOW};
#[cfg(windows)]
use winapi::um::consoleapi;
#[cfg(windows)]
use winapi::um::wincon::{
GetConsoleScreenBufferInfo, SetConsoleScreenBufferSize, CONSOLE_SCREEN_BUFFER_INFO, COORD,
DISABLE_NEWLINE_AUTO_RETURN, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT,
ENABLE_VIRTUAL_TERMINAL_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
};
/// Represents the size of the terminal screen.
/// The number of rows and columns of character cells are expressed.
@ -90,6 +103,7 @@ impl Write for Handle {
}
}
#[cfg(unix)]
impl Handle {
fn writable_fd(&self) -> RawFd {
match self {
@ -99,15 +113,202 @@ impl Handle {
}
}
#[cfg(windows)]
impl Handle {
fn writable_handle(&self) -> RawHandle {
match self {
Handle::File(f) => f.as_raw_handle(),
Handle::Stdio { stdout, .. } => stdout.as_raw_handle(),
}
}
fn readable_handle(&self) -> RawHandle {
match self {
Handle::File(f) => f.as_raw_handle(),
Handle::Stdio { stdin, .. } => stdin.as_raw_handle(),
}
}
fn enable_virtual_terminal_processing(&self) -> Result<(), Error> {
let mode = self.get_console_output_mode()?;
self.set_console_output_mode(
mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN,
)?;
let mode = self.get_console_input_mode()?;
self.set_console_output_mode(mode | ENABLE_VIRTUAL_TERMINAL_INPUT)?;
Ok(())
}
fn get_console_input_mode(&self) -> Result<u32, Error> {
let mut mode = 0;
let handle = self.readable_handle();
if unsafe { consoleapi::GetConsoleMode(handle, &mut mode) } == 0 {
bail!("GetConsoleMode failed: {}", IOError::last_os_error());
}
Ok(mode)
}
fn set_console_input_mode(&self, mode: u32) -> Result<(), Error> {
let handle = self.readable_handle();
if unsafe { consoleapi::SetConsoleMode(handle, mode) } == 0 {
bail!("SetConsoleMode failed: {}", IOError::last_os_error());
}
Ok(())
}
fn get_console_output_mode(&self) -> Result<u32, Error> {
let mut mode = 0;
let handle = self.writable_handle();
if unsafe { consoleapi::GetConsoleMode(handle, &mut mode) } == 0 {
bail!("GetConsoleMode failed: {}", IOError::last_os_error());
}
Ok(mode)
}
fn set_console_output_mode(&self, mode: u32) -> Result<(), Error> {
let handle = self.writable_handle();
if unsafe { consoleapi::SetConsoleMode(handle, mode) } == 0 {
bail!("SetConsoleMode failed: {}", IOError::last_os_error());
}
Ok(())
}
}
const BUF_SIZE: usize = 128;
#[cfg(windows)]
pub struct WindowsTerminal {
handle: Handle,
write_buffer: Vec<u8>,
saved_input_mode: u32,
saved_output_mode: u32,
}
#[cfg(windows)]
impl Drop for WindowsTerminal {
fn drop(&mut self) {
self.handle
.set_console_input_mode(self.saved_input_mode)
.expect("failed to restore console input mode");
self.handle
.set_console_output_mode(self.saved_output_mode)
.expect("failed to restore console output mode");
}
}
#[cfg(windows)]
impl WindowsTerminal {
pub fn new() -> Result<Self, Error> {
let read = stdin();
let write = stdout();
if !read.is_tty() || !write.is_tty() {
bail!("stdin and stdout must both be tty handles");
}
let handle = Handle::Stdio {
stdin: read,
stdout: write,
};
let saved_input_mode = handle.get_console_input_mode()?;
let saved_output_mode = handle.get_console_output_mode()?;
handle.enable_virtual_terminal_processing()?;
Ok(Self {
handle,
saved_input_mode,
saved_output_mode,
write_buffer: Vec::with_capacity(BUF_SIZE),
})
}
}
#[cfg(windows)]
impl Read for WindowsTerminal {
fn read(&mut self, buf: &mut [u8]) -> IOResult<usize> {
self.handle.read(buf)
}
}
#[cfg(windows)]
impl Write for WindowsTerminal {
fn write(&mut self, buf: &[u8]) -> IOResult<usize> {
if self.write_buffer.len() + buf.len() > self.write_buffer.capacity() {
self.flush()?;
}
if buf.len() >= self.write_buffer.capacity() {
self.handle.write(buf)
} else {
self.write_buffer.write(buf)
}
}
fn flush(&mut self) -> IOResult<()> {
if self.write_buffer.len() > 0 {
self.handle.write(&self.write_buffer)?;
self.write_buffer.clear();
}
self.handle.flush()
}
}
#[cfg(windows)]
impl Terminal for WindowsTerminal {
fn set_raw_mode(&mut self) -> Result<(), Error> {
let mode = self.handle.get_console_input_mode()?;
self.handle.set_console_input_mode(
mode & !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT),
)
}
fn get_screen_size(&mut self) -> Result<ScreenSize, Error> {
let mut info: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() };
let handle = self.handle.writable_handle();
let ok = unsafe { GetConsoleScreenBufferInfo(handle, &mut info as *mut _) };
if ok != 1 {
bail!(
"failed to GetConsoleScreenBufferInfo: {}",
IOError::last_os_error()
);
}
Ok(ScreenSize {
rows: cast(info.dwSize.Y)?,
cols: cast(info.dwSize.X)?,
xpixel: 0,
ypixel: 0,
})
}
fn set_screen_size(&mut self, size: ScreenSize) -> Result<(), Error> {
let size = COORD {
X: cast(size.cols)?,
Y: cast(size.rows)?,
};
let handle = self.handle.writable_handle();
if unsafe { SetConsoleScreenBufferSize(handle, size) } != 1 {
bail!(
"failed to SetConsoleScreenBufferSize: {}",
IOError::last_os_error()
);
}
Ok(())
}
}
/// A unix style terminal
#[cfg(unix)]
pub struct UnixTerminal {
handle: Handle,
saved_termios: Termios,
write_buffer: Vec<u8>,
}
#[cfg(unix)]
impl UnixTerminal {
/// Attempt to create an instance from the stdin and stdout of the
/// process. This will fail unless both are associated with a tty.
@ -135,6 +336,7 @@ impl UnixTerminal {
/// yield a terminal even if the stdio streams have been redirected,
/// provided that the process has an associated controlling terminal.
pub fn new() -> Result<UnixTerminal, Error> {
use std::fs::OpenOptions;
let file = OpenOptions::new().read(true).write(true).open("/dev/tty")?;
let saved_termios = Termios::from_fd(file.as_raw_fd())?;
Ok(UnixTerminal {
@ -145,12 +347,14 @@ impl UnixTerminal {
}
}
#[cfg(unix)]
impl Read for UnixTerminal {
fn read(&mut self, buf: &mut [u8]) -> IOResult<usize> {
self.handle.read(buf)
}
}
#[cfg(unix)]
impl Write for UnixTerminal {
fn write(&mut self, buf: &[u8]) -> IOResult<usize> {
if self.write_buffer.len() + buf.len() > self.write_buffer.capacity() {
@ -176,6 +380,7 @@ fn cast<T: NumCast + Display + Copy, U: NumCast>(n: T) -> Result<U, Error> {
num::cast(n).ok_or_else(|| format_err!("{} is out of bounds for this system", n))
}
#[cfg(unix)]
impl Terminal for UnixTerminal {
fn set_raw_mode(&mut self) -> Result<(), Error> {
let fd = self.handle.writable_fd();
@ -219,6 +424,7 @@ impl Terminal for UnixTerminal {
}
}
#[cfg(unix)]
impl Drop for UnixTerminal {
fn drop(&mut self) {
let fd = self.handle.writable_fd();