mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Add support for Windows ConPTY API
This commit is contained in:
parent
ec6f756946
commit
f1bc6802e1
10 changed files with 762 additions and 185 deletions
|
@ -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
53
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
13
src/lib.rs
13
src/lib.rs
|
@ -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;
|
||||
|
|
|
@ -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
303
src/tty/windows/conpty.rs
Normal 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
334
src/tty/windows/mod.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in a new issue