Add support for Windows ConPTY API

This commit is contained in:
David Hewitt 2018-12-28 16:01:58 +00:00 committed by Christian Duerr
parent ec6f756946
commit f1bc6802e1
10 changed files with 762 additions and 185 deletions

View File

@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New configuration field `visual_bell.color` allows changing the visual bell color
- Crashes on Windows are now also reported with a popup in addition to stderr
- Windows: New configuration field `enable_experimental_conpty_backend` which enables support
for the Pseudoconsole API (ConPTY) added in Windows 10 October 2018 (1809) update
### Changed
@ -22,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix panic after quitting Alacritty on macOS
- Tabs are no longer replaced by spaces when copying them to the clipboard
- Alt modifier is no longer sent separately from the modified key
- Various Windows issues, like color support and performance, through the new ConPTY
## Version 0.2.4

53
Cargo.lock generated
View File

@ -51,14 +51,16 @@ dependencies = [
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-anonymous-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"notify 4.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
"static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -68,6 +70,7 @@ dependencies = [
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"vte 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winpty 0.1.0",
"x11-dl 2.18.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1268,6 +1271,17 @@ dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mio-anonymous-pipes"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"spsc-buffer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mio-extras"
version = "2.0.5"
@ -1913,7 +1927,7 @@ dependencies = [
"mime 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_urlencoded 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2029,12 +2043,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_derive"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2049,7 +2063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2059,7 +2073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -2070,7 +2084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -2129,7 +2143,7 @@ dependencies = [
[[package]]
name = "smithay-client-toolkit"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"andrew 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2155,6 +2169,11 @@ dependencies = [
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "spsc-buffer"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "stable_deref_trait"
version = "1.1.1"
@ -2645,6 +2664,11 @@ name = "widestring"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "widestring"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.2.8"
@ -2707,7 +2731,7 @@ dependencies = [
"objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smithay-client-toolkit 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"smithay-client-toolkit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"wayland-client 0.21.7 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"x11-dl 2.18.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2947,6 +2971,7 @@ dependencies = [
"checksum miniz_oxide 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5ad30a47319c16cde58d0314f5d98202a80c9083b5f61178457403dfb14e509c"
"checksum miniz_oxide_c_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "28edaef377517fd9fe3e085c37d892ce7acd1fbeab9239c5a36eec352d8a8b7e"
"checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432"
"checksum mio-anonymous-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8c274c3c52dcd1d78c5d7ed841eca1e9ea2db8353f3b8ec25789cc62c471aaf"
"checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40"
"checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
@ -3031,8 +3056,8 @@ dependencies = [
"checksum security-framework-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "40d95f3d7da09612affe897f320d78264f0d2320f3e8eea27d12bd1bd94445e2"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa52f19aee12441d5ad11c9a00459122bd8f98707cadf9778c540674f1935b6"
"checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154"
"checksum serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)" = "157e12af46859e968da75dea9845530e13d03bcab2009a41b9b7bb3cf4eb3ec2"
"checksum serde_derive 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)" = "9469829702497daf2daf3c190e130c3fa72f719920f73c86160d43e8f8d76951"
"checksum serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "c37ccd6be3ed1fdf419ee848f7c758eb31b054d7cd3ae3600e3bae0adf569811"
"checksum serde_urlencoded 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d48f9f99cd749a2de71d29da5f948de7f2764cc5a9d7f3c97e3514d4ee6eabf2"
"checksum serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0887a8e097a69559b56aa2526bf7aff7c3048cf627dff781f0b56a6001534593"
@ -3042,8 +3067,9 @@ dependencies = [
"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
"checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d"
"checksum smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b73ea3738b47563803ef814925e69be00799a8c07420be8b996f8e98fb2336db"
"checksum smithay-client-toolkit 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4bfd1c912756e610ab598d60fb16adeb3b6745ac0b0a4a2cc1a6b9fa88111409"
"checksum smithay-client-toolkit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d858330eeed4efaf71c560555e2a6a0597d01b7d52685c3cc964ab1cc360f8c6"
"checksum socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d11a52082057d87cb5caa31ad812f4504b97ab44732cd8359df2e9ff9f48e7"
"checksum spsc-buffer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be6c3f39c37a4283ee4b43d1311c828f2e1fb0541e76ea0cb1a2abd9ef2f5b3b"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "389ce475f424f267dbed6479cbd8f126c5e1afb053b0acdaa019c74305fc65d1"
"checksum stb_truetype 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71a7d260b43b6129a22dc341be18a231044ca67a48b7e32625f380cc5ec9ad70"
@ -3099,6 +3125,7 @@ dependencies = [
"checksum wayland-sys 0.21.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a0931c24c91e4e56c1119e4137e237df2ccc3696df94f64b1e2f61982d89cc32"
"checksum which 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e84a603e7e0b1ce1aa1ee2b109c7be00155ce52df5081590d1ffb93f4f515cb2"
"checksum widestring 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7157704c2e12e3d2189c507b7482c52820a16dfa4465ba91add92f266667cadb"
"checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"

View File

@ -58,9 +58,12 @@ x11-dl = "2"
[target.'cfg(windows)'.dependencies]
winpty = { path = "./winpty" }
mio-named-pipes = "0.1"
winapi = { version = "0.3.5", features = ["winuser", "synchapi", "roerrorapi", "winerror"]}
miow = "0.3"
winapi = { version = "0.3.5", features = ["impl-default", "winuser", "synchapi", "roerrorapi", "winerror", "wincon"]}
dunce = "0.1"
dirs = "1.0"
widestring = "0.4"
mio-anonymous-pipes = "0.1"
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.2"

View File

@ -283,6 +283,17 @@ shell:
#args:
# - --login
# Windows 10 ConPTY backend
#
# This will enable better color support and may resolve other issues,
# however this API and its implementation is still young and so is
# disabled by default, as stability may not be as good as the winpty
# backend.
#
# Alacritty will fall back to the WinPTY automatically if the ConPTY
# backend cannot be initialized.
enable_experimental_conpty_backend: false
# Key bindings
#
# Key bindings are specified as a list of objects. Each binding will specify

View File

@ -541,6 +541,12 @@ pub struct Config {
// TODO: DEPRECATED
#[serde(default, deserialize_with = "failure_default")]
unfocused_hollow_cursor: Option<bool>,
/// Enable experimental conpty backend instead of using winpty.
/// Will only take effect on Windows 10 Oct 2018 and later.
#[cfg(windows)]
#[serde(default, deserialize_with="failure_default")]
enable_experimental_conpty_backend: bool
}
fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T>, D::Error>
@ -1709,6 +1715,13 @@ impl Config {
self.colors.cursor.cursor.map(|_| Color::Named(NamedColor::Cursor))
}
/// Enable experimental conpty backend (Windows only)
#[cfg(windows)]
#[inline]
pub fn enable_experimental_conpty_backend(&self) -> bool {
self.enable_experimental_conpty_backend
}
// Update the history size, used in ref tests
pub fn set_history(&mut self, history: u32) {
self.scrolling.history = history;

View File

@ -20,19 +20,6 @@
#[macro_use] extern crate log;
#[macro_use] extern crate serde_derive;
#[cfg(windows)]
extern crate mio_named_pipes;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
extern crate winpty;
#[cfg(windows)]
extern crate dunce;
#[cfg(windows)]
extern crate image;
#[cfg(windows)]
extern crate dirs;
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;

View File

@ -160,9 +160,9 @@ fn run(
// and we need to be able to resize the PTY from the main thread while the IO
// thread owns the EventedRW object.
#[cfg(windows)]
let resize_handle = unsafe { &mut *pty.winpty.get() };
let mut resize_handle = pty.resize_handle();
#[cfg(not(windows))]
let resize_handle = &mut pty.fd.as_raw_fd();
let mut resize_handle = pty.fd.as_raw_fd();
// Create the pseudoterminal I/O loop
//
@ -239,7 +239,7 @@ fn run(
//
// The second argument is a list of types that want to be notified
// of display size changes.
display.handle_resize(&mut terminal_lock, &config, &mut [resize_handle, &mut processor]);
display.handle_resize(&mut terminal_lock, &config, &mut [&mut resize_handle, &mut processor]);
drop(terminal_lock);

303
src/tty/windows/conpty.rs Normal file
View File

@ -0,0 +1,303 @@
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{process_should_exit, Pty, HANDLE};
use std::env;
use std::i16;
use std::mem;
use std::os::windows::io::IntoRawHandle;
use std::ptr;
use std::sync::Arc;
use dunce::canonicalize;
use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
use miow;
use widestring::U16CString;
use winapi::ctypes::c_void;
use winapi::shared::basetsd::{PSIZE_T, SIZE_T};
use winapi::shared::minwindef::{BYTE, DWORD};
use winapi::shared::ntdef::{HANDLE, HRESULT, LPCWSTR, LPWSTR};
use winapi::shared::winerror::S_OK;
use winapi::um::libloaderapi::{GetModuleHandleA, GetProcAddress};
use winapi::um::processthreadsapi::{
CreateProcessW, InitializeProcThreadAttributeList, UpdateProcThreadAttribute,
PROCESS_INFORMATION, STARTUPINFOW,
};
use winapi::um::winbase::{EXTENDED_STARTUPINFO_PRESENT, STARTUPINFOEXW};
use winapi::um::wincon::COORD;
use crate::cli::Options;
use crate::config::{Config, Shell};
use crate::display::OnResize;
use crate::term::SizeInfo;
// This will be merged into winapi as PR #699
// TODO: Use the winapi definition directly after that.
pub type HPCON = *mut c_void;
/// Dynamically-loaded Pseudoconsole API from kernel32.dll
///
/// The field names are deliberately PascalCase as this matches
/// the defined symbols in kernel32 and also is the convention
/// that the `winapi` crate follows.
#[allow(non_snake_case)]
struct ConptyApi {
CreatePseudoConsole:
unsafe extern "system" fn(COORD, HANDLE, HANDLE, DWORD, *mut HPCON) -> HRESULT,
ResizePseudoConsole: unsafe extern "system" fn(HPCON, COORD) -> HRESULT,
ClosePseudoConsole: unsafe extern "system" fn(HPCON) -> HRESULT,
}
impl ConptyApi {
/// Load the API or None if it cannot be found.
pub fn new() -> Option<Self> {
// Unsafe because windows API calls
unsafe {
let hmodule = GetModuleHandleA("kernel32\0".as_ptr() as _);
assert!(!hmodule.is_null());
let cpc = GetProcAddress(hmodule, "CreatePseudoConsole\0".as_ptr() as _);
let rpc = GetProcAddress(hmodule, "ResizePseudoConsole\0".as_ptr() as _);
let clpc = GetProcAddress(hmodule, "ClosePseudoConsole\0".as_ptr() as _);
if cpc.is_null() || rpc.is_null() || clpc.is_null() {
None
} else {
Some(Self {
CreatePseudoConsole: mem::transmute(cpc),
ResizePseudoConsole: mem::transmute(rpc),
ClosePseudoConsole: mem::transmute(clpc),
})
}
}
}
}
/// RAII Pseudoconsole
pub struct Conpty {
pub handle: HPCON,
api: ConptyApi,
}
/// Handle can be cloned freely and moved between threads.
pub type ConptyHandle = Arc<Conpty>;
impl Drop for Conpty {
fn drop(&mut self) {
// The pseusdoconsole might already have been closed by the console process exiting.
// ClosePseudoConsole will fail with error code 1 in that case.
//
// This check should be sufficient to avoid that.
if !process_should_exit() {
let result = unsafe { (self.api.ClosePseudoConsole)(self.handle) };
// As noted above, if the pseudoconsole is already closed then
// ClosePseudoConsole will fail with the error code 1.
// (This was not in the MSDN docs as of Nov 2018.)
//
// If ClosePseudoConsole is successful then result is S_OK.
assert!(result == S_OK);
}
}
}
// The Conpty API can be accessed from multiple threads.
unsafe impl Send for Conpty {}
unsafe impl Sync for Conpty {}
pub fn new<'a>(
config: &Config,
options: &Options,
size: &SizeInfo,
_window_id: Option<usize>,
) -> Option<Pty<'a>> {
if !config.enable_experimental_conpty_backend() {
return None;
}
let api = ConptyApi::new()?;
let mut pty_handle = 0 as HPCON;
// Passing 0 as the size parameter allows the "system default" buffer
// size to be used. There may be small performance and memory advantages
// to be gained by tuning this in the future, but it's likely a reasonable
// start point.
let (conout, conout_pty_handle) = miow::pipe::anonymous(0).unwrap();
let (conin_pty_handle, conin) = miow::pipe::anonymous(0).unwrap();
let coord =
coord_from_sizeinfo(size).expect("Overflow when creating initial size on pseudoconsole");
// Create the Pseudo Console, using the pipes
let result = unsafe {
(api.CreatePseudoConsole)(
coord,
conin_pty_handle.into_raw_handle(),
conout_pty_handle.into_raw_handle(),
0,
&mut pty_handle as *mut HPCON,
)
};
assert!(result == S_OK);
let mut success;
// Prepare child process startup info
let mut size: SIZE_T = 0;
let mut startup_info_ex: STARTUPINFOEXW = Default::default();
startup_info_ex.StartupInfo.cb = mem::size_of::<STARTUPINFOEXW>() as u32;
// Create the appropriately sized thread attribute list.
unsafe {
success =
InitializeProcThreadAttributeList(ptr::null_mut(), 1, 0, &mut size as PSIZE_T) > 0;
// This call was expected to return false.
assert!(!success);
}
let mut attr_list: Box<[BYTE]> = vec![0; size].into_boxed_slice();
// Set startup info's attribute list & initialize it
//
// Lint failure is spurious; it's because winapi's definition of PROC_THREAD_ATTRIBUTE_LIST
// implies it is one pointer in size (32 or 64 bits) but really this is just a dummy value.
// Casting a *mut u8 (pointer to 8 bit type) might therefore not be aligned correctly in
// the compiler's eyes.
#[allow(clippy::cast_ptr_alignment)]
{
startup_info_ex.lpAttributeList = attr_list.as_mut_ptr() as _;
}
unsafe {
success = InitializeProcThreadAttributeList(
startup_info_ex.lpAttributeList,
1,
0,
&mut size as PSIZE_T,
) > 0;
assert!(success);
}
// Set thread attribute list's Pseudo Console to the specified ConPTY
unsafe {
success = UpdateProcThreadAttribute(
startup_info_ex.lpAttributeList,
0,
22 | 0x0002_0000, // PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
pty_handle,
mem::size_of::<HPCON>(),
ptr::null_mut(),
ptr::null_mut(),
) > 0;
assert!(success);
}
// Get process commandline
let default_shell = &Shell::new(env::var("COMSPEC").unwrap_or_else(|_| "cmd".into()));
let shell = config.shell().unwrap_or(default_shell);
let initial_command = options.command().unwrap_or(shell);
let mut cmdline = initial_command.args().to_vec();
cmdline.insert(0, initial_command.program().into());
// Warning, here be borrow hell
let cwd = options
.working_dir
.as_ref()
.map(|dir| canonicalize(dir).unwrap());
let cwd = cwd.as_ref().map(|dir| dir.to_str().unwrap());
// Create the client application, using startup info containing ConPTY info
let cmdline = U16CString::from_str(&cmdline.join(" ")).unwrap().into_raw();
let cwd = cwd.map(|s| U16CString::from_str(&s).unwrap());
let cwd_ptr = match &cwd {
Some(b) => b.as_ptr() as LPCWSTR,
None => ptr::null(),
};
let mut proc_info: PROCESS_INFORMATION = Default::default();
unsafe {
success = CreateProcessW(
ptr::null(),
cmdline as LPWSTR,
ptr::null_mut(),
ptr::null_mut(),
true as i32,
EXTENDED_STARTUPINFO_PRESENT,
ptr::null_mut(),
cwd_ptr,
&mut startup_info_ex.StartupInfo as *mut STARTUPINFOW,
&mut proc_info as *mut PROCESS_INFORMATION,
) > 0;
assert!(success);
}
// Recover raw memory to cmdline so it can be freed
unsafe {
U16CString::from_raw(cmdline);
}
// Store handle to console
unsafe {
HANDLE = proc_info.hProcess;
}
let conin = EventedAnonWrite::new(conin);
let conout = EventedAnonRead::new(conout);
let agent = Conpty {
handle: pty_handle,
api,
};
Some(Pty {
handle: super::PtyHandle::Conpty(ConptyHandle::new(agent)),
conout: super::EventedReadablePipe::Anonymous(conout),
conin: super::EventedWritablePipe::Anonymous(conin),
read_token: 0.into(),
write_token: 0.into(),
})
}
impl OnResize for ConptyHandle {
fn on_resize(&mut self, sizeinfo: &SizeInfo) {
if let Some(coord) = coord_from_sizeinfo(sizeinfo) {
let result = unsafe { (self.api.ResizePseudoConsole)(self.handle, coord) };
assert!(result == S_OK);
}
}
}
/// Helper to build a COORD from a SizeInfo, returing None in overflow cases.
fn coord_from_sizeinfo(sizeinfo: &SizeInfo) -> Option<COORD> {
let cols = sizeinfo.cols().0;
let lines = sizeinfo.lines().0;
if cols <= i16::MAX as usize && lines <= i16::MAX as usize {
Some(COORD {
X: sizeinfo.cols().0 as i16,
Y: sizeinfo.lines().0 as i16,
})
} else {
None
}
}

334
src/tty/windows/mod.rs Normal file
View File

@ -0,0 +1,334 @@
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::io::{self, Read, Write};
use std::os::raw::c_void;
use mio::{self, Evented, Poll, PollOpt, Ready, Token};
use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
use mio_named_pipes::NamedPipe;
use winapi::shared::winerror::WAIT_TIMEOUT;
use winapi::um::synchapi::WaitForSingleObject;
use winapi::um::winbase::WAIT_OBJECT_0;
use crate::cli::Options;
use crate::config::Config;
use crate::display::OnResize;
use crate::term::SizeInfo;
use crate::tty::EventedReadWrite;
mod conpty;
mod winpty;
/// Handle to the winpty agent or conpty process. Required so we know when it closes.
static mut HANDLE: *mut c_void = 0usize as *mut c_void;
pub fn process_should_exit() -> bool {
unsafe {
match WaitForSingleObject(HANDLE, 0) {
// Process has exited
WAIT_OBJECT_0 => {
info!("wait_object_0");
true
}
// Reached timeout of 0, process has not exited
WAIT_TIMEOUT => false,
// Error checking process, winpty gave us a bad agent handle?
_ => {
info!("Bad exit: {}", ::std::io::Error::last_os_error());
true
}
}
}
}
#[derive(Clone)]
pub enum PtyHandle<'a> {
Winpty(winpty::WinptyHandle<'a>),
Conpty(conpty::ConptyHandle),
}
pub struct Pty<'a> {
handle: PtyHandle<'a>,
// 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,
read_token: mio::Token,
write_token: mio::Token,
}
impl<'a> Pty<'a> {
pub fn resize_handle(&self) -> impl OnResize + 'a {
self.handle.clone()
}
}
pub fn new<'a>(
config: &Config,
options: &Options,
size: &SizeInfo,
window_id: Option<usize>,
) -> Pty<'a> {
if let Some(pty) = conpty::new(config, options, size, window_id) {
info!("Using Conpty agent.");
pty
} else {
info!("Using Winpty agent.");
winpty::new(config, options, size, window_id)
}
}
// TODO: The ConPTY API curently 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<usize> {
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<usize> {
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<'a> OnResize for PtyHandle<'a> {
fn on_resize(&mut self, sizeinfo: &SizeInfo) {
match self {
PtyHandle::Winpty(w) => w.winpty_mut().on_resize(sizeinfo),
PtyHandle::Conpty(c) => {
let mut handle = c.clone();
handle.on_resize(sizeinfo)
}
}
}
}
impl<'a> EventedReadWrite for Pty<'a> {
type Reader = EventedReadablePipe;
type Writer = EventedWritablePipe;
#[inline]
fn register(
&mut self,
poll: &mio::Poll,
token: &mut dyn Iterator<Item = &usize>,
interest: mio::Ready,
poll_opts: mio::PollOpt,
) -> io::Result<()> {
self.read_token = (*token.next().unwrap()).into();
self.write_token = (*token.next().unwrap()).into();
if interest.is_readable() {
poll.register(
&self.conout,
self.read_token,
mio::Ready::readable(),
poll_opts,
)?
} else {
poll.register(
&self.conout,
self.read_token,
mio::Ready::empty(),
poll_opts,
)?
}
if interest.is_writable() {
poll.register(
&self.conin,
self.write_token,
mio::Ready::writable(),
poll_opts,
)?
} else {
poll.register(
&self.conin,
self.write_token,
mio::Ready::empty(),
poll_opts,
)?
}
Ok(())
}
#[inline]
fn reregister(
&mut self,
poll: &mio::Poll,
interest: mio::Ready,
poll_opts: mio::PollOpt,
) -> io::Result<()> {
if interest.is_readable() {
poll.reregister(
&self.conout,
self.read_token,
mio::Ready::readable(),
poll_opts,
)?;
} else {
poll.reregister(
&self.conout,
self.read_token,
mio::Ready::empty(),
poll_opts,
)?;
}
if interest.is_writable() {
poll.reregister(
&self.conin,
self.write_token,
mio::Ready::writable(),
poll_opts,
)?;
} else {
poll.reregister(
&self.conin,
self.write_token,
mio::Ready::empty(),
poll_opts,
)?;
}
Ok(())
}
#[inline]
fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> {
poll.deregister(&self.conout)?;
poll.deregister(&self.conin)?;
Ok(())
}
#[inline]
fn reader(&mut self) -> &mut Self::Reader {
&mut self.conout
}
#[inline]
fn read_token(&self) -> mio::Token {
self.read_token
}
#[inline]
fn writer(&mut self) -> &mut Self::Writer {
&mut self.conin
}
#[inline]
fn write_token(&self) -> mio::Token {
self.write_token
}
}

View File

@ -12,73 +12,78 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{Pty, HANDLE};
use std::io;
use std::fs::OpenOptions;
use std::os::raw::c_void;
use std::os::windows::io::{FromRawHandle, IntoRawHandle};
use std::os::windows::fs::OpenOptionsExt;
use std::env;
use std::cell::UnsafeCell;
use std::sync::Arc;
use std::u16;
use dunce::canonicalize;
use mio;
use mio::Evented;
use mio_named_pipes::NamedPipe;
use winapi::um::synchapi::WaitForSingleObject;
use winapi::um::winbase::{WAIT_OBJECT_0, FILE_FLAG_OVERLAPPED};
use winapi::shared::winerror::WAIT_TIMEOUT;
use winapi::um::winbase::FILE_FLAG_OVERLAPPED;
use winpty::{ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty};
use winpty::Config as WinptyConfig;
use crate::config::{Config, Shell};
use crate::display::OnResize;
use crate::cli::Options;
use crate::tty::EventedReadWrite;
use crate::term::SizeInfo;
/// Handle to the winpty agent process. Required so we know when it closes.
static mut HANDLE: *mut c_void = 0usize as *mut c_void;
// We store a raw pointer because we need mutable access to call
// on_resize from a separate thread. Winpty internally uses a mutex
// so this is safe, despite outwards appearance.
pub struct Agent<'a> {
winpty: *mut Winpty<'a>
}
/// Handle can be cloned freely and moved between threads.
pub type WinptyHandle<'a> = Arc<Agent<'a>>;
// Because Winpty has a mutex, we can do this.
unsafe impl<'a> Send for Agent<'a> {}
unsafe impl<'a> Sync for Agent<'a> {}
impl<'a> Agent<'a> {
pub fn new(winpty: Winpty<'a>) -> Self {
Self {
winpty: Box::into_raw(Box::new(winpty))
}
}
/// Get immutable access to Winpty.
pub fn winpty(&self) -> &Winpty<'a> {
unsafe {&*self.winpty}
}
/// Get mutable access to Winpty.
/// Can offer internal mutability like this because Winpty uses
/// a mutex internally.
pub fn winpty_mut(&self) -> &mut Winpty<'a> {
unsafe {&mut *self.winpty}
}
}
impl<'a> Drop for Agent<'a> {
fn drop(&mut self) {
unsafe { Box::from_raw(self.winpty); }
}
}
/// How long the winpty agent should wait for any RPC request
/// This is a placeholder value until we see how often long responses happen
const AGENT_TIMEOUT: u32 = 10000;
pub fn process_should_exit() -> bool {
unsafe {
match WaitForSingleObject(HANDLE, 0) {
// Process has exited
WAIT_OBJECT_0 => {
info!("wait_object_0");
true
}
// Reached timeout of 0, process has not exited
WAIT_TIMEOUT => false,
// Error checking process, winpty gave us a bad agent handle?
_ => {
info!("Bad exit: {}", ::std::io::Error::last_os_error());
true
}
}
}
}
pub struct Pty<'a, R: io::Read + Evented + Send, W: io::Write + Evented + Send> {
// TODO: Provide methods for accessing this safely
pub winpty: UnsafeCell<Winpty<'a>>,
conout: R,
conin: W,
read_token: mio::Token,
write_token: mio::Token,
}
pub fn new<'a>(
config: &Config,
options: &Options,
size: &SizeInfo,
_window_id: Option<usize>,
) -> Pty<'a, NamedPipe, NamedPipe> {
) -> Pty<'a> {
// Create config
let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap();
@ -155,123 +160,14 @@ pub fn new<'a>(
HANDLE = winpty.raw_handle();
}
let agent = Agent::new(winpty);
Pty {
winpty: UnsafeCell::new(winpty),
conout: conout_pipe,
conin: conin_pipe,
// Placeholder tokens that are overwritten
handle: super::PtyHandle::Winpty(WinptyHandle::new(agent)),
conout: super::EventedReadablePipe::Named(conout_pipe),
conin: super::EventedWritablePipe::Named(conin_pipe),
read_token: 0.into(),
write_token: 0.into(),
}
}
impl<'a> EventedReadWrite for Pty<'a, NamedPipe, NamedPipe> {
type Reader = NamedPipe;
type Writer = NamedPipe;
#[inline]
fn register(
&mut self,
poll: &mio::Poll,
token: &mut Iterator<Item = &usize>,
interest: mio::Ready,
poll_opts: mio::PollOpt,
) -> io::Result<()> {
self.read_token = (*token.next().unwrap()).into();
self.write_token = (*token.next().unwrap()).into();
if interest.is_readable() {
poll.register(
&self.conout,
self.read_token,
mio::Ready::readable(),
poll_opts,
)?
} else {
poll.register(
&self.conout,
self.read_token,
mio::Ready::empty(),
poll_opts,
)?
}
if interest.is_writable() {
poll.register(
&self.conin,
self.write_token,
mio::Ready::writable(),
poll_opts,
)?
} else {
poll.register(
&self.conin,
self.write_token,
mio::Ready::empty(),
poll_opts,
)?
}
Ok(())
}
#[inline]
fn reregister(&mut self, poll: &mio::Poll, interest: mio::Ready, poll_opts: mio::PollOpt) -> io::Result<()> {
if interest.is_readable() {
poll.reregister(
&self.conout,
self.read_token,
mio::Ready::readable(),
poll_opts,
)?;
} else {
poll.reregister(
&self.conout,
self.read_token,
mio::Ready::empty(),
poll_opts,
)?;
}
if interest.is_writable() {
poll.reregister(
&self.conin,
self.write_token,
mio::Ready::writable(),
poll_opts,
)?;
} else {
poll.reregister(
&self.conin,
self.write_token,
mio::Ready::empty(),
poll_opts,
)?;
}
Ok(())
}
#[inline]
fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> {
poll.deregister(&self.conout)?;
poll.deregister(&self.conin)?;
Ok(())
}
#[inline]
fn reader(&mut self) -> &mut NamedPipe {
&mut self.conout
}
#[inline]
fn read_token(&self) -> mio::Token {
self.read_token
}
#[inline]
fn writer(&mut self) -> &mut NamedPipe {
&mut self.conin
}
#[inline]
fn write_token(&self) -> mio::Token {
self.write_token
write_token: 0.into()
}
}