summaryrefslogtreecommitdiffstats
path: root/winpty
diff options
context:
space:
mode:
authorChristian Duerr <chrisduerr@users.noreply.github.com>2018-10-31 19:47:21 +0000
committerGitHub <noreply@github.com>2018-10-31 19:47:21 +0000
commit5a8a34304f38bce95ff89824e79c894f15183cbc (patch)
treecc5fefe015a5f90e12c36bcb4ffb49d5d6a90c10 /winpty
parentcc1ad49172bfc2ec494c9c79c54f66cc95cd3135 (diff)
Fix deb build
Since cargo-deb builds all members of the workspace by default, it is necessary that the winpty subcrate can be built on all operating systems, since it's not possible to have OS-specific workspace members. To achieve this the crate has been changed to be empty by default on non-windows systems. It might make sense to do something similar with winpty-sys, but it's not strictly necessary at this point since we don't directly depend on it. This fixes #1716.
Diffstat (limited to 'winpty')
-rw-r--r--winpty/Cargo.toml4
-rw-r--r--winpty/src/lib.rs474
-rw-r--r--winpty/src/windows.rs467
3 files changed, 476 insertions, 469 deletions
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 <zacmps@gmail.com>"]
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<Err<'a>> {
- 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<Self, Err<'b>> {
- 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<Self, Err<'b>> {
- 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<Handle> instead of Vec<i32>
- pub fn console_process_list(&mut self, count: usize) -> Result<Vec<i32>, 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<Self, Err<'b>> {
- 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<Err<'a>> {
+ 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<Self, Err<'b>> {
+ 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<Self, Err<'b>> {
+ 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<Handle> instead of Vec<i32>
+ pub fn console_process_list(&mut self, count: usize) -> Result<Vec<i32>, 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<Self, Err<'b>> {
+ 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());
+ });
+ }
+}