alacritty/src/tty/windows.rs

287 lines
8.2 KiB
Rust
Raw Normal View History

// 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 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 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) {
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?"));
}
}
}