From c102e845cd96dd14fc3ea928eb3c56cf884f8f35 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Tue, 2 Jun 2020 22:31:06 +0100 Subject: [PATCH] Add cargo feature for WinPTY --- CHANGELOG.md | 1 + alacritty/Cargo.toml | 5 +- alacritty_terminal/Cargo.toml | 4 +- .../src/tty/windows/automatic_backend.rs | 189 ++++++++++++++++++ alacritty_terminal/src/tty/windows/conpty.rs | 10 +- alacritty_terminal/src/tty/windows/mod.rs | 184 ++++------------- alacritty_terminal/src/tty/windows/winpty.rs | 10 +- 7 files changed, 236 insertions(+), 167 deletions(-) create mode 100644 alacritty_terminal/src/tty/windows/automatic_backend.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f9e59e2..0f79a68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Option `cursor.thickness` to set terminal cursor thickness - Font fallback on Windows - Support for Fontconfig embolden and matrix options +- Opt-out compilation flag `winpty` to disable WinPTY support ### Changed diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 4033e58..3a352f1 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -11,7 +11,7 @@ edition = "2018" [dependencies] alacritty_terminal = { path = "../alacritty_terminal", default-features = false } clap = "2" -log = "0.4" +log = { version = "0.4", features = ["std"] } time = "0.1.40" fnv = "1" serde = { version = "1", features = ["derive"] } @@ -47,9 +47,10 @@ winapi = { version = "0.3.7", features = ["impl-default", "wincon"]} embed-resource = "1.3" [features] -default = ["wayland", "x11"] +default = ["wayland", "x11", "winpty"] x11 = ["alacritty_terminal/x11"] wayland = ["alacritty_terminal/wayland"] +winpty = ["alacritty_terminal/winpty"] # Enabling this feature makes shaders automatically reload when changed live-shader-reload = [] nightly = [] diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index 99ed6a2..f6f0f36 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -29,7 +29,7 @@ nix = "0.17.0" signal-hook = { version = "0.1", features = ["mio-support"] } [target.'cfg(windows)'.dependencies] -winpty = "0.2.0" +winpty = { version = "0.2.0", optional = true } mio-named-pipes = "0.1" miow = "0.3" winapi = { version = "0.3.7", features = [ @@ -42,7 +42,7 @@ mio-anonymous-pipes = "0.1" objc = "0.2.2" [features] -default = ["x11", "wayland"] +default = ["x11", "wayland", "winpty"] x11 = ["copypasta/x11"] wayland = ["copypasta/wayland"] nightly = [] diff --git a/alacritty_terminal/src/tty/windows/automatic_backend.rs b/alacritty_terminal/src/tty/windows/automatic_backend.rs new file mode 100644 index 0000000..74a546d --- /dev/null +++ b/alacritty_terminal/src/tty/windows/automatic_backend.rs @@ -0,0 +1,189 @@ +/// Types to determine the appropriate PTY backend at runtime. +/// +/// Unless the winpty feature is disabled, the PTY backend will automatically fall back to +/// WinPTY when the newer ConPTY API is not supported, as long as the user hasn't explicitly +/// opted into the WinPTY config option. +use std::io::{self, Read, Write}; + +use log::info; +use mio::{Evented, Poll, PollOpt, Ready, Token}; +use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite}; +use mio_named_pipes::NamedPipe; + +use crate::config::Config; +use crate::event::OnResize; +use crate::term::SizeInfo; + +use super::{conpty, winpty, Pty}; + +pub fn new(config: &Config, size: &SizeInfo, window_id: Option) -> Pty { + if let Some(pty) = conpty::new(config, size, window_id) { + info!("Using ConPTY backend"); + pty + } else { + info!("Using WinPTY backend"); + winpty::new(config, size, window_id) + } +} + +pub enum PtyBackend { + Winpty(winpty::Agent), + Conpty(conpty::Conpty), +} + +impl OnResize for PtyBackend { + fn on_resize(&mut self, size: &SizeInfo) { + match self { + PtyBackend::Winpty(w) => w.on_resize(size), + PtyBackend::Conpty(c) => c.on_resize(size), + } + } +} + +// TODO: The ConPTY API currently must use synchronous pipes as the input +// and output handles. This has led to the need to support two different +// types of pipe. +// +// When https://github.com/Microsoft/console/issues/262 lands then the +// Anonymous variant of this enum can be removed from the codebase and +// everything can just use NamedPipe. +pub enum EventedReadablePipe { + Anonymous(EventedAnonRead), + Named(NamedPipe), +} + +pub enum EventedWritablePipe { + Anonymous(EventedAnonWrite), + Named(NamedPipe), +} + +impl Evented for EventedReadablePipe { + fn register( + &self, + poll: &Poll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + match self { + EventedReadablePipe::Anonymous(p) => p.register(poll, token, interest, opts), + EventedReadablePipe::Named(p) => p.register(poll, token, interest, opts), + } + } + + fn reregister( + &self, + poll: &Poll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + match self { + EventedReadablePipe::Anonymous(p) => p.reregister(poll, token, interest, opts), + EventedReadablePipe::Named(p) => p.reregister(poll, token, interest, opts), + } + } + + fn deregister(&self, poll: &Poll) -> io::Result<()> { + match self { + EventedReadablePipe::Anonymous(p) => p.deregister(poll), + EventedReadablePipe::Named(p) => p.deregister(poll), + } + } +} + +impl Read for EventedReadablePipe { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + EventedReadablePipe::Anonymous(p) => p.read(buf), + EventedReadablePipe::Named(p) => p.read(buf), + } + } +} + +impl Evented for EventedWritablePipe { + fn register( + &self, + poll: &Poll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + match self { + EventedWritablePipe::Anonymous(p) => p.register(poll, token, interest, opts), + EventedWritablePipe::Named(p) => p.register(poll, token, interest, opts), + } + } + + fn reregister( + &self, + poll: &Poll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + match self { + EventedWritablePipe::Anonymous(p) => p.reregister(poll, token, interest, opts), + EventedWritablePipe::Named(p) => p.reregister(poll, token, interest, opts), + } + } + + fn deregister(&self, poll: &Poll) -> io::Result<()> { + match self { + EventedWritablePipe::Anonymous(p) => p.deregister(poll), + EventedWritablePipe::Named(p) => p.deregister(poll), + } + } +} + +impl Write for EventedWritablePipe { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + EventedWritablePipe::Anonymous(p) => p.write(buf), + EventedWritablePipe::Named(p) => p.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match self { + EventedWritablePipe::Anonymous(p) => p.flush(), + EventedWritablePipe::Named(p) => p.flush(), + } + } +} + +impl From for PtyBackend { + fn from(inner: winpty::Agent) -> Self { + PtyBackend::Winpty(inner) + } +} + +impl From for PtyBackend { + fn from(inner: conpty::Conpty) -> Self { + PtyBackend::Conpty(inner) + } +} + +impl From for EventedReadablePipe { + fn from(inner: EventedAnonRead) -> Self { + EventedReadablePipe::Anonymous(inner) + } +} + +impl From for EventedReadablePipe { + fn from(inner: NamedPipe) -> Self { + EventedReadablePipe::Named(inner) + } +} + +impl From for EventedWritablePipe { + fn from(inner: EventedAnonWrite) -> Self { + EventedWritablePipe::Anonymous(inner) + } +} + +impl From for EventedWritablePipe { + fn from(inner: NamedPipe) -> Self { + EventedWritablePipe::Named(inner) + } +} diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index 2056f3d..561df71 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -231,15 +231,7 @@ pub fn new(config: &Config, size: &SizeInfo, _window_id: Option) -> let child_watcher = ChildExitWatcher::new(proc_info.hProcess).unwrap(); let conpty = Conpty { handle: pty_handle, api }; - Some(Pty { - backend: super::PtyBackend::Conpty(conpty), - conout: super::EventedReadablePipe::Anonymous(conout), - conin: super::EventedWritablePipe::Anonymous(conin), - read_token: 0.into(), - write_token: 0.into(), - child_event_token: 0.into(), - child_watcher, - }) + Some(Pty::new(conpty, conout, conin, child_watcher)) } // Panic with the last os error as message. diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs index 8e5b466..47b03d9 100644 --- a/alacritty_terminal/src/tty/windows/mod.rs +++ b/alacritty_terminal/src/tty/windows/mod.rs @@ -13,181 +13,78 @@ // limitations under the License. use std::ffi::OsStr; -use std::io::{self, Read, Write}; +use std::io; use std::iter::once; use std::os::windows::ffi::OsStrExt; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::TryRecvError; -use mio::{self, Evented, Poll, PollOpt, Ready, Token}; -use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite}; -use mio_named_pipes::NamedPipe; - -use log::info; - use crate::config::{Config, Shell}; use crate::event::OnResize; use crate::term::SizeInfo; use crate::tty::windows::child::ChildExitWatcher; use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; +#[cfg(feature = "winpty")] +mod automatic_backend; mod child; mod conpty; +#[cfg(feature = "winpty")] mod winpty; -static IS_CONPTY: AtomicBool = AtomicBool::new(false); +#[cfg(not(feature = "winpty"))] +use conpty::Conpty as Backend; +#[cfg(not(feature = "winpty"))] +use mio_anonymous_pipes::{EventedAnonRead as ReadPipe, EventedAnonWrite as WritePipe}; -pub fn is_conpty() -> bool { - IS_CONPTY.load(Ordering::Relaxed) -} - -enum PtyBackend { - Winpty(winpty::Agent), - Conpty(conpty::Conpty), -} +#[cfg(feature = "winpty")] +use automatic_backend::{ + EventedReadablePipe as ReadPipe, EventedWritablePipe as WritePipe, PtyBackend as Backend, +}; pub struct Pty { // XXX: Backend is required to be the first field, to ensure correct drop order. Dropping - // `conout` before `backend` will cause a deadlock. - backend: PtyBackend, - // TODO: It's on the roadmap for the Conpty API to support Overlapped I/O. - // See https://github.com/Microsoft/console/issues/262. - // When support for that lands then it should be possible to use - // NamedPipe for the conout and conin handles. - conout: EventedReadablePipe, - conin: EventedWritablePipe, + // `conout` before `backend` will cause a deadlock (with Conpty). + backend: Backend, + conout: ReadPipe, + conin: WritePipe, read_token: mio::Token, write_token: mio::Token, child_event_token: mio::Token, child_watcher: ChildExitWatcher, } +#[cfg(not(feature = "winpty"))] pub fn new(config: &Config, size: &SizeInfo, window_id: Option) -> Pty { - if let Some(pty) = conpty::new(config, size, window_id) { - info!("Using ConPTY backend"); - IS_CONPTY.store(true, Ordering::Relaxed); - pty - } else { - info!("Using WinPTY backend"); - winpty::new(config, size, window_id) - } + conpty::new(config, size, window_id).expect("Failed to create ConPTY backend") } -// TODO: The ConPTY API currently must use synchronous pipes as the input -// and output handles. This has led to the need to support two different -// types of pipe. -// -// When https://github.com/Microsoft/console/issues/262 lands then the -// Anonymous variant of this enum can be removed from the codebase and -// everything can just use NamedPipe. -pub enum EventedReadablePipe { - Anonymous(EventedAnonRead), - Named(NamedPipe), +#[cfg(feature = "winpty")] +pub fn new(config: &Config, size: &SizeInfo, window_id: Option) -> Pty { + automatic_backend::new(config, size, window_id) } -pub enum EventedWritablePipe { - Anonymous(EventedAnonWrite), - Named(NamedPipe), -} - -impl Evented for EventedReadablePipe { - fn register( - &self, - poll: &Poll, - token: Token, - interest: Ready, - opts: PollOpt, - ) -> io::Result<()> { - match self { - EventedReadablePipe::Anonymous(p) => p.register(poll, token, interest, opts), - EventedReadablePipe::Named(p) => p.register(poll, token, interest, opts), - } - } - - fn reregister( - &self, - poll: &Poll, - token: Token, - interest: Ready, - opts: PollOpt, - ) -> io::Result<()> { - match self { - EventedReadablePipe::Anonymous(p) => p.reregister(poll, token, interest, opts), - EventedReadablePipe::Named(p) => p.reregister(poll, token, interest, opts), - } - } - - fn deregister(&self, poll: &Poll) -> io::Result<()> { - match self { - EventedReadablePipe::Anonymous(p) => p.deregister(poll), - EventedReadablePipe::Named(p) => p.deregister(poll), - } - } -} - -impl Read for EventedReadablePipe { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self { - EventedReadablePipe::Anonymous(p) => p.read(buf), - EventedReadablePipe::Named(p) => p.read(buf), - } - } -} - -impl Evented for EventedWritablePipe { - fn register( - &self, - poll: &Poll, - token: Token, - interest: Ready, - opts: PollOpt, - ) -> io::Result<()> { - match self { - EventedWritablePipe::Anonymous(p) => p.register(poll, token, interest, opts), - EventedWritablePipe::Named(p) => p.register(poll, token, interest, opts), - } - } - - fn reregister( - &self, - poll: &Poll, - token: Token, - interest: Ready, - opts: PollOpt, - ) -> io::Result<()> { - match self { - EventedWritablePipe::Anonymous(p) => p.reregister(poll, token, interest, opts), - EventedWritablePipe::Named(p) => p.reregister(poll, token, interest, opts), - } - } - - fn deregister(&self, poll: &Poll) -> io::Result<()> { - match self { - EventedWritablePipe::Anonymous(p) => p.deregister(poll), - EventedWritablePipe::Named(p) => p.deregister(poll), - } - } -} - -impl Write for EventedWritablePipe { - fn write(&mut self, buf: &[u8]) -> io::Result { - match self { - EventedWritablePipe::Anonymous(p) => p.write(buf), - EventedWritablePipe::Named(p) => p.write(buf), - } - } - - fn flush(&mut self) -> io::Result<()> { - match self { - EventedWritablePipe::Anonymous(p) => p.flush(), - EventedWritablePipe::Named(p) => p.flush(), +impl Pty { + fn new( + backend: impl Into, + conout: impl Into, + conin: impl Into, + child_watcher: ChildExitWatcher, + ) -> Self { + Self { + backend: backend.into(), + conout: conout.into(), + conin: conin.into(), + read_token: 0.into(), + write_token: 0.into(), + child_event_token: 0.into(), + child_watcher, } } } impl EventedReadWrite for Pty { - type Reader = EventedReadablePipe; - type Writer = EventedWritablePipe; + type Reader = ReadPipe; + type Writer = WritePipe; #[inline] fn register( @@ -295,10 +192,7 @@ impl EventedPty for Pty { impl OnResize for Pty { fn on_resize(&mut self, size: &SizeInfo) { - match &mut self.backend { - PtyBackend::Winpty(w) => w.on_resize(size), - PtyBackend::Conpty(c) => c.on_resize(size), - } + self.backend.on_resize(size) } } diff --git a/alacritty_terminal/src/tty/windows/winpty.rs b/alacritty_terminal/src/tty/windows/winpty.rs index d466955..acfa674 100644 --- a/alacritty_terminal/src/tty/windows/winpty.rs +++ b/alacritty_terminal/src/tty/windows/winpty.rs @@ -70,15 +70,7 @@ pub fn new(config: &Config, size: &SizeInfo, _window_id: Option) -> let child_watcher = ChildExitWatcher::new(agent.raw_handle()).unwrap(); - Pty { - backend: super::PtyBackend::Winpty(agent), - conout: super::EventedReadablePipe::Named(conout_pipe), - conin: super::EventedWritablePipe::Named(conin_pipe), - read_token: 0.into(), - write_token: 0.into(), - child_event_token: 0.into(), - child_watcher, - } + Pty::new(agent, conout_pipe, conin_pipe, child_watcher) } impl OnResize for Agent {