1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-20 19:27:22 +03:00

add use-winpty feature

When enabled, the windows version will use winpty rather than conpty.
This potentially allows running on older windows versions but has
a few caveats:

* Requires winpty.dll and winpty-agent.exe be in the PATH
* The initial screen / cursor position looks funky for me with the
  latest release of winpty, but that is from a couple of years ago
* Mouse reporting doesn't work, perhaps for the same reasons that
  it isn't working in conpty.

I want to make this into a runtime selectable feature before tidying
up the installation aspects; we need that plumbing anyway to be
able to select between local and remote mux'd tabs.
This commit is contained in:
Wez Furlong 2019-03-25 09:21:00 -07:00
parent 416311892b
commit eeab341879
9 changed files with 221 additions and 32 deletions

View File

@ -89,6 +89,7 @@ core-text = "13.1"
debug-escape-sequences = ["term/debug-escape-sequences"]
force-glutin = []
force-fontconfig = ["fontconfig"]
use-winpty = []
[patch.crates-io]
# We need https://github.com/tomaka/glutin/pull/1099

View File

@ -5,7 +5,10 @@ pub mod win;
#[cfg(unix)]
pub use self::unix::{openpty, Child, Command, ExitStatus, MasterPty, SlavePty};
#[cfg(windows)]
#[cfg(all(windows, not(feature = "use-winpty")))]
pub use self::win::conpty::{openpty, Command, MasterPty, SlavePty};
#[cfg(all(windows, feature = "use-winpty"))]
pub use self::win::winpty::{openpty, Command, MasterPty, SlavePty};
#[cfg(windows)]
pub use self::win::{Child, ExitStatus};

View File

@ -1,7 +1,7 @@
use failure::Error;
use std::env;
use std::ffi::{OsStr, OsString};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::ffi::OsStrExt;
#[derive(Debug)]
pub struct CommandBuilder {

View File

@ -1,28 +1,24 @@
use super::cmdline::CommandBuilder;
use super::ownedhandle::OwnedHandle;
use super::winsize;
use super::{Child, ExitStatus};
use super::Child;
use failure::Error;
use lazy_static::lazy_static;
use shared_library::shared_library;
use std::env;
use std::ffi::{OsStr, OsString};
use std::io::{self, Error as IoError, Result as IoResult};
use std::io::{self, Error as IoError};
use std::mem;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::os::windows::ffi::OsStringExt;
use std::os::windows::raw::HANDLE;
use std::path::Path;
use std::ptr;
use std::sync::{Arc, Mutex};
use winapi::shared::minwindef::DWORD;
use winapi::shared::winerror::{HRESULT, S_OK};
use winapi::um::fileapi::WriteFile;
use winapi::um::handleapi::*;
use winapi::um::namedpipeapi::CreatePipe;
use winapi::um::processthreadsapi::*;
use winapi::um::synchapi::WaitForSingleObject;
use winapi::um::winbase::EXTENDED_STARTUPINFO_PRESENT;
use winapi::um::winbase::INFINITE;
use winapi::um::winbase::STARTUPINFOEXW;
use winapi::um::wincon::COORD;
@ -114,10 +110,8 @@ impl Command {
// Make sure we close out the thread handle so we don't leak it;
// we do this simply by making it owned
let _main_thread = OwnedHandle { handle: pi.hThread };
let proc = OwnedHandle {
handle: pi.hProcess,
};
let _main_thread = OwnedHandle::new(pi.hThread);
let proc = OwnedHandle::new(pi.hProcess);
Ok(Child { proc })
}

View File

@ -1,4 +1,4 @@
use std::io::{self, Error as IoError, Result as IoResult};
use std::io::{Error as IoError, Result as IoResult};
use winapi::shared::minwindef::DWORD;
use winapi::um::minwinbase::STILL_ACTIVE;
use winapi::um::processthreadsapi::*;
@ -6,10 +6,14 @@ use winapi::um::synchapi::WaitForSingleObject;
use winapi::um::winbase::INFINITE;
pub mod cmdline;
#[cfg(not(feature = "use-winpty"))]
pub mod conpty;
pub mod ownedhandle;
#[cfg(feature = "use-winpty")]
pub mod winpty;
pub mod ownedhandle;
use ownedhandle::OwnedHandle;
#[derive(Debug, Clone, Copy)]

View File

@ -1,2 +1,171 @@
use super::cmdline::CommandBuilder;
use super::ownedhandle::OwnedHandle;
use super::{winsize, Child};
use failure::Error;
use safe::{AgentFlags, MouseMode, SpawnConfig, SpawnFlags, Timeout, WinPty, WinPtyConfig};
use std::ffi::{OsStr, OsString};
use std::os::windows::ffi::OsStringExt;
use std::path::Path;
use std::sync::{Arc, Mutex};
mod safe;
mod sys;
#[derive(Debug)]
pub struct Command {
builder: CommandBuilder,
}
impl Command {
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
Self {
builder: CommandBuilder::new(program),
}
}
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command {
self.builder.arg(arg);
self
}
pub fn args<I, S>(&mut self, args: I) -> &mut Command
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.builder.args(args);
self
}
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Command
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.builder.env(key, val);
self
}
}
struct Inner {
pty: WinPty,
size: winsize,
reader: OwnedHandle,
writer: OwnedHandle,
}
#[derive(Clone)]
pub struct MasterPty {
inner: Arc<Mutex<Inner>>,
}
pub struct SlavePty {
inner: Arc<Mutex<Inner>>,
}
impl MasterPty {
pub fn resize(
&self,
num_rows: u16,
num_cols: u16,
pixel_width: u16,
pixel_height: u16,
) -> Result<(), Error> {
let mut inner = self.inner.lock().unwrap();
if inner.pty.set_size(num_cols as i32, num_rows as i32)? {
inner.size = winsize {
ws_row: num_rows,
ws_col: num_cols,
ws_xpixel: pixel_width,
ws_ypixel: pixel_height,
};
Ok(())
} else {
bail!("MasterPty::resize returned false");
}
}
pub fn get_size(&self) -> Result<winsize, Error> {
Ok(self.inner.lock().unwrap().size)
}
pub fn try_clone_reader(&self) -> Result<Box<std::io::Read + Send>, Error> {
Ok(Box::new(self.inner.lock().unwrap().reader.try_clone()?))
}
}
impl std::io::Write for MasterPty {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
self.inner.lock().unwrap().writer.write(buf)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
Ok(())
}
}
impl SlavePty {
pub fn spawn_command(self, cmd: Command) -> Result<Child, Error> {
let (exe, cmdline) = cmd.builder.cmdline()?;
let cmd_os = OsString::from_wide(&cmdline);
eprintln!(
"Running: module: {} {:?}",
Path::new(&OsString::from_wide(&exe)).display(),
cmd_os
);
let spawn_config = SpawnConfig::new(
SpawnFlags::AUTO_SHUTDOWN | SpawnFlags::EXIT_AFTER_SHUTDOWN,
Some(exe),
Some(cmdline),
None, // cwd
None, // env
)?;
let mut inner = self.inner.lock().unwrap();
let spawned = inner.pty.spawn(&spawn_config)?;
let child = Child {
proc: spawned.process_handle,
};
Ok(child)
}
}
pub fn openpty(
num_rows: u16,
num_cols: u16,
pixel_width: u16,
pixel_height: u16,
) -> Result<(MasterPty, SlavePty), Error> {
let mut config = WinPtyConfig::new(AgentFlags::empty())?;
config.set_initial_size(num_cols as i32, num_rows as i32);
config.set_mouse_mode(MouseMode::Auto);
config.set_agent_timeout(Timeout::Milliseconds(10_000));
let pty = config.open()?;
let reader = pty.conout()?;
let writer = pty.conin()?;
let size = winsize {
ws_row: num_rows,
ws_col: num_cols,
ws_xpixel: pixel_width,
ws_ypixel: pixel_height,
};
let inner = Arc::new(Mutex::new(Inner {
pty,
reader,
writer,
size,
}));
let master = MasterPty {
inner: Arc::clone(&inner),
};
let slave = SlavePty { inner };
Ok((master, slave))
}

View File

@ -1,6 +1,7 @@
//! A type-safe wrapper around the sys module, which in turn exposes
//! the API exported by winpty.dll.
//! https://github.com/rprichard/winpty/blob/master/src/include/winpty.h
#![allow(dead_code)]
use super::sys::*;
use crate::pty::win::ownedhandle::OwnedHandle;
use bitflags::bitflags;
@ -32,18 +33,18 @@ bitflags! {
}
#[repr(u32)]
enum MouseMode {
pub enum MouseMode {
None = WINPTY_MOUSE_MODE_NONE,
Auto = WINPTY_MOUSE_MODE_AUTO,
Force = WINPTY_MOUSE_MODE_FORCE,
}
enum Timeout {
pub enum Timeout {
Infinite,
Milliseconds(DWORD),
}
struct WinPtyConfig {
pub struct WinPtyConfig {
config: *mut winpty_config_t,
}
@ -60,7 +61,9 @@ fn wstr_to_string(wstr: LPCWSTR) -> Result<String, Error> {
}
fn check_err<T>(err: winpty_error_ptr_t, value: T) -> Result<T, Error> {
ensure!(!err.is_null(), "winpty error object is null");
if err.is_null() {
return Ok(value);
}
unsafe {
let code = (WINPTY.winpty_error_code)(err);
if code == WINPTY_ERROR_SUCCESS {
@ -126,11 +129,15 @@ impl Drop for WinPty {
}
}
fn pipe_client(name: LPCWSTR) -> Result<OwnedHandle, Error> {
fn pipe_client(name: LPCWSTR, for_read: bool) -> Result<OwnedHandle, Error> {
let handle = unsafe {
CreateFileW(
name,
GENERIC_READ | GENERIC_WRITE,
if for_read {
GENERIC_READ
} else {
GENERIC_WRITE
},
0,
ptr::null_mut(),
OPEN_EXISTING,
@ -152,20 +159,20 @@ impl WinPty {
}
pub fn conin(&self) -> Result<OwnedHandle, Error> {
pipe_client(unsafe { (WINPTY.winpty_conin_name)(self.pty) })
pipe_client(unsafe { (WINPTY.winpty_conin_name)(self.pty) }, false)
}
pub fn conout(&self) -> Result<OwnedHandle, Error> {
pipe_client(unsafe { (WINPTY.winpty_conout_name)(self.pty) })
pipe_client(unsafe { (WINPTY.winpty_conout_name)(self.pty) }, true)
}
pub fn conerr(&self) -> Result<OwnedHandle, Error> {
pipe_client(unsafe { (WINPTY.winpty_conerr_name)(self.pty) })
pipe_client(unsafe { (WINPTY.winpty_conerr_name)(self.pty) }, true)
}
pub fn set_size(&mut self, rows: c_int, cols: c_int) -> Result<bool, Error> {
pub fn set_size(&mut self, cols: c_int, rows: c_int) -> Result<bool, Error> {
let mut err: winpty_error_ptr_t = ptr::null_mut();
let result = unsafe { (WINPTY.winpty_set_size)(self.pty, rows, cols, &mut err) };
let result = unsafe { (WINPTY.winpty_set_size)(self.pty, cols, rows, &mut err) };
Ok(result != 0)
}
@ -200,8 +207,8 @@ impl WinPty {
}
pub struct SpawnedProcess {
process_handle: OwnedHandle,
thread_handle: OwnedHandle,
pub process_handle: OwnedHandle,
pub thread_handle: OwnedHandle,
}
pub struct SpawnConfig {
@ -223,7 +230,7 @@ fn str_ptr(s: &Option<Vec<u16>>) -> LPCWSTR {
}
impl SpawnConfig {
pub fn new(
pub fn with_os_str_args(
flags: SpawnFlags,
appname: Option<&OsStr>,
cmdline: Option<&OsStr>,
@ -234,7 +241,16 @@ impl SpawnConfig {
let cmdline = cmdline.map(str_to_wide);
let cwd = cwd.map(str_to_wide);
let env = env.map(str_to_wide);
Self::new(flags, appname, cmdline, cwd, env)
}
pub fn new(
flags: SpawnFlags,
appname: Option<Vec<u16>>,
cmdline: Option<Vec<u16>>,
cwd: Option<Vec<u16>>,
env: Option<Vec<u16>>,
) -> Result<Self, Error> {
let mut err: winpty_error_ptr_t = ptr::null_mut();
let spawn_config = unsafe {

View File

@ -93,12 +93,14 @@ shared_library!(WinPtyFuncs,
rows: c_int,
err: *mut winpty_error_ptr_t
) -> BOOL,
/*
pub fn winpty_get_console_process_list(
wp: *mut winpty_t,
processList: *mut c_int,
processCount: c_int,
err: *mut winpty_error_ptr_t
) -> c_int,
*/
pub fn winpty_free(wp: *mut winpty_t),
);

View File

@ -171,11 +171,11 @@ fn safely_create_sock_path(sock_path: &String) -> Result<UnixListener, Error> {
builder.create(sock_dir)?;
// Let's be sure that the ownership looks sane
let meta = sock_dir.symlink_metadata()?;
#[cfg(unix)]
{
// Let's be sure that the ownership looks sane
let meta = sock_dir.symlink_metadata()?;
let permissions = meta.permissions();
if (permissions.mode() & 0o22) != 0 {
bail!(