diff options
author | Theodore Dubois <tblodt@icloud.com> | 2019-04-28 06:24:58 -0700 |
---|---|---|
committer | Christian Duerr <chrisduerr@users.noreply.github.com> | 2019-04-28 13:24:58 +0000 |
commit | dbd8538762ef8968a493e1bf996e8693479ca783 (patch) | |
tree | 32ac2a6a5e01238a272d4ba534551d2e42903c7a /src/tty | |
parent | 9c6d12ea2c863ba76015bdedc00db13b7307725a (diff) | |
download | r-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.rs | 96 | ||||
-rw-r--r-- | src/tty/unix.rs | 405 | ||||
-rw-r--r-- | src/tty/windows/conpty.rs | 289 | ||||
-rw-r--r-- | src/tty/windows/mod.rs | 303 | ||||
-rw-r--r-- | src/tty/windows/winpty.rs | 169 |
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?")); - } - } -} |