diff --git a/winpty/Cargo.toml b/winpty/Cargo.toml index b19a8e6b..7ce53cd4 100644 --- a/winpty/Cargo.toml +++ b/winpty/Cargo.toml @@ -5,12 +5,12 @@ authors = ["Zac Pullar-Strecker "] license = "MIT" description = "Safe rust bindings for winpty" -[dependencies] +[target.'cfg(windows)'.dependencies] # TODO: Replace with official repo winpty-sys = { git = "https://github.com/zacps/winpty", branch = "rust" } bitflags = "1.0" widestring = "0.2.2" -[dev-dependencies] +[target.'cfg(windows)'.dev-dependencies] named_pipe = "0.3" winapi = { version = "0.3", features = ["winnt", "processthreadsapi"] } diff --git a/winpty/src/lib.rs b/winpty/src/lib.rs index 42a83569..308843a9 100644 --- a/winpty/src/lib.rs +++ b/winpty/src/lib.rs @@ -1,475 +1,15 @@ #![cfg_attr(feature = "cargo-clippy", deny(clippy, if_not_else, enum_glob_use, wrong_pub_self_convention))] #[macro_use] +#[cfg(windows)] extern crate bitflags; +#[cfg(windows)] extern crate widestring; +#[cfg(windows)] extern crate winpty_sys; -use std::error::Error; -use std::fmt; -use std::path::PathBuf; -use std::result::Result; -use std::os::windows::io::RawHandle; -use std::ptr::{null, null_mut}; -use fmt::{Display, Formatter}; +#[cfg(windows)] +pub mod windows; -use winpty_sys::*; - -use widestring::WideCString; - -pub enum ErrorCodes { - Success, - OutOfMemory, - SpawnCreateProcessFailed, - LostConnection, - AgentExeMissing, - Unspecified, - AgentDied, - AgentTimeout, - AgentCreationFailed, -} -pub enum MouseMode { - None, - Auto, - Force, -} -bitflags!( - pub struct SpawnFlags: u64 { - const AUTO_SHUTDOWN = 0x1; - const EXIT_AFTER_SHUTDOWN = 0x2; - } -); -bitflags!( - pub struct ConfigFlags: u64 { - const CONERR = 0x1; - const PLAIN_OUTPUT = 0x2; - const COLOR_ESCAPES = 0x4; - } -); - -#[derive(Debug)] -pub struct Err<'a> { - ptr: &'a mut winpty_error_t, - code: u32, - message: String, -} - -// Check to see whether winpty gave us an error -fn check_err<'a>(e: *mut winpty_error_t) -> Option> { - let err = unsafe { - let raw = winpty_error_msg(e); - Err { - ptr: &mut *e, - code: winpty_error_code(e), - message: String::from_utf16_lossy(std::slice::from_raw_parts(raw, wcslen(raw))), - } - }; - if err.code == 0 { - None - } else { - Some(err) - } -} - -impl<'a> Drop for Err<'a> { - fn drop(&mut self) { - unsafe { - winpty_error_free(self.ptr); - } - } -} -impl<'a> Display for Err<'a> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - write!(f, "Code: {}, Message: {}", self.code, self.message) - } -} -impl<'a> Error for Err<'a> { - fn description(&self) -> &str { - &self.message - } -} - -#[derive(Debug)] -/// Winpty agent config -pub struct Config<'a>(&'a mut winpty_config_t); - -impl<'a, 'b> Config<'a> { - pub fn new(flags: ConfigFlags) -> Result> { - let mut err = null_mut() as *mut winpty_error_t; - let config = unsafe { winpty_config_new(flags.bits(), &mut err) }; - - if let Some(err) = check_err(err) { - Result::Err(err) - } else { - unsafe { Ok(Config(&mut *config)) } - } - } - - /// Set the initial size of the console window - pub fn set_initial_size(&mut self, cols: i32, rows: i32) { - unsafe { - winpty_config_set_initial_size(self.0, cols, rows); - } - } - - /// Set the mouse mode - pub fn set_mouse_mode(&mut self, mode: &MouseMode) { - let m = match mode { - MouseMode::None => 0, - MouseMode::Auto => 1, - MouseMode::Force => 2, - }; - unsafe { - winpty_config_set_mouse_mode(self.0, m); - } - } - - /// Amount of time to wait for the agent to startup and to wait for any given - /// agent RPC request. Must be greater than 0. Can be INFINITE. - // Might be a better way to represent this while still retaining infinite capability? - // Enum? - pub fn set_agent_timeout(&mut self, timeout: u32) { - unsafe { - winpty_config_set_agent_timeout(self.0, timeout); - } - } -} - -impl<'a> Drop for Config<'a> { - fn drop(&mut self) { - unsafe { - winpty_config_free(self.0); - } - } -} - -#[derive(Debug)] -/// A struct representing the winpty agent process -pub struct Winpty<'a>(&'a mut winpty_t); - -impl<'a, 'b> Winpty<'a> { - /// Starts the agent. This process will connect to the agent - /// over a control pipe, and the agent will open data pipes - /// (e.g. CONIN and CONOUT). - pub fn open(cfg: &Config) -> Result> { - let mut err = null_mut() as *mut winpty_error_t; - unsafe { - let winpty = winpty_open(cfg.0, &mut err); - let err = check_err(err); - if let Some(err) = err { - Result::Err(err) - } else { - Ok(Winpty(&mut *winpty)) - } - } - } - - /// Returns the handle to the winpty agent process - pub fn raw_handle(&mut self) -> RawHandle { - unsafe { winpty_agent_process(self.0) } - } - - /// Returns the name of the input pipe. - /// Pipe is half-duplex. - pub fn conin_name(&mut self) -> PathBuf { - unsafe { - let raw = winpty_conin_name(self.0); - PathBuf::from(&String::from_utf16_lossy(std::slice::from_raw_parts( - raw, - wcslen(raw), - ))) - } - } - - /// Returns the name of the output pipe. - /// Pipe is half-duplex. - pub fn conout_name(&mut self) -> PathBuf { - unsafe { - let raw = winpty_conout_name(self.0); - PathBuf::from(&String::from_utf16_lossy(std::slice::from_raw_parts( - raw, - wcslen(raw), - ))) - } - } - - /// Returns the name of the error pipe. - /// The name will only be valid if ConfigFlags::CONERR was specified. - /// Pipe is half-duplex. - pub fn conerr_name(&mut self) -> PathBuf { - unsafe { - let raw = winpty_conerr_name(self.0); - PathBuf::from(&String::from_utf16_lossy(std::slice::from_raw_parts( - raw, - wcslen(raw), - ))) - } - } - - /// Change the size of the Windows console window. - /// - /// cols & rows MUST be greater than 0 - pub fn set_size(&mut self, cols: usize, rows: usize) -> Result<(), Err> { - assert!(cols > 0 && rows > 0); - let mut err = null_mut() as *mut winpty_error_t; - - unsafe { - winpty_set_size(self.0, cols as i32, rows as i32, &mut err); - } - - if let Some(err) = check_err(err) { - Result::Err(err) - } else { - Ok(()) - } - } - - /// Get the list of processses running in the winpty agent. Returns <= count processes - /// - /// `count` must be greater than 0. Larger values cause a larger allocation. - // TODO: This should return Vec instead of Vec - pub fn console_process_list(&mut self, count: usize) -> Result, Err> { - assert!(count > 0); - - let mut err = null_mut() as *mut winpty_error_t; - let mut process_list = Vec::with_capacity(count); - - unsafe { - let len = winpty_get_console_process_list(self.0, process_list.as_mut_ptr(), count as i32, &mut err) as usize; - process_list.set_len(len); - } - - if let Some(err) = check_err(err) { - Result::Err(err) - } else { - Ok(process_list) - } - } - - /// Spawns the new process. - /// - /// spawn can only be called once per Winpty object. If it is called - /// before the output data pipe(s) is/are connected, then collected output is - /// buffered until the pipes are connected, rather than being discarded. - /// (https://blogs.msdn.microsoft.com/oldnewthing/20110107-00/?p=11803) - // TODO: Support getting the process and thread handle of the spawned process (Not the agent) - // TODO: Support returning the error from CreateProcess - pub fn spawn( - &mut self, - cfg: &SpawnConfig, - ) -> Result<(), Err> { - let mut err = null_mut() as *mut winpty_error_t; - - unsafe { - let ok = winpty_spawn( - self.0, - cfg.0 as *const winpty_spawn_config_s, - null_mut(), // Process handle - null_mut(), // Thread handle - null_mut(), // Create process error - &mut err, - ); - if ok == 0 { return Ok(());} - } - - if let Some(err) = check_err(err) { - Result::Err(err) - } else { - Ok(()) - } - } -} - -// winpty_t is thread-safe -unsafe impl<'a> Sync for Winpty<'a> {} -unsafe impl<'a> Send for Winpty<'a> {} - -impl<'a> Drop for Winpty<'a> { - fn drop(&mut self) { - unsafe { - winpty_free(self.0); - } - } -} - -#[derive(Debug)] -/// Information about a process for winpty to spawn -pub struct SpawnConfig<'a>(&'a mut winpty_spawn_config_t); - -impl<'a, 'b> SpawnConfig<'a> { - /// Creates a new spawnconfig - pub fn new( - spawnflags: SpawnFlags, - appname: Option<&str>, - cmdline: Option<&str>, - cwd: Option<&str>, - end: Option<&str>, - ) -> Result> { - let mut err = null_mut() as *mut winpty_error_t; - let (appname, cmdline, cwd, end) = ( - appname.map_or(null(), |s| WideCString::from_str(s).unwrap().into_raw()), - cmdline.map_or(null(), |s| WideCString::from_str(s).unwrap().into_raw()), - cwd.map_or(null(), |s| WideCString::from_str(s).unwrap().into_raw()), - end.map_or(null(), |s| WideCString::from_str(s).unwrap().into_raw()), - ); - - let spawn_config = unsafe { - winpty_spawn_config_new(spawnflags.bits(), appname, cmdline, cwd, end, &mut err) - }; - - // Required to free the strings - unsafe { - if !appname.is_null() { - WideCString::from_raw(appname as *mut u16); - } - if !cmdline.is_null() { - WideCString::from_raw(cmdline as *mut u16); - } - if !cwd.is_null() { - WideCString::from_raw(cwd as *mut u16); - } - if !end.is_null() { - WideCString::from_raw(end as *mut u16); - } - } - - if let Some(err) = check_err(err) { - Result::Err(err) - } else { - unsafe { Ok(SpawnConfig(&mut *spawn_config)) } - } - } -} -impl<'a> Drop for SpawnConfig<'a> { - fn drop(&mut self) { - unsafe { - winpty_spawn_config_free(self.0); - } - } -} - -#[cfg(test)] -mod tests { - extern crate named_pipe; - extern crate winapi; - - use self::named_pipe::PipeClient; - use self::winapi::um::processthreadsapi::OpenProcess; - use self::winapi::um::winnt::READ_CONTROL; - - use {Config, ConfigFlags, SpawnConfig, SpawnFlags, Winpty}; - - #[test] - // Test that we can start a process in winpty - fn spawn_process() { - let mut winpty = Winpty::open( - &Config::new(ConfigFlags::empty()).expect("failed to create config") - ).expect("failed to create winpty instance"); - - winpty.spawn( - &SpawnConfig::new( - SpawnFlags::empty(), - None, - Some("cmd"), - None, - None - ).expect("failed to create spawn config") - ).unwrap(); - } - - #[test] - // Test that pipes connected before winpty is spawned can be connected to - fn valid_pipe_connect_before() { - let mut winpty = Winpty::open( - &Config::new(ConfigFlags::empty()).expect("failed to create config") - ).expect("failed to create winpty instance"); - - // Check we can connect to both pipes - PipeClient::connect_ms(winpty.conout_name(), 1000).expect("failed to connect to conout pipe"); - PipeClient::connect_ms(winpty.conin_name(), 1000).expect("failed to connect to conin pipe"); - - winpty.spawn( - &SpawnConfig::new( - SpawnFlags::empty(), - None, - Some("cmd"), - None, - None - ).expect("failed to create spawn config") - ).unwrap(); - } - - #[test] - // Test that pipes connected after winpty is spawned can be connected to - fn valid_pipe_connect_after() { - let mut winpty = Winpty::open( - &Config::new(ConfigFlags::empty()).expect("failed to create config") - ).expect("failed to create winpty instance"); - - winpty.spawn( - &SpawnConfig::new( - SpawnFlags::empty(), - None, - Some("cmd"), - None, - None - ).expect("failed to create spawn config") - ).unwrap(); - - // Check we can connect to both pipes - PipeClient::connect_ms(winpty.conout_name(), 1000).expect("failed to connect to conout pipe"); - PipeClient::connect_ms(winpty.conin_name(), 1000).expect("failed to connect to conin pipe"); - } - - #[test] - fn resize() { - let mut winpty = Winpty::open( - &Config::new(ConfigFlags::empty()).expect("failed to create config") - ).expect("failed to create winpty instance"); - - winpty.spawn( - &SpawnConfig::new( - SpawnFlags::empty(), - None, - Some("cmd"), - None, - None - ).expect("failed to create spawn config") - ).unwrap(); - - winpty.set_size(1, 1).unwrap(); - } - - #[test] - #[ignore] - // Test that each id returned by cosole_process_list points to an actual process - fn console_process_list_valid() { - let mut winpty = Winpty::open( - &Config::new(ConfigFlags::empty()).expect("failed to create config") - ).expect("failed to create winpty instance"); - - winpty.spawn( - &SpawnConfig::new( - SpawnFlags::empty(), - None, - Some("cmd"), - None, - None - ).expect("failed to create spawn config") - ).unwrap(); - - let processes = winpty.console_process_list(1000).expect("failed to get console process list"); - - // Check that each id is valid - processes.iter().for_each(|id| { - let handle = unsafe { - OpenProcess( - READ_CONTROL, // permissions - false as i32, // inheret - *id as u32 - ) - }; - assert!(!handle.is_null()); - }); - } -} +#[cfg(windows)] +pub use windows::*; diff --git a/winpty/src/windows.rs b/winpty/src/windows.rs new file mode 100644 index 00000000..67370709 --- /dev/null +++ b/winpty/src/windows.rs @@ -0,0 +1,467 @@ +use std::error::Error; +use std::fmt::{self, Display, Formatter}; +use std::path::PathBuf; +use std::result::Result; +use std::os::windows::io::RawHandle; +use std::ptr::{null, null_mut}; + +use winpty_sys::*; + +use widestring::WideCString; + +pub enum ErrorCodes { + Success, + OutOfMemory, + SpawnCreateProcessFailed, + LostConnection, + AgentExeMissing, + Unspecified, + AgentDied, + AgentTimeout, + AgentCreationFailed, +} +pub enum MouseMode { + None, + Auto, + Force, +} +bitflags!( + pub struct SpawnFlags: u64 { + const AUTO_SHUTDOWN = 0x1; + const EXIT_AFTER_SHUTDOWN = 0x2; + } +); +bitflags!( + pub struct ConfigFlags: u64 { + const CONERR = 0x1; + const PLAIN_OUTPUT = 0x2; + const COLOR_ESCAPES = 0x4; + } +); + +#[derive(Debug)] +pub struct Err<'a> { + ptr: &'a mut winpty_error_t, + code: u32, + message: String, +} + +// Check to see whether winpty gave us an error +fn check_err<'a>(e: *mut winpty_error_t) -> Option> { + let err = unsafe { + let raw = winpty_error_msg(e); + Err { + ptr: &mut *e, + code: winpty_error_code(e), + message: String::from_utf16_lossy(std::slice::from_raw_parts(raw, wcslen(raw))), + } + }; + if err.code == 0 { + None + } else { + Some(err) + } +} + +impl<'a> Drop for Err<'a> { + fn drop(&mut self) { + unsafe { + winpty_error_free(self.ptr); + } + } +} +impl<'a> Display for Err<'a> { + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + write!(f, "Code: {}, Message: {}", self.code, self.message) + } +} +impl<'a> Error for Err<'a> { + fn description(&self) -> &str { + &self.message + } +} + +#[derive(Debug)] +/// Winpty agent config +pub struct Config<'a>(&'a mut winpty_config_t); + +impl<'a, 'b> Config<'a> { + pub fn new(flags: ConfigFlags) -> Result> { + let mut err = null_mut() as *mut winpty_error_t; + let config = unsafe { winpty_config_new(flags.bits(), &mut err) }; + + if let Some(err) = check_err(err) { + Result::Err(err) + } else { + unsafe { Ok(Config(&mut *config)) } + } + } + + /// Set the initial size of the console window + pub fn set_initial_size(&mut self, cols: i32, rows: i32) { + unsafe { + winpty_config_set_initial_size(self.0, cols, rows); + } + } + + /// Set the mouse mode + pub fn set_mouse_mode(&mut self, mode: &MouseMode) { + let m = match mode { + MouseMode::None => 0, + MouseMode::Auto => 1, + MouseMode::Force => 2, + }; + unsafe { + winpty_config_set_mouse_mode(self.0, m); + } + } + + /// Amount of time to wait for the agent to startup and to wait for any given + /// agent RPC request. Must be greater than 0. Can be INFINITE. + // Might be a better way to represent this while still retaining infinite capability? + // Enum? + pub fn set_agent_timeout(&mut self, timeout: u32) { + unsafe { + winpty_config_set_agent_timeout(self.0, timeout); + } + } +} + +impl<'a> Drop for Config<'a> { + fn drop(&mut self) { + unsafe { + winpty_config_free(self.0); + } + } +} + +#[derive(Debug)] +/// A struct representing the winpty agent process +pub struct Winpty<'a>(&'a mut winpty_t); + +impl<'a, 'b> Winpty<'a> { + /// Starts the agent. This process will connect to the agent + /// over a control pipe, and the agent will open data pipes + /// (e.g. CONIN and CONOUT). + pub fn open(cfg: &Config) -> Result> { + let mut err = null_mut() as *mut winpty_error_t; + unsafe { + let winpty = winpty_open(cfg.0, &mut err); + let err = check_err(err); + if let Some(err) = err { + Result::Err(err) + } else { + Ok(Winpty(&mut *winpty)) + } + } + } + + /// Returns the handle to the winpty agent process + pub fn raw_handle(&mut self) -> RawHandle { + unsafe { winpty_agent_process(self.0) } + } + + /// Returns the name of the input pipe. + /// Pipe is half-duplex. + pub fn conin_name(&mut self) -> PathBuf { + unsafe { + let raw = winpty_conin_name(self.0); + PathBuf::from(&String::from_utf16_lossy(std::slice::from_raw_parts( + raw, + wcslen(raw), + ))) + } + } + + /// Returns the name of the output pipe. + /// Pipe is half-duplex. + pub fn conout_name(&mut self) -> PathBuf { + unsafe { + let raw = winpty_conout_name(self.0); + PathBuf::from(&String::from_utf16_lossy(std::slice::from_raw_parts( + raw, + wcslen(raw), + ))) + } + } + + /// Returns the name of the error pipe. + /// The name will only be valid if ConfigFlags::CONERR was specified. + /// Pipe is half-duplex. + pub fn conerr_name(&mut self) -> PathBuf { + unsafe { + let raw = winpty_conerr_name(self.0); + PathBuf::from(&String::from_utf16_lossy(std::slice::from_raw_parts( + raw, + wcslen(raw), + ))) + } + } + + /// Change the size of the Windows console window. + /// + /// cols & rows MUST be greater than 0 + pub fn set_size(&mut self, cols: usize, rows: usize) -> Result<(), Err> { + assert!(cols > 0 && rows > 0); + let mut err = null_mut() as *mut winpty_error_t; + + unsafe { + winpty_set_size(self.0, cols as i32, rows as i32, &mut err); + } + + if let Some(err) = check_err(err) { + Result::Err(err) + } else { + Ok(()) + } + } + + /// Get the list of processses running in the winpty agent. Returns <= count processes + /// + /// `count` must be greater than 0. Larger values cause a larger allocation. + // TODO: This should return Vec instead of Vec + pub fn console_process_list(&mut self, count: usize) -> Result, Err> { + assert!(count > 0); + + let mut err = null_mut() as *mut winpty_error_t; + let mut process_list = Vec::with_capacity(count); + + unsafe { + let len = winpty_get_console_process_list(self.0, process_list.as_mut_ptr(), count as i32, &mut err) as usize; + process_list.set_len(len); + } + + if let Some(err) = check_err(err) { + Result::Err(err) + } else { + Ok(process_list) + } + } + + /// Spawns the new process. + /// + /// spawn can only be called once per Winpty object. If it is called + /// before the output data pipe(s) is/are connected, then collected output is + /// buffered until the pipes are connected, rather than being discarded. + /// (https://blogs.msdn.microsoft.com/oldnewthing/20110107-00/?p=11803) + // TODO: Support getting the process and thread handle of the spawned process (Not the agent) + // TODO: Support returning the error from CreateProcess + pub fn spawn( + &mut self, + cfg: &SpawnConfig, + ) -> Result<(), Err> { + let mut err = null_mut() as *mut winpty_error_t; + + unsafe { + let ok = winpty_spawn( + self.0, + cfg.0 as *const winpty_spawn_config_s, + null_mut(), // Process handle + null_mut(), // Thread handle + null_mut(), // Create process error + &mut err, + ); + if ok == 0 { return Ok(());} + } + + if let Some(err) = check_err(err) { + Result::Err(err) + } else { + Ok(()) + } + } +} + +// winpty_t is thread-safe +unsafe impl<'a> Sync for Winpty<'a> {} +unsafe impl<'a> Send for Winpty<'a> {} + +impl<'a> Drop for Winpty<'a> { + fn drop(&mut self) { + unsafe { + winpty_free(self.0); + } + } +} + +#[derive(Debug)] +/// Information about a process for winpty to spawn +pub struct SpawnConfig<'a>(&'a mut winpty_spawn_config_t); + +impl<'a, 'b> SpawnConfig<'a> { + /// Creates a new spawnconfig + pub fn new( + spawnflags: SpawnFlags, + appname: Option<&str>, + cmdline: Option<&str>, + cwd: Option<&str>, + end: Option<&str>, + ) -> Result> { + let mut err = null_mut() as *mut winpty_error_t; + let (appname, cmdline, cwd, end) = ( + appname.map_or(null(), |s| WideCString::from_str(s).unwrap().into_raw()), + cmdline.map_or(null(), |s| WideCString::from_str(s).unwrap().into_raw()), + cwd.map_or(null(), |s| WideCString::from_str(s).unwrap().into_raw()), + end.map_or(null(), |s| WideCString::from_str(s).unwrap().into_raw()), + ); + + let spawn_config = unsafe { + winpty_spawn_config_new(spawnflags.bits(), appname, cmdline, cwd, end, &mut err) + }; + + // Required to free the strings + unsafe { + if !appname.is_null() { + WideCString::from_raw(appname as *mut u16); + } + if !cmdline.is_null() { + WideCString::from_raw(cmdline as *mut u16); + } + if !cwd.is_null() { + WideCString::from_raw(cwd as *mut u16); + } + if !end.is_null() { + WideCString::from_raw(end as *mut u16); + } + } + + if let Some(err) = check_err(err) { + Result::Err(err) + } else { + unsafe { Ok(SpawnConfig(&mut *spawn_config)) } + } + } +} +impl<'a> Drop for SpawnConfig<'a> { + fn drop(&mut self) { + unsafe { + winpty_spawn_config_free(self.0); + } + } +} + +#[cfg(test)] +mod tests { + extern crate named_pipe; + extern crate winapi; + + use self::named_pipe::PipeClient; + use self::winapi::um::processthreadsapi::OpenProcess; + use self::winapi::um::winnt::READ_CONTROL; + + use {Config, ConfigFlags, SpawnConfig, SpawnFlags, Winpty}; + + #[test] + // Test that we can start a process in winpty + fn spawn_process() { + let mut winpty = Winpty::open( + &Config::new(ConfigFlags::empty()).expect("failed to create config") + ).expect("failed to create winpty instance"); + + winpty.spawn( + &SpawnConfig::new( + SpawnFlags::empty(), + None, + Some("cmd"), + None, + None + ).expect("failed to create spawn config") + ).unwrap(); + } + + #[test] + // Test that pipes connected before winpty is spawned can be connected to + fn valid_pipe_connect_before() { + let mut winpty = Winpty::open( + &Config::new(ConfigFlags::empty()).expect("failed to create config") + ).expect("failed to create winpty instance"); + + // Check we can connect to both pipes + PipeClient::connect_ms(winpty.conout_name(), 1000).expect("failed to connect to conout pipe"); + PipeClient::connect_ms(winpty.conin_name(), 1000).expect("failed to connect to conin pipe"); + + winpty.spawn( + &SpawnConfig::new( + SpawnFlags::empty(), + None, + Some("cmd"), + None, + None + ).expect("failed to create spawn config") + ).unwrap(); + } + + #[test] + // Test that pipes connected after winpty is spawned can be connected to + fn valid_pipe_connect_after() { + let mut winpty = Winpty::open( + &Config::new(ConfigFlags::empty()).expect("failed to create config") + ).expect("failed to create winpty instance"); + + winpty.spawn( + &SpawnConfig::new( + SpawnFlags::empty(), + None, + Some("cmd"), + None, + None + ).expect("failed to create spawn config") + ).unwrap(); + + // Check we can connect to both pipes + PipeClient::connect_ms(winpty.conout_name(), 1000).expect("failed to connect to conout pipe"); + PipeClient::connect_ms(winpty.conin_name(), 1000).expect("failed to connect to conin pipe"); + } + + #[test] + fn resize() { + let mut winpty = Winpty::open( + &Config::new(ConfigFlags::empty()).expect("failed to create config") + ).expect("failed to create winpty instance"); + + winpty.spawn( + &SpawnConfig::new( + SpawnFlags::empty(), + None, + Some("cmd"), + None, + None + ).expect("failed to create spawn config") + ).unwrap(); + + winpty.set_size(1, 1).unwrap(); + } + + #[test] + #[ignore] + // Test that each id returned by cosole_process_list points to an actual process + fn console_process_list_valid() { + let mut winpty = Winpty::open( + &Config::new(ConfigFlags::empty()).expect("failed to create config") + ).expect("failed to create winpty instance"); + + winpty.spawn( + &SpawnConfig::new( + SpawnFlags::empty(), + None, + Some("cmd"), + None, + None + ).expect("failed to create spawn config") + ).unwrap(); + + let processes = winpty.console_process_list(1000).expect("failed to get console process list"); + + // Check that each id is valid + processes.iter().for_each(|id| { + let handle = unsafe { + OpenProcess( + READ_CONTROL, // permissions + false as i32, // inheret + *id as u32 + ) + }; + assert!(!handle.is_null()); + }); + } +}