summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Hewitt <1939362+davidhewitt@users.noreply.github.com>2018-12-28 16:01:58 +0000
committerChristian Duerr <chrisduerr@users.noreply.github.com>2018-12-28 16:01:58 +0000
commitf1bc6802e1d0d03feaa43e61c2bf465795a96da9 (patch)
treed2b993eb020631ca1de14b47eb0d9213ee37dd90
parentec6f756946c998d327316d370b381003e51d3a70 (diff)
Add support for Windows ConPTY APIv0.2.4-conpty
-rw-r--r--CHANGELOG.md3
-rw-r--r--Cargo.lock53
-rw-r--r--Cargo.toml5
-rw-r--r--alacritty_windows.yml11
-rw-r--r--src/config.rs13
-rw-r--r--src/lib.rs13
-rw-r--r--src/main.rs6
-rw-r--r--src/tty/windows/conpty.rs303
-rw-r--r--src/tty/windows/mod.rs334
-rw-r--r--src/tty/windows/winpty.rs (renamed from src/tty/windows.rs)200
10 files changed, 759 insertions, 182 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6d46d65..df645133 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/Cargo.lock b/Cargo.lock
index 8c97eab3..30377bd9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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)",
@@ -1269,6 +1272,17 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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)",
@@ -2156,6 +2170,11 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2646,6 +2665,11 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index dbea97ec..a3ec22a0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/alacritty_windows.yml b/alacritty_windows.yml
index 59de4099..ba764dfc 100644
--- a/alacritty_windows.yml
+++ b/alacritty_windows.yml
@@ -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
diff --git a/src/config.rs b/src/config.rs
index 76d9d7e3..547bce1f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -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;
diff --git a/src/lib.rs b/src/lib.rs
index 5e56361b..d6873f96 100644
--- a/src/lib.rs
+++ b/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;
diff --git a/src/main.rs b/src/main.rs
index 2ce43fe1..ca8da5af 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -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);
diff --git a/src/tty/windows/conpty.rs b/src/tty/windows/conpty.rs
new file mode 100644
index 00000000..b16a61c4
--- /dev/null
+++ b/src/tty/windows/conpty.rs
@@ -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
+ }
+}
diff --git a/src/tty/windows/mod.rs b/src/tty/windows/mod.rs
new file mode 100644
index 00000000..1eb442ad
--- /dev/null
+++ b/src/tty/windows/mod.rs
@@ -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::N