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:
parent
416311892b
commit
eeab341879
@ -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
|
||||
|
@ -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};
|
||||
|
@ -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 {
|
||||
|
@ -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 })
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
);
|
||||
|
||||
|
@ -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!(
|
||||
|
Loading…
Reference in New Issue
Block a user