mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-11 13:51:01 -05:00
parent
d741d3817d
commit
48861e4633
8 changed files with 171 additions and 54 deletions
|
@ -30,6 +30,7 @@ use alacritty_terminal::selection::Selection;
|
||||||
use alacritty_terminal::sync::FairMutex;
|
use alacritty_terminal::sync::FairMutex;
|
||||||
use alacritty_terminal::term::cell::Cell;
|
use alacritty_terminal::term::cell::Cell;
|
||||||
use alacritty_terminal::term::{SizeInfo, Term};
|
use alacritty_terminal::term::{SizeInfo, Term};
|
||||||
|
#[cfg(not(windows))]
|
||||||
use alacritty_terminal::tty;
|
use alacritty_terminal::tty;
|
||||||
use alacritty_terminal::util::{limit, start_daemon};
|
use alacritty_terminal::util::{limit, start_daemon};
|
||||||
|
|
||||||
|
@ -351,14 +352,14 @@ impl<N: Notify> Processor<N> {
|
||||||
info!("glutin event: {:?}", event);
|
info!("glutin event: {:?}", event);
|
||||||
}
|
}
|
||||||
|
|
||||||
match (&event, tty::process_should_exit()) {
|
match &event {
|
||||||
// Check for shutdown
|
// Check for shutdown
|
||||||
(GlutinEvent::UserEvent(Event::Exit), _) | (_, true) => {
|
GlutinEvent::UserEvent(Event::Exit) => {
|
||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Exit;
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
// Process events
|
// Process events
|
||||||
(GlutinEvent::EventsCleared, _) => {
|
GlutinEvent::EventsCleared => {
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
if event_queue.is_empty() {
|
if event_queue.is_empty() {
|
||||||
|
|
|
@ -250,8 +250,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue terminal redraw
|
if processed > 0 {
|
||||||
self.event_proxy.send_event(Event::Wakeup);
|
// Queue terminal redraw
|
||||||
|
self.event_proxy.send_event(Event::Wakeup);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -327,7 +329,6 @@ where
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
token if token == self.pty.child_event_token() => {
|
token if token == self.pty.child_event_token() => {
|
||||||
if let Some(tty::ChildEvent::Exited) = self.pty.next_child_event() {
|
if let Some(tty::ChildEvent::Exited) = self.pty.next_child_event() {
|
||||||
if !self.hold {
|
if !self.hold {
|
||||||
|
|
|
@ -54,7 +54,7 @@ pub trait EventedReadWrite {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Events concerning TTY child processes
|
/// Events concerning TTY child processes
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ChildEvent {
|
pub enum ChildEvent {
|
||||||
/// Indicates the child has exited
|
/// Indicates the child has exited
|
||||||
Exited,
|
Exited,
|
||||||
|
@ -66,13 +66,11 @@ pub enum ChildEvent {
|
||||||
/// notified if the PTY child process does something we care about (other than writing to the TTY).
|
/// notified if the PTY child process does something we care about (other than writing to the TTY).
|
||||||
/// In particular, this allows for race-free child exit notification on UNIX (cf. `SIGCHLD`).
|
/// In particular, this allows for race-free child exit notification on UNIX (cf. `SIGCHLD`).
|
||||||
pub trait EventedPty: EventedReadWrite {
|
pub trait EventedPty: EventedReadWrite {
|
||||||
#[cfg(unix)]
|
|
||||||
fn child_event_token(&self) -> mio::Token;
|
fn child_event_token(&self) -> mio::Token;
|
||||||
|
|
||||||
/// Tries to retrieve an event
|
/// Tries to retrieve an event
|
||||||
///
|
///
|
||||||
/// Returns `Some(event)` on success, or `None` if there are no events to retrieve.
|
/// Returns `Some(event)` on success, or `None` if there are no events to retrieve.
|
||||||
#[cfg(unix)]
|
|
||||||
fn next_child_event(&mut self) -> Option<ChildEvent>;
|
fn next_child_event(&mut self) -> Option<ChildEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -333,10 +333,6 @@ impl EventedPty for Pty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_should_exit() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Types that can produce a `libc::winsize`
|
/// Types that can produce a `libc::winsize`
|
||||||
pub trait ToWinsize {
|
pub trait ToWinsize {
|
||||||
/// Get a `libc::winsize`
|
/// Get a `libc::winsize`
|
||||||
|
|
115
alacritty_terminal/src/tty/windows/child.rs
Normal file
115
alacritty_terminal/src/tty/windows/child.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::io::Error;
|
||||||
|
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||||
|
|
||||||
|
use mio_extras::channel::{channel, Receiver, Sender};
|
||||||
|
|
||||||
|
use winapi::shared::ntdef::{BOOLEAN, HANDLE, PVOID};
|
||||||
|
use winapi::um::winbase::{RegisterWaitForSingleObject, UnregisterWait, INFINITE};
|
||||||
|
use winapi::um::winnt::{WT_EXECUTEINWAITTHREAD, WT_EXECUTEONLYONCE};
|
||||||
|
|
||||||
|
use crate::tty::ChildEvent;
|
||||||
|
|
||||||
|
/// WinAPI callback to run when child process exits.
|
||||||
|
extern "system" fn child_exit_callback(ctx: PVOID, timed_out: BOOLEAN) {
|
||||||
|
if timed_out != 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let event_tx: Box<_> = unsafe { Box::from_raw(ctx as *mut Sender<ChildEvent>) };
|
||||||
|
let _ = event_tx.send(ChildEvent::Exited);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChildExitWatcher {
|
||||||
|
wait_handle: AtomicPtr<c_void>,
|
||||||
|
event_rx: Receiver<ChildEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChildExitWatcher {
|
||||||
|
pub fn new(child_handle: HANDLE) -> Result<ChildExitWatcher, Error> {
|
||||||
|
let (event_tx, event_rx) = channel::<ChildEvent>();
|
||||||
|
|
||||||
|
let mut wait_handle: HANDLE = 0 as HANDLE;
|
||||||
|
let sender_ref = Box::new(event_tx);
|
||||||
|
|
||||||
|
let success = unsafe {
|
||||||
|
RegisterWaitForSingleObject(
|
||||||
|
&mut wait_handle,
|
||||||
|
child_handle,
|
||||||
|
Some(child_exit_callback),
|
||||||
|
Box::into_raw(sender_ref) as PVOID,
|
||||||
|
INFINITE,
|
||||||
|
WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if success == 0 {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(ChildExitWatcher { wait_handle: AtomicPtr::from(wait_handle), event_rx })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_rx(&self) -> &Receiver<ChildEvent> {
|
||||||
|
&self.event_rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ChildExitWatcher {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
UnregisterWait(self.wait_handle.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::os::windows::io::AsRawHandle;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use mio::{Events, Poll, PollOpt, Ready, Token};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn event_is_emitted_when_child_exits() {
|
||||||
|
const WAIT_TIMEOUT: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
|
let mut child = Command::new("cmd.exe").spawn().unwrap();
|
||||||
|
let child_exit_watcher = ChildExitWatcher::new(child.as_raw_handle()).unwrap();
|
||||||
|
|
||||||
|
let mut events = Events::with_capacity(1);
|
||||||
|
let poll = Poll::new().unwrap();
|
||||||
|
let child_events_token = Token::from(0usize);
|
||||||
|
|
||||||
|
poll.register(
|
||||||
|
child_exit_watcher.event_rx(),
|
||||||
|
child_events_token,
|
||||||
|
Ready::readable(),
|
||||||
|
PollOpt::oneshot(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
child.kill().unwrap();
|
||||||
|
|
||||||
|
// Poll for the event or fail with timeout if nothing has been sent
|
||||||
|
poll.poll(&mut events, Some(WAIT_TIMEOUT)).unwrap();
|
||||||
|
assert_eq!(events.iter().next().unwrap().token(), child_events_token);
|
||||||
|
// Verify that at least one `ChildEvent::Exited` was received
|
||||||
|
assert_eq!(child_exit_watcher.event_rx().try_recv(), Ok(ChildEvent::Exited));
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,8 +12,6 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::{Pty, HANDLE};
|
|
||||||
|
|
||||||
use std::i16;
|
use std::i16;
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -22,7 +20,6 @@ use std::ptr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
use log::info;
|
|
||||||
use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
|
use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
|
||||||
use miow;
|
use miow;
|
||||||
use widestring::U16CString;
|
use widestring::U16CString;
|
||||||
|
@ -41,6 +38,8 @@ use winapi::um::wincontypes::{COORD, HPCON};
|
||||||
use crate::config::{Config, Shell};
|
use crate::config::{Config, Shell};
|
||||||
use crate::event::OnResize;
|
use crate::event::OnResize;
|
||||||
use crate::term::SizeInfo;
|
use crate::term::SizeInfo;
|
||||||
|
use crate::tty::windows::child::ChildExitWatcher;
|
||||||
|
use crate::tty::windows::Pty;
|
||||||
|
|
||||||
/// Dynamically-loaded Pseudoconsole API from kernel32.dll
|
/// Dynamically-loaded Pseudoconsole API from kernel32.dll
|
||||||
///
|
///
|
||||||
|
@ -241,14 +240,10 @@ pub fn new<'a, C>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store handle to console
|
|
||||||
unsafe {
|
|
||||||
HANDLE = proc_info.hProcess;
|
|
||||||
}
|
|
||||||
|
|
||||||
let conin = EventedAnonWrite::new(conin);
|
let conin = EventedAnonWrite::new(conin);
|
||||||
let conout = EventedAnonRead::new(conout);
|
let conout = EventedAnonRead::new(conout);
|
||||||
|
|
||||||
|
let child_watcher = ChildExitWatcher::new(proc_info.hProcess).unwrap();
|
||||||
let agent = Conpty { handle: pty_handle, api };
|
let agent = Conpty { handle: pty_handle, api };
|
||||||
|
|
||||||
Some(Pty {
|
Some(Pty {
|
||||||
|
@ -257,6 +252,8 @@ pub fn new<'a, C>(
|
||||||
conin: super::EventedWritablePipe::Anonymous(conin),
|
conin: super::EventedWritablePipe::Anonymous(conin),
|
||||||
read_token: 0.into(),
|
read_token: 0.into(),
|
||||||
write_token: 0.into(),
|
write_token: 0.into(),
|
||||||
|
child_event_token: 0.into(),
|
||||||
|
child_watcher,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,49 +13,27 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::os::raw::c_void;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::mpsc::TryRecvError;
|
||||||
|
|
||||||
use mio::{self, Evented, Poll, PollOpt, Ready, Token};
|
use mio::{self, Evented, Poll, PollOpt, Ready, Token};
|
||||||
use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
|
use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
|
||||||
use mio_named_pipes::NamedPipe;
|
use mio_named_pipes::NamedPipe;
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use winapi::shared::winerror::WAIT_TIMEOUT;
|
|
||||||
use winapi::um::synchapi::WaitForSingleObject;
|
|
||||||
use winapi::um::winbase::WAIT_OBJECT_0;
|
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::event::OnResize;
|
use crate::event::OnResize;
|
||||||
use crate::term::SizeInfo;
|
use crate::term::SizeInfo;
|
||||||
use crate::tty::{EventedPty, EventedReadWrite};
|
use crate::tty::windows::child::ChildExitWatcher;
|
||||||
|
use crate::tty::{ChildEvent, EventedPty, EventedReadWrite};
|
||||||
|
|
||||||
|
mod child;
|
||||||
mod conpty;
|
mod conpty;
|
||||||
mod winpty;
|
mod winpty;
|
||||||
|
|
||||||
/// Handle to the winpty agent or conpty process. Required so we know when it closes.
|
|
||||||
static mut HANDLE: *mut c_void = 0usize as *mut c_void;
|
|
||||||
static IS_CONPTY: AtomicBool = AtomicBool::new(false);
|
static IS_CONPTY: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
pub fn process_should_exit() -> bool {
|
|
||||||
unsafe {
|
|
||||||
match WaitForSingleObject(HANDLE, 0) {
|
|
||||||
// Process has exited
|
|
||||||
WAIT_OBJECT_0 => {
|
|
||||||
info!("wait_object_0");
|
|
||||||
true
|
|
||||||
},
|
|
||||||
// Reached timeout of 0, process has not exited
|
|
||||||
WAIT_TIMEOUT => false,
|
|
||||||
// Error checking process, winpty gave us a bad agent handle?
|
|
||||||
_ => {
|
|
||||||
info!("Bad exit: {}", ::std::io::Error::last_os_error());
|
|
||||||
true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_conpty() -> bool {
|
pub fn is_conpty() -> bool {
|
||||||
IS_CONPTY.load(Ordering::Relaxed)
|
IS_CONPTY.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
@ -76,6 +54,8 @@ pub struct Pty<'a> {
|
||||||
conin: EventedWritablePipe,
|
conin: EventedWritablePipe,
|
||||||
read_token: mio::Token,
|
read_token: mio::Token,
|
||||||
write_token: mio::Token,
|
write_token: mio::Token,
|
||||||
|
child_event_token: mio::Token,
|
||||||
|
child_watcher: ChildExitWatcher,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Pty<'a> {
|
impl<'a> Pty<'a> {
|
||||||
|
@ -244,6 +224,15 @@ impl<'a> EventedReadWrite for Pty<'a> {
|
||||||
} else {
|
} else {
|
||||||
poll.register(&self.conin, self.write_token, mio::Ready::empty(), poll_opts)?
|
poll.register(&self.conin, self.write_token, mio::Ready::empty(), poll_opts)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.child_event_token = token.next().unwrap();
|
||||||
|
poll.register(
|
||||||
|
self.child_watcher.event_rx(),
|
||||||
|
self.child_event_token,
|
||||||
|
mio::Ready::readable(),
|
||||||
|
poll_opts,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +253,14 @@ impl<'a> EventedReadWrite for Pty<'a> {
|
||||||
} else {
|
} else {
|
||||||
poll.reregister(&self.conin, self.write_token, mio::Ready::empty(), poll_opts)?;
|
poll.reregister(&self.conin, self.write_token, mio::Ready::empty(), poll_opts)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
poll.reregister(
|
||||||
|
self.child_watcher.event_rx(),
|
||||||
|
self.child_event_token,
|
||||||
|
mio::Ready::readable(),
|
||||||
|
poll_opts,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,6 +268,7 @@ impl<'a> EventedReadWrite for Pty<'a> {
|
||||||
fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> {
|
fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> {
|
||||||
poll.deregister(&self.conout)?;
|
poll.deregister(&self.conout)?;
|
||||||
poll.deregister(&self.conin)?;
|
poll.deregister(&self.conin)?;
|
||||||
|
poll.deregister(self.child_watcher.event_rx())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,4 +293,16 @@ impl<'a> EventedReadWrite for Pty<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EventedPty for Pty<'a> {}
|
impl<'a> EventedPty for Pty<'a> {
|
||||||
|
fn child_event_token(&self) -> mio::Token {
|
||||||
|
self.child_event_token
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_child_event(&mut self) -> Option<ChildEvent> {
|
||||||
|
match self.child_watcher.event_rx().try_recv() {
|
||||||
|
Ok(ev) => Some(ev),
|
||||||
|
Err(TryRecvError::Empty) => None,
|
||||||
|
Err(TryRecvError::Disconnected) => Some(ChildEvent::Exited),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::{Pty, HANDLE};
|
|
||||||
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::windows::fs::OpenOptionsExt;
|
use std::os::windows::fs::OpenOptionsExt;
|
||||||
|
@ -30,6 +28,8 @@ use winpty::{Config as WinptyConfig, ConfigFlags, MouseMode, SpawnConfig, SpawnF
|
||||||
use crate::config::{Config, Shell};
|
use crate::config::{Config, Shell};
|
||||||
use crate::event::OnResize;
|
use crate::event::OnResize;
|
||||||
use crate::term::SizeInfo;
|
use crate::term::SizeInfo;
|
||||||
|
use crate::tty::windows::child::ChildExitWatcher;
|
||||||
|
use crate::tty::windows::Pty;
|
||||||
|
|
||||||
// We store a raw pointer because we need mutable access to call
|
// We store a raw pointer because we need mutable access to call
|
||||||
// on_resize from a separate thread. Winpty internally uses a mutex
|
// on_resize from a separate thread. Winpty internally uses a mutex
|
||||||
|
@ -136,10 +136,7 @@ pub fn new<'a, C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>
|
||||||
|
|
||||||
winpty.spawn(&spawnconfig).unwrap();
|
winpty.spawn(&spawnconfig).unwrap();
|
||||||
|
|
||||||
unsafe {
|
let child_watcher = ChildExitWatcher::new(winpty.raw_handle()).unwrap();
|
||||||
HANDLE = winpty.raw_handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
let agent = Agent::new(winpty);
|
let agent = Agent::new(winpty);
|
||||||
|
|
||||||
Pty {
|
Pty {
|
||||||
|
@ -148,6 +145,8 @@ pub fn new<'a, C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>
|
||||||
conin: super::EventedWritablePipe::Named(conin_pipe),
|
conin: super::EventedWritablePipe::Named(conin_pipe),
|
||||||
read_token: 0.into(),
|
read_token: 0.into(),
|
||||||
write_token: 0.into(),
|
write_token: 0.into(),
|
||||||
|
child_event_token: 0.into(),
|
||||||
|
child_watcher,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue