aboutsummaryrefslogtreecommitdiff
path: root/alacritty_terminal/src/tty/windows
diff options
context:
space:
mode:
authorTheodore Dubois <tblodt@icloud.com>2019-04-28 06:24:58 -0700
committerChristian Duerr <chrisduerr@users.noreply.github.com>2019-04-28 13:24:58 +0000
commitdbd8538762ef8968a493e1bf996e8693479ca783 (patch)
tree32ac2a6a5e01238a272d4ba534551d2e42903c7a /alacritty_terminal/src/tty/windows
parent9c6d12ea2c863ba76015bdedc00db13b7307725a (diff)
downloadalacritty-dbd8538762ef8968a493e1bf996e8693479ca783.tar.gz
alacritty-dbd8538762ef8968a493e1bf996e8693479ca783.zip
Split alacritty into a separate crates
The crate containing the entry point is called alacritty, and the crate containing everything else is called alacritty_terminal.
Diffstat (limited to 'alacritty_terminal/src/tty/windows')
-rw-r--r--alacritty_terminal/src/tty/windows/conpty.rs289
-rw-r--r--alacritty_terminal/src/tty/windows/mod.rs303
-rw-r--r--alacritty_terminal/src/tty/windows/winpty.rs169
3 files changed, 761 insertions, 0 deletions
diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs
new file mode 100644
index 00000000..f23d78a7
--- /dev/null
+++ b/alacritty_terminal/src/tty/windows/conpty.rs
@@ -0,0 +1,289 @@
+// 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::{Pty, HANDLE};
+
+use std::i16;
+use std::io::Error;
+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::shared::basetsd::{PSIZE_T, SIZE_T};
+use winapi::shared::minwindef::{BYTE, DWORD};
+use winapi::shared::ntdef::{HANDLE, HRESULT, 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, STARTF_USESTDHANDLES, STARTUPINFOEXW};
+use winapi::um::wincontypes::{COORD, HPCON};
+
+use crate::cli::Options;
+use crate::config::{Config, Shell};
+use crate::display::OnResize;
+use crate::term::SizeInfo;
+
+/// 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),
+}
+
+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) {
+ unsafe { (self.api.ClosePseudoConsole)(self.handle) }
+ }
+}
+
+// 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();
+
+ let title = options.title.as_ref().map(String::as_str).unwrap_or("Alacritty");
+ let title = U16CString::from_str(title).unwrap();
+ startup_info_ex.StartupInfo.lpTitle = title.as_ptr() as LPWSTR;
+
+ startup_info_ex.StartupInfo.cb = mem::size_of::<STARTUPINFOEXW>() as u32;
+
+ // Setting this flag but leaving all the handles as default (null) ensures the
+ // pty process does not inherit any handles from this Alacritty process.
+ startup_info_ex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+ // Create the appropriately sized thread attribute list.
+ unsafe {
+ let failure =
+ InitializeProcThreadAttributeList(ptr::null_mut(), 1, 0, &mut size as PSIZE_T) > 0;
+
+ // This call was expected to return false.
+ if failure {
+ panic_shell_spawn();
+ }
+ }
+
+ 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;
+
+ if !success {
+ panic_shell_spawn();
+ }
+ }
+
+ // 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;
+
+ if !success {
+ panic_shell_spawn();
+ }
+ }
+
+ // Get process commandline
+ let default_shell = &Shell::new("powershell");
+ 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();
+ let cwd = cwd.map(|s| U16CString::from_str(&s).unwrap());
+
+ let mut proc_info: PROCESS_INFORMATION = Default::default();
+ unsafe {
+ success = CreateProcessW(
+ ptr::null(),
+ cmdline.as_ptr() as LPWSTR,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ false as i32,
+ EXTENDED_STARTUPINFO_PRESENT,
+ ptr::null_mut(),
+ cwd.as_ref().map_or_else(ptr::null, |s| s.as_ptr()),
+ &mut startup_info_ex.StartupInfo as *mut STARTUPINFOW,
+ &mut proc_info as *mut PROCESS_INFORMATION,
+ ) > 0;
+
+ if !success {
+ panic_shell_spawn();
+ }
+ }
+
+ // 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(),
+ })
+}
+
+// Panic with the last os error as message
+fn panic_shell_spawn() {
+ panic!("Unable to spawn shell: {}", Error::last_os_error());
+}
+
+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/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs
new file mode 100644
index 00000000..c87c5257
--- /dev/null
+++ b/alacritty_terminal/src/tty/windows/mod.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 std::io::{self, Read, Write};
+use std::os::raw::c_void;
+use std::sync::atomic::{AtomicBool, Ordering};
+
+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::{EventedPty, 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;
+static IS_CONPTY: AtomicBool = AtomicBool::new(false);
+
+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 fn is_conpty() -> bool {
+ IS_CONPTY.load(Ordering::Relaxed)
+}
+
+#[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");
+ IS_CONPTY.store(true, Ordering::Relaxed);
+ 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.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 = mio::Token>,
+ interest: mio::Ready,
+ poll_opts: mio::PollOpt,
+ ) -> io::Result<()> {
+ self.read_token = token.next().unwrap();
+ self.write_token = token.next().unwrap();
+
+ 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
+ }
+}
+
+impl<'a> EventedPty for Pty<'a> {}
diff --git a/alacritty_terminal/src/tty/windows/winpty.rs b/alacritty_terminal/src/tty/windows/winpty.rs
new file mode 100644
index 00000000..10bd9d01
--- /dev/null
+++ b/alacritty_terminal/src/tty/windows/winpty.rs
@@ -0,0 +1,169 @@
+// 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::{Pty, HANDLE};
+
+use std::fs::OpenOptions;
+use std::io;
+use std::os::windows::fs::OpenOptionsExt;
+use std::os::windows::io::{FromRawHandle, IntoRawHandle};
+use std::sync::Arc;
+use std::u16;
+
+use dunce::canonicalize;
+use mio_named_pipes::NamedPipe;
+use winapi::um::winbase::FILE_FLAG_OVERLAPPED;
+use winpty::Config as WinptyConfig;
+use winpty::{ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty};
+
+use crate::cli::Options;
+use crate::config::{Config, Shell};
+use crate::display::OnResize;
+use crate::term::SizeInfo;
+
+// 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 }
+ }
+
+ pub fn resize(&self, size: &SizeInfo) {
+ // This is safe since Winpty uses a mutex internally.
+ unsafe {
+ (&mut *self.winpty).on_resize(size);
+ }
+ }
+}
+
+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 new<'a>(
+ config: &Config,
+ options: &Options,
+ size: &SizeInfo,
+ _window_id: Option<usize>,
+) -> Pty<'a> {
+ // Create config
+ let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap();
+
+ wconfig.set_initial_size(size.cols().0 as i32, size.lines().0 as i32);
+ wconfig.set_mouse_mode(&MouseMode::Auto);
+ wconfig.set_agent_timeout(AGENT_TIMEOUT);
+
+ // Start agent
+ let mut winpty = Winpty::open(&wconfig).unwrap();
+ let (conin, conout) = (winpty.conin_name(), winpty.conout_name());
+
+ // Get process commandline
+ let default_shell = &Shell::new("powershell");
+ 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());
+
+ // Spawn process
+ let spawnconfig = SpawnConfig::new(
+ SpawnFlags::AUTO_SHUTDOWN | SpawnFlags::EXIT_AFTER_SHUTDOWN,
+ None, // appname
+ Some(&cmdline.join(" ")),
+ cwd,
+ None, // Env
+ )
+ .unwrap();
+
+ let default_opts = &mut OpenOptions::new();
+ default_opts.share_mode(0).custom_flags(FILE_FLAG_OVERLAPPED);
+
+ let (conout_pipe, conin_pipe);
+ unsafe {
+ conout_pipe = NamedPipe::from_raw_handle(
+ default_opts.clone().read(true).open(conout).unwrap().into_raw_handle(),
+ );
+ conin_pipe = NamedPipe::from_raw_handle(
+ default_opts.clone().write(true).open(conin).unwrap().into_raw_handle(),
+ );
+ };
+
+ if let Some(err) = conout_pipe.connect().err() {
+ if err.kind() != io::ErrorKind::WouldBlock {
+ panic!(err);
+ }
+ }
+ assert!(conout_pipe.take_error().unwrap().is_none());
+
+ if let Some(err) = conin_pipe.connect().err() {
+ if err.kind() != io::ErrorKind::WouldBlock {
+ panic!(err);
+ }
+ }
+ assert!(conin_pipe.take_error().unwrap().is_none());
+
+ winpty.spawn(&spawnconfig).unwrap();
+
+ unsafe {
+ HANDLE = winpty.raw_handle();
+ }
+
+ let agent = Agent::new(winpty);
+
+ Pty {
+ 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> OnResize for Winpty<'a> {
+ fn on_resize(&mut self, sizeinfo: &SizeInfo) {
+ let (cols, lines) = (sizeinfo.cols().0, sizeinfo.lines().0);
+ if cols > 0 && cols <= u16::MAX as usize && lines > 0 && lines <= u16::MAX as usize {
+ self.set_size(cols as u16, lines as u16)
+ .unwrap_or_else(|_| info!("Unable to set winpty size, did it die?"));
+ }
+ }
+}