diff options
author | Zac Pullar-Strecker <zacps@users.noreply.github.com> | 2018-10-17 06:02:52 +1300 |
---|---|---|
committer | Joe Wilm <jwilm@users.noreply.github.com> | 2018-10-16 10:02:52 -0700 |
commit | 15e0deae2b49078b47a782679300cdf99d9ce687 (patch) | |
tree | 8175fbed0def1af08cd2db41583975adbb27dff1 /src/tty/windows.rs | |
parent | b41c6b736d67d61e92b174dfea58ae46813934cd (diff) | |
download | alacritty-15e0deae2b49078b47a782679300cdf99d9ce687.tar.gz alacritty-15e0deae2b49078b47a782679300cdf99d9ce687.zip |
Add support for Windows (#1374)
Initial support for Windows is implemented using the winpty translation
layer. Clipboard support for Windows is provided through the `clipboard`
crate, and font rasterization is provided by RustType.
The tty.rs file has been split into OS-specific files to separate
standard pty handling from the winpty implementation.
Several binary components are fetched via build script on windows
including libclang and winpty. These could be integrated more directly
in the future either by building those dependencies as part of the
Alacritty build process or by leveraging git lfs to store the artifacts.
Fixes #28.
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?")); + } + } +} |