aboutsummaryrefslogtreecommitdiff
path: root/src/tty
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 /src/tty
parent9c6d12ea2c863ba76015bdedc00db13b7307725a (diff)
downloadr-alacritty-dbd8538762ef8968a493e1bf996e8693479ca783.tar.gz
r-alacritty-dbd8538762ef8968a493e1bf996e8693479ca783.tar.bz2
r-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 'src/tty')
-rw-r--r--src/tty/mod.rs96
-rw-r--r--src/tty/unix.rs405
-rw-r--r--src/tty/windows/conpty.rs289
-rw-r--r--src/tty/windows/mod.rs303
-rw-r--r--src/tty/windows/winpty.rs169
5 files changed, 0 insertions, 1262 deletions
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
deleted file mode 100644
index ec175ee6..00000000
--- a/src/tty/mod.rs
+++ /dev/null
@@ -1,96 +0,0 @@
-// 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.
-//
-//! tty related functionality
-use mio;
-use std::{env, io};
-
-use terminfo::Database;
-
-use crate::config::Config;
-
-#[cfg(not(windows))]
-mod unix;
-#[cfg(not(windows))]
-pub use self::unix::*;
-
-#[cfg(windows)]
-mod windows;
-#[cfg(windows)]
-pub use self::windows::*;
-
-/// This trait defines the behaviour needed to read and/or write to a stream.
-/// It defines an abstraction over mio's interface in order to allow either one
-/// read/write object or a seperate read and write object.
-pub trait EventedReadWrite {
- type Reader: io::Read;
- type Writer: io::Write;
-
- fn register(
- &mut self,
- _: &mio::Poll,
- _: &mut dyn Iterator<Item = mio::Token>,
- _: mio::Ready,
- _: mio::PollOpt,
- ) -> io::Result<()>;
- fn reregister(&mut self, _: &mio::Poll, _: mio::Ready, _: mio::PollOpt) -> io::Result<()>;
- fn deregister(&mut self, _: &mio::Poll) -> io::Result<()>;
-
- fn reader(&mut self) -> &mut Self::Reader;
- fn read_token(&self) -> mio::Token;
- fn writer(&mut self) -> &mut Self::Writer;
- fn write_token(&self) -> mio::Token;
-}
-
-/// Events concerning TTY child processes
-#[derive(PartialEq)]
-pub enum ChildEvent {
- /// Indicates the child has exited
- Exited,
-}
-
-/// A pseudoterminal (or PTY)
-///
-/// This is a refinement of EventedReadWrite that also provides a channel through which we can be
-/// notified if the PTY child process does something we care about (other than writing to the TTY).
-/// In particular, this allows for race-free child exit notification on UNIX (cf. `SIGCHLD`).
-pub trait EventedPty: EventedReadWrite {
- #[cfg(unix)]
- fn child_event_token(&self) -> mio::Token;
-
- /// Tries to retrieve an event
- ///
- /// Returns `Some(event)` on success, or `None` if there are no events to retrieve.
- #[cfg(unix)]
- fn next_child_event(&mut self) -> Option<ChildEvent>;
-}
-
-// Setup environment variables
-pub fn setup_env(config: &Config) {
- // Default to 'alacritty' terminfo if it is available, otherwise
- // default to 'xterm-256color'. May be overridden by user's config
- // below.
- env::set_var(
- "TERM",
- if Database::from_name("alacritty").is_ok() { "alacritty" } else { "xterm-256color" },
- );
-
- // Advertise 24-bit color support
- env::set_var("COLORTERM", "truecolor");
-
- // Set env vars from config
- for (key, value) in config.env().iter() {
- env::set_var(key, value);
- }
-}
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
deleted file mode 100644
index 0e3dc2fd..00000000
--- a/src/tty/unix.rs
+++ /dev/null
@@ -1,405 +0,0 @@
-// 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.
-//
-//! tty related functionality
-//!
-
-use crate::cli::Options;
-use crate::config::{Config, Shell};
-use crate::display::OnResize;
-use crate::term::SizeInfo;
-use crate::tty::{ChildEvent, EventedPty, EventedReadWrite};
-use mio;
-
-use libc::{self, c_int, pid_t, winsize, TIOCSCTTY};
-use nix::pty::openpty;
-use signal_hook::{self as sighook, iterator::Signals};
-
-use mio::unix::EventedFd;
-use std::ffi::CStr;
-use std::fs::File;
-use std::io;
-use std::os::unix::{
- io::{AsRawFd, FromRawFd, RawFd},
- process::CommandExt,
-};
-use std::process::{Child, Command, Stdio};
-use std::ptr;
-use std::sync::atomic::{AtomicUsize, Ordering};
-
-/// Process ID of child process
-///
-/// Necessary to put this in static storage for `sigchld` to have access
-static PID: AtomicUsize = AtomicUsize::new(0);
-
-pub fn child_pid() -> pid_t {
- PID.load(Ordering::Relaxed) as pid_t
-}
-
-/// Get the current value of errno
-fn errno() -> c_int {
- ::errno::errno().0
-}
-
-/// Get raw fds for master/slave ends of a new pty
-fn make_pty(size: winsize) -> (RawFd, RawFd) {
- let mut win_size = size;
- win_size.ws_xpixel = 0;
- win_size.ws_ypixel = 0;
-
- let ends = openpty(Some(&win_size), None).expect("openpty failed");
-
- (ends.master, ends.slave)
-}
-
-/// Really only needed on BSD, but should be fine elsewhere
-fn set_controlling_terminal(fd: c_int) {
- let res = unsafe {
- // TIOSCTTY changes based on platform and the `ioctl` call is different
- // based on architecture (32/64). So a generic cast is used to make sure
- // there are no issues. To allow such a generic cast the clippy warning
- // is disabled.
- #[allow(clippy::cast_lossless)]
- libc::ioctl(fd, TIOCSCTTY as _, 0)
- };
-
- if res < 0 {
- die!("ioctl TIOCSCTTY failed: {}", errno());
- }
-}
-
-#[derive(Debug)]
-struct Passwd<'a> {
- name: &'a str,
- passwd: &'a str,
- uid: libc::uid_t,
- gid: libc::gid_t,
- gecos: &'a str,
- dir: &'a str,
- shell: &'a str,
-}
-
-/// Return a Passwd struct with pointers into the provided buf
-///
-/// # Unsafety
-///
-/// If `buf` is changed while `Passwd` is alive, bad thing will almost certainly happen.
-fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd<'_> {
- // Create zeroed passwd struct
- let mut entry: libc::passwd = unsafe { ::std::mem::uninitialized() };
-
- let mut res: *mut libc::passwd = ptr::null_mut();
-
- // Try and read the pw file.
- let uid = unsafe { libc::getuid() };
- let status = unsafe {
- libc::getpwuid_r(uid, &mut entry, buf.as_mut_ptr() as *mut _, buf.len(), &mut res)
- };
-
- if status < 0 {
- die!("getpwuid_r failed");
- }
-
- if res.is_null() {
- die!("pw not found");
- }
-
- // sanity check
- assert_eq!(entry.pw_uid, uid);
-
- // Build a borrowed Passwd struct
- Passwd {
- name: unsafe { CStr::from_ptr(entry.pw_name).to_str().unwrap() },
- passwd: unsafe { CStr::from_ptr(entry.pw_passwd).to_str().unwrap() },
- uid: entry.pw_uid,
- gid: entry.pw_gid,
- gecos: unsafe { CStr::from_ptr(entry.pw_gecos).to_str().unwrap() },
- dir: unsafe { CStr::from_ptr(entry.pw_dir).to_str().unwrap() },
- shell: unsafe { CStr::from_ptr(entry.pw_shell).to_str().unwrap() },
- }
-}
-
-pub struct Pty {
- child: Child,
- pub fd: File,
- token: mio::Token,
- signals: Signals,
- signals_token: mio::Token,
-}
-
-impl Pty {
- /// Resize the pty
- ///
- /// Tells the kernel that the window size changed with the new pixel
- /// dimensions and line/column counts.
- pub fn resize<T: ToWinsize>(&self, size: &T) {
- let win = size.to_winsize();
-
- let res = unsafe { libc::ioctl(self.fd.as_raw_fd(), libc::TIOCSWINSZ, &win as *const _) };
-
- if res < 0 {
- die!("ioctl TIOCSWINSZ failed: {}", errno());
- }
- }
-}
-
-/// Create a new tty and return a handle to interact with it.
-pub fn new<T: ToWinsize>(
- config: &Config,
- options: &Options,
- size: &T,
- window_id: Option<usize>,
-) -> Pty {
- let win_size = size.to_winsize();
- let mut buf = [0; 1024];
- let pw = get_pw_entry(&mut buf);
-
- let (master, slave) = make_pty(win_size);
-
- let default_shell = if cfg!(target_os = "macos") {
- let shell_name = pw.shell.rsplit('/').next().unwrap();
- let argv = vec![String::from("-c"), format!("exec -a -{} {}", shell_name, pw.shell)];
-
- Shell::new_with_args("/bin/bash", argv)
- } else {
- Shell::new(pw.shell)
- };
- let shell = config.shell().unwrap_or(&default_shell);
-
- let initial_command = options.command().unwrap_or(shell);
-
- let mut builder = Command::new(initial_command.program());
- for arg in initial_command.args() {
- builder.arg(arg);
- }
-
- // Setup child stdin/stdout/stderr as slave fd of pty
- // Ownership of fd is transferred to the Stdio structs and will be closed by them at the end of
- // this scope. (It is not an issue that the fd is closed three times since File::drop ignores
- // error on libc::close.)
- builder.stdin(unsafe { Stdio::from_raw_fd(slave) });
- builder.stderr(unsafe { Stdio::from_raw_fd(slave) });
- builder.stdout(unsafe { Stdio::from_raw_fd(slave) });
-
- // Setup shell environment
- builder.env("LOGNAME", pw.name);
- builder.env("USER", pw.name);
- builder.env("SHELL", pw.shell);
- builder.env("HOME", pw.dir);
-
- if let Some(window_id) = window_id {
- builder.env("WINDOWID", format!("{}", window_id));
- }
-
- builder.before_exec(move || {
- // Create a new process group
- unsafe {
- let err = libc::setsid();
- if err == -1 {
- die!("Failed to set session id: {}", errno());
- }
- }
-
- set_controlling_terminal(slave);
-
- // No longer need slave/master fds
- unsafe {
- libc::close(slave);
- libc::close(master);
- }
-
- unsafe {
- libc::signal(libc::SIGCHLD, libc::SIG_DFL);
- libc::signal(libc::SIGHUP, libc::SIG_DFL);
- libc::signal(libc::SIGINT, libc::SIG_DFL);
- libc::signal(libc::SIGQUIT, libc::SIG_DFL);
- libc::signal(libc::SIGTERM, libc::SIG_DFL);
- libc::signal(libc::SIGALRM, libc::SIG_DFL);
- }
- Ok(())
- });
-
- // Handle set working directory option
- if let Some(ref dir) = options.working_dir {
- builder.current_dir(dir.as_path());
- }
-
- // Prepare signal handling before spawning child
- let signals = Signals::new(&[sighook::SIGCHLD]).expect("error preparing signal handling");
-
- match builder.spawn() {
- Ok(child) => {
- // Remember child PID so other modules can use it
- PID.store(child.id() as usize, Ordering::Relaxed);
-
- unsafe {
- // Maybe this should be done outside of this function so nonblocking
- // isn't forced upon consumers. Although maybe it should be?
- set_nonblocking(master);
- }
-
- let pty = Pty {
- child,
- fd: unsafe { File::from_raw_fd(master) },
- token: mio::Token::from(0),
- signals,
- signals_token: mio::Token::from(0),
- };
- pty.resize(size);
- pty
- },
- Err(err) => {
- die!("Failed to spawn command: {}", err);
- },
- }
-}
-
-impl EventedReadWrite for Pty {
- type Reader = File;
- type Writer = File;
-
- #[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.token = token.next().unwrap();
- poll.register(&EventedFd(&self.fd.as_raw_fd()), self.token, interest, poll_opts)?;
-
- self.signals_token = token.next().unwrap();
- poll.register(
- &self.signals,
- self.signals_token,
- mio::Ready::readable(),
- mio::PollOpt::level(),
- )
- }
-
- #[inline]
- fn reregister(
- &mut self,
- poll: &mio::Poll,
- interest: mio::Ready,
- poll_opts: mio::PollOpt,
- ) -> io::Result<()> {
- poll.reregister(&EventedFd(&self.fd.as_raw_fd()), self.token, interest, poll_opts)?;
-
- poll.reregister(
- &self.signals,
- self.signals_token,
- mio::Ready::readable(),
- mio::PollOpt::level(),
- )
- }
-
- #[inline]
- fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> {
- poll.deregister(&EventedFd(&self.fd.as_raw_fd()))?;
- poll.deregister(&self.signals)
- }
-
- #[inline]
- fn reader(&mut self) -> &mut File {
- &mut self.fd
- }
-
- #[inline]
- fn read_token(&self) -> mio::Token {
- self.token
- }
-
- #[inline]
- fn writer(&mut self) -> &mut File {
- &mut self.fd
- }
-
- #[inline]
- fn write_token(&self) -> mio::Token {
- self.token
- }
-}
-
-impl EventedPty for Pty {
- #[inline]
- fn next_child_event(&mut self) -> Option<ChildEvent> {
- self.signals.pending().next().and_then(|signal| {
- if signal != sighook::SIGCHLD {
- return None;
- }
-
- match self.child.try_wait() {
- Err(e) => {
- error!("Error checking child process termination: {}", e);
- None
- },
- Ok(None) => None,
- Ok(_) => Some(ChildEvent::Exited),
- }
- })
- }
-
- #[inline]
- fn child_event_token(&self) -> mio::Token {
- self.signals_token
- }
-}
-
-pub fn process_should_exit() -> bool {
- false
-}
-
-/// Types that can produce a `libc::winsize`
-pub trait ToWinsize {
- /// Get a `libc::winsize`
- fn to_winsize(&self) -> winsize;
-}
-
-impl<'a> ToWinsize for &'a SizeInfo {
- fn to_winsize(&self) -> winsize {
- winsize {
- ws_row: self.lines().0 as libc::c_ushort,
- ws_col: self.cols().0 as libc::c_ushort,
- ws_xpixel: self.width as libc::c_ushort,
- ws_ypixel: self.height as libc::c_ushort,
- }
- }
-}
-
-impl OnResize for i32 {
- fn on_resize(&mut self, size: &SizeInfo) {
- let win = size.to_winsize();
-
- let res = unsafe { libc::ioctl(*self, libc::TIOCSWINSZ, &win as *const _) };
-
- if res < 0 {
- die!("ioctl TIOCSWINSZ failed: {}", errno());
- }
- }
-}
-
-unsafe fn set_nonblocking(fd: c_int) {
- use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
-
- let res = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
- assert_eq!(res, 0);
-}
-
-#[test]
-fn test_get_pw_entry() {
- let mut buf: [i8; 1024] = [0; 1024];
- let _pw = get_pw_entry(&mut buf);
-}
diff --git a/src/tty/windows/conpty.rs b/src/tty/windows/conpty.rs
deleted file mode 100644
index f23d78a7..00000000
--- a/src/tty/windows/conpty.rs
+++ /dev/null
@@ -1,289 +0,0 @@
-// 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/src/tty/windows/mod.rs b/src/tty/windows/mod.rs
deleted file mode 100644
index c87c5257..00000000
--- a/src/tty/windows/mod.rs
+++ /dev/null
@@ -1,303 +0,0 @@
-// 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/src/tty/windows/winpty.rs b/src/tty/windows/winpty.rs
deleted file mode 100644
index 10bd9d01..00000000
--- a/src/tty/windows/winpty.rs
+++ /dev/null
@@ -1,169 +0,0 @@
-// 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?"));
- }
- }
-}