mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
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.
This commit is contained in:
parent
cc1ad49172
commit
5a8a34304f
3 changed files with 476 additions and 469 deletions
|
@ -5,12 +5,12 @@ authors = ["Zac Pullar-Strecker <zacmps@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Safe rust bindings for winpty"
|
description = "Safe rust bindings for winpty"
|
||||||
|
|
||||||
[dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
# TODO: Replace with official repo
|
# TODO: Replace with official repo
|
||||||
winpty-sys = { git = "https://github.com/zacps/winpty", branch = "rust" }
|
winpty-sys = { git = "https://github.com/zacps/winpty", branch = "rust" }
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
widestring = "0.2.2"
|
widestring = "0.2.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[target.'cfg(windows)'.dev-dependencies]
|
||||||
named_pipe = "0.3"
|
named_pipe = "0.3"
|
||||||
winapi = { version = "0.3", features = ["winnt", "processthreadsapi"] }
|
winapi = { version = "0.3", features = ["winnt", "processthreadsapi"] }
|
||||||
|
|
|
@ -1,475 +1,15 @@
|
||||||
#![cfg_attr(feature = "cargo-clippy", deny(clippy, if_not_else, enum_glob_use, wrong_pub_self_convention))]
|
#![cfg_attr(feature = "cargo-clippy", deny(clippy, if_not_else, enum_glob_use, wrong_pub_self_convention))]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
#[cfg(windows)]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
|
#[cfg(windows)]
|
||||||
extern crate widestring;
|
extern crate widestring;
|
||||||
|
#[cfg(windows)]
|
||||||
extern crate winpty_sys;
|
extern crate winpty_sys;
|
||||||
|
|
||||||
use std::error::Error;
|
#[cfg(windows)]
|
||||||
use std::fmt;
|
pub mod windows;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::result::Result;
|
|
||||||
use std::os::windows::io::RawHandle;
|
|
||||||
use std::ptr::{null, null_mut};
|
|
||||||
use fmt::{Display, Formatter};
|
|
||||||
|
|
||||||
use winpty_sys::*;
|
#[cfg(windows)]
|
||||||
|
pub use windows::*;
|
||||||
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());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
467
winpty/src/windows.rs
Normal file
467
winpty/src/windows.rs
Normal file
|
@ -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());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue