use std::io::Error; use std::os::windows::io::IntoRawHandle; use std::{i16, mem, ptr}; use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite}; use winapi::shared::basetsd::{PSIZE_T, SIZE_T}; use winapi::shared::minwindef::BYTE; use winapi::shared::ntdef::LPWSTR; use winapi::shared::winerror::S_OK; use winapi::um::consoleapi::{ClosePseudoConsole, CreatePseudoConsole, ResizePseudoConsole}; 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::config::PtyConfig; use crate::event::OnResize; use crate::grid::Dimensions; use crate::term::SizeInfo; use crate::tty::windows::child::ChildExitWatcher; use crate::tty::windows::{cmdline, win32_string, Pty}; /// RAII Pseudoconsole. pub struct Conpty { pub handle: HPCON, } impl Drop for Conpty { fn drop(&mut self) { // XXX: This will block until the conout pipe is drained. Will cause a deadlock if the // conout pipe has already been dropped by this point. // // See PR #3084 and https://docs.microsoft.com/en-us/windows/console/closepseudoconsole. unsafe { ClosePseudoConsole(self.handle) } } } // The ConPTY handle can be sent between threads. unsafe impl Send for Conpty {} pub fn new(config: &PtyConfig, size: &SizeInfo) -> Option { 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 { CreatePseudoConsole( coord, conin_pty_handle.into_raw_handle(), conout_pty_handle.into_raw_handle(), 0, &mut pty_handle as *mut HPCON, ) }; assert_eq!(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(); startup_info_ex.StartupInfo.lpTitle = std::ptr::null_mut() as LPWSTR; startup_info_ex.StartupInfo.cb = mem::size_of::() 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::(), ptr::null_mut(), ptr::null_mut(), ) > 0; if !success { panic_shell_spawn(); } } let cmdline = win32_string(&cmdline(config)); let cwd = config.working_directory.as_ref().map(win32_string); 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(); } } let conin = EventedAnonWrite::new(conin); let conout = EventedAnonRead::new(conout); let child_watcher = ChildExitWatcher::new(proc_info.hProcess).unwrap(); let conpty = Conpty { handle: pty_handle }; Some(Pty::new(conpty, conout, conin, child_watcher)) } // Panic with the last os error as message. fn panic_shell_spawn() { panic!("Unable to spawn shell: {}", Error::last_os_error()); } impl OnResize for Conpty { fn on_resize(&mut self, sizeinfo: &SizeInfo) { if let Some(coord) = coord_from_sizeinfo(sizeinfo) { let result = unsafe { ResizePseudoConsole(self.handle, coord) }; assert_eq!(result, S_OK); } } } /// Helper to build a COORD from a SizeInfo, returning None in overflow cases. fn coord_from_sizeinfo(size: &SizeInfo) -> Option { let lines = size.screen_lines(); let columns = size.columns(); if columns <= i16::MAX as usize && lines <= i16::MAX as usize { Some(COORD { X: columns as i16, Y: lines as i16 }) } else { None } }