diff options
Diffstat (limited to 'src/tty/windows.rs')
-rw-r--r-- | src/tty/windows.rs | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/src/tty/windows.rs b/src/tty/windows.rs new file mode 100644 index 00000000..9a452955 --- /dev/null +++ b/src/tty/windows.rs @@ -0,0 +1,284 @@ +// 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; +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 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 winpty::{ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty}; +use winpty::Config as WinptyConfig; + +use config::{Config, Shell}; +use display::OnResize; +use cli::Options; +use tty::EventedReadWrite; +use 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; + +/// 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> { + // 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(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()); + + // 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(); + } + + Pty { + winpty: UnsafeCell::new(winpty), + conout: conout_pipe, + conin: conin_pipe, + // Placeholder tokens that are overwritten + 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 + } +} + +impl<'a> OnResize for Winpty<'a> { + fn on_resize(&mut self, sizeinfo: &SizeInfo) { + if sizeinfo.cols().0 > 0 && sizeinfo.lines().0 > 0 { + self.set_size(sizeinfo.cols().0, sizeinfo.lines().0) + .unwrap_or_else(|_| info!("Unable to set winpty size, did it die?")); + } + } +} |