1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-26 14:54:16 +03:00

use futures to manage closing a window

This commit is contained in:
Wez Furlong 2018-03-03 15:48:36 -08:00
parent 5db84856e9
commit ce8de2ca03
2 changed files with 109 additions and 69 deletions

View File

@ -13,12 +13,14 @@
use futures::{Async, Future}; use futures::{Async, Future};
use futures::executor::{self, Notify, Spawn}; use futures::executor::{self, Notify, Spawn};
use futures::future::{ExecuteError, Executor}; use futures::future::{ExecuteError, Executor};
use glium::glutin::EventsLoopProxy;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::sync::mpsc; use std::sync::mpsc;
use wakeup::{channel, GuiSender};
pub struct Core { pub struct Core {
tx: mpsc::Sender<usize>, tx: GuiSender<usize>,
rx: mpsc::Receiver<usize>, rx: mpsc::Receiver<usize>,
notify: Arc<Notifier>, notify: Arc<Notifier>,
// Slab of running futures used to track what's running and what slots are // Slab of running futures used to track what's running and what slots are
@ -34,8 +36,8 @@ enum Slot {
} }
impl Core { impl Core {
pub fn new() -> Self { pub fn new(proxy: EventsLoopProxy) -> Self {
let (tx, rx) = mpsc::channel(); let (tx, rx) = channel(proxy);
Self { Self {
notify: Arc::new(Notifier { notify: Arc::new(Notifier {
tx: Mutex::new(tx.clone()), tx: Mutex::new(tx.clone()),
@ -128,7 +130,7 @@ struct Notifier {
// itself is basically `Sync` as-is. Ideally this'd use something like // itself is basically `Sync` as-is. Ideally this'd use something like
// an off-the-shelf mpsc queue as well as `thread::park` and // an off-the-shelf mpsc queue as well as `thread::park` and
// `Thread::unpark`. // `Thread::unpark`.
tx: Mutex<mpsc::Sender<usize>>, tx: Mutex<GuiSender<usize>>,
} }
impl Notify for Notifier { impl Notify for Notifier {

View File

@ -36,15 +36,16 @@ extern crate xcb;
#[cfg(all(unix, not(target_os = "macos")))] #[cfg(all(unix, not(target_os = "macos")))]
extern crate xcb_util; extern crate xcb_util;
use futures::Future; use futures::{future, Future};
use glium::glutin::WindowId; use glium::glutin::WindowId;
use mio::{PollOpt, Ready, Token}; use mio::{PollOpt, Ready, Token};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use std::env; use std::env;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::unix::io::RawFd; use std::os::unix::io::RawFd;
use std::process::Command; use std::process::Command;
use std::rc::Rc;
use std::str; use std::str;
use std::sync::mpsc::{Receiver, TryRecvError}; use std::sync::mpsc::{Receiver, TryRecvError};
use std::time::Duration; use std::time::Duration;
@ -85,11 +86,15 @@ fn get_shell() -> Result<String, Error> {
}) })
} }
#[derive(Default)]
struct Windows {
by_id: HashMap<WindowId, gliumwindows::TerminalWindow>,
by_fd: HashMap<RawFd, WindowId>,
}
struct GuiEventLoop { struct GuiEventLoop {
event_loop: RefCell<glium::glutin::EventsLoop>, event_loop: RefCell<glium::glutin::EventsLoop>,
windows_by_id: RefCell<HashMap<WindowId, gliumwindows::TerminalWindow>>, windows: Rc<RefCell<Windows>>,
windows_by_fd: RefCell<HashMap<RawFd, WindowId>>,
terminate: RefCell<bool>,
core: futurecore::Core, core: futurecore::Core,
poll: remotemio::IOMgr, poll: remotemio::IOMgr,
poll_rx: Receiver<remotemio::Notification>, poll_rx: Receiver<remotemio::Notification>,
@ -101,7 +106,7 @@ struct GuiEventLoop {
impl GuiEventLoop { impl GuiEventLoop {
fn new() -> Result<Self, Error> { fn new() -> Result<Self, Error> {
let event_loop = glium::glutin::EventsLoop::new(); let event_loop = glium::glutin::EventsLoop::new();
let core = futurecore::Core::new(); let core = futurecore::Core::new(event_loop.create_proxy());
let (wake_tx, poll_rx) = wakeup::channel(event_loop.create_proxy()); let (wake_tx, poll_rx) = wakeup::channel(event_loop.create_proxy());
let (paster, paster_rx) = wakeup::channel(event_loop.create_proxy()); let (paster, paster_rx) = wakeup::channel(event_loop.create_proxy());
@ -118,17 +123,16 @@ impl GuiEventLoop {
paster_rx, paster_rx,
sigchld_rx, sigchld_rx,
event_loop: RefCell::new(event_loop), event_loop: RefCell::new(event_loop),
windows_by_id: RefCell::new(HashMap::new()), windows: Rc::new(RefCell::new(Default::default())),
windows_by_fd: RefCell::new(HashMap::new()),
terminate: RefCell::new(false),
}) })
} }
fn add_window(&self, window: gliumwindows::TerminalWindow) -> Result<(), Error> { fn add_window(&self, window: gliumwindows::TerminalWindow) -> Result<(), Error> {
let window_id = window.window_id(); let window_id = window.window_id();
let fd = window.pty_fd(); let fd = window.pty_fd();
self.windows_by_id.borrow_mut().insert(window_id, window); let mut windows = self.windows.borrow_mut();
self.windows_by_fd.borrow_mut().insert(fd, window_id); windows.by_id.insert(window_id, window);
windows.by_fd.insert(fd, window_id);
self.poll self.poll
.register(fd, Token(fd as usize), Ready::readable(), PollOpt::edge())? .register(fd, Token(fd as usize), Ready::readable(), PollOpt::edge())?
.wait()??; .wait()??;
@ -138,32 +142,26 @@ impl GuiEventLoop {
fn process_gui_event( fn process_gui_event(
&self, &self,
event: &glium::glutin::Event, event: &glium::glutin::Event,
dead_windows: &mut HashSet<WindowId>,
) -> Result<glium::glutin::ControlFlow, Error> { ) -> Result<glium::glutin::ControlFlow, Error> {
use glium::glutin::ControlFlow::{Break, Continue}; use glium::glutin::ControlFlow::{Break, Continue};
use glium::glutin::Event; use glium::glutin::Event;
let result = match *event { let result = match *event {
Event::WindowEvent { window_id, .. } => { Event::WindowEvent { window_id, .. } => {
match self.windows_by_id.borrow_mut().get_mut(&window_id) { let dead = match self.windows.borrow_mut().by_id.get_mut(&window_id) {
Some(window) => match window.dispatch_event(event) { Some(window) => match window.dispatch_event(event) {
Ok(_) => Continue, Ok(_) => None,
Err(err) => match err.downcast_ref::<gliumwindows::SessionTerminated>() { Err(err) => match err.downcast_ref::<gliumwindows::SessionTerminated>() {
Some(_) => { Some(_) => Some(window_id),
dead_windows.insert(window_id); _ => return Err(err),
Continue
}
_ => {
eprintln!("{:?}", err);
Break
}
}, },
}, },
None => { None => None,
// This happens surprisingly often! };
// eprintln!("window event for unknown {:?}", window_id);
Continue if let Some(window_id) = dead {
} self.schedule_window_close(window_id)?;
} }
Continue
} }
Event::Awakened => Break, Event::Awakened => Break,
_ => Continue, _ => Continue,
@ -171,34 +169,59 @@ impl GuiEventLoop {
Ok(result) Ok(result)
} }
fn schedule_window_close(&self, window_id: WindowId) -> Result<(), Error> {
let fd = {
let mut windows = self.windows.borrow_mut();
let window = windows.by_id.get_mut(&window_id).ok_or_else(|| {
format_err!("no window_id {:?} in the windows_by_id map", window_id)
})?;
window.pty_fd()
};
let windows = Rc::clone(&self.windows);
self.core.spawn(self.poll.deregister(fd)?.then(move |_| {
println!("done dereg");
let mut windows = windows.borrow_mut();
windows.by_id.remove(&window_id);
windows.by_fd.remove(&fd);
future::ok(())
}));
Ok(())
}
fn process_pty_event(&self, event: mio::Event) -> Result<(), Error> { fn process_pty_event(&self, event: mio::Event) -> Result<(), Error> {
// The token is the fd // The token is the fd
let fd = event.token().0 as RawFd; let fd = event.token().0 as RawFd;
let mut by_fd = self.windows_by_fd.borrow_mut(); let (window_id, result) = {
let result = { let mut windows = self.windows.borrow_mut();
let window_id = by_fd.get(&fd).ok_or_else(|| {
format_err!("fd {} has no associated window in windows_by_fd map", fd)
})?;
let mut by_id = self.windows_by_id.borrow_mut(); let window_id = windows
let window = by_id.get_mut(window_id).ok_or_else(|| { .by_fd
.get(&fd)
.ok_or_else(|| {
format_err!("fd {} has no associated window in windows_by_fd map", fd)
})
.map(|w| *w)?;
let window = windows.by_id.get_mut(&window_id).ok_or_else(|| {
format_err!( format_err!(
"fd {} -> window_id {:?} but no associated window is in the windows_by_id map", "fd {} -> window_id {:?} but no associated window is in the windows_by_id map",
fd, fd,
window_id window_id
) )
})?; })?;
window.try_read_pty() (window_id, window.try_read_pty())
}; };
match result { match result {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => match err.downcast_ref::<gliumwindows::SessionTerminated>() { Err(err) => match err.downcast_ref::<gliumwindows::SessionTerminated>() {
Some(_) => { Some(_) => {
eprintln!("shutting down pty: {:?}", err); self.schedule_window_close(window_id)?;
self.poll.deregister(fd)?.wait()??;
by_fd.remove(&fd);
Ok(()) Ok(())
} }
_ => { _ => {
@ -209,7 +232,7 @@ impl GuiEventLoop {
} }
fn do_paint(&self) { fn do_paint(&self) {
for (_, window) in self.windows_by_id.borrow_mut().iter_mut() { for window in &mut self.windows.borrow_mut().by_id.values_mut() {
window.paint_if_needed().unwrap(); window.paint_if_needed().unwrap();
} }
} }
@ -234,8 +257,9 @@ impl GuiEventLoop {
loop { loop {
match self.paster_rx.try_recv() { match self.paster_rx.try_recv() {
Ok(window_id) => { Ok(window_id) => {
self.windows_by_id self.windows
.borrow_mut() .borrow_mut()
.by_id
.get_mut(&window_id) .get_mut(&window_id)
.map(|w| w.process_clipboard()); .map(|w| w.process_clipboard());
} }
@ -245,30 +269,36 @@ impl GuiEventLoop {
} }
} }
fn process_sigchld_wakeups(&self, dead_windows: &mut HashSet<WindowId>) -> Result<(), Error> { fn process_sigchld_wakeups(&self) -> Result<(), Error> {
loop { loop {
match self.sigchld_rx.try_recv() { match self.sigchld_rx.try_recv() {
Ok(_) => for (window_id, window) in self.windows_by_id.borrow_mut().iter_mut() { Ok(_) => {
match window.test_for_child_exit() { let window_ids: Vec<WindowId> = self.windows
Ok(_) => {} .borrow_mut()
Err(err) => { .by_id
eprintln!("pty finished: {:?}", err); .iter_mut()
dead_windows.insert(window_id.clone()); .filter_map(|(window_id, window)| match window.test_for_child_exit() {
} Ok(_) => None,
Err(_) => Some(*window_id),
})
.collect();
for window_id in window_ids {
self.schedule_window_close(window_id)?;
} }
}, }
Err(TryRecvError::Empty) => return Ok(()), Err(TryRecvError::Empty) => return Ok(()),
Err(err) => bail!("paster_rx disconnected {:?}", err), Err(err) => bail!("paster_rx disconnected {:?}", err),
} }
} }
} }
fn run_event_loop(&self, mut dead_windows: &mut HashSet<WindowId>) -> Result<(), Error> { fn run_event_loop(&self) -> Result<(), Error> {
let mut event_loop = self.event_loop.borrow_mut(); let mut event_loop = self.event_loop.borrow_mut();
event_loop.run_forever(|event| { event_loop.run_forever(|event| {
use glium::glutin::ControlFlow::{Break, Continue}; use glium::glutin::ControlFlow::{Break, Continue};
let result = self.process_gui_event(&event, &mut dead_windows); let result = self.process_gui_event(&event);
match result { match result {
Ok(Continue) => Continue, Ok(Continue) => Continue,
@ -282,24 +312,32 @@ impl GuiEventLoop {
Ok(()) Ok(())
} }
fn process_futures(&self) {
loop {
if !self.core.turn() {
break;
}
println!("did one future");
}
}
fn run(&self) -> Result<(), Error> { fn run(&self) -> Result<(), Error> {
while !*self.terminate.borrow() { loop {
let mut dead_windows = HashSet::new(); self.process_futures();
{
let windows = self.windows.borrow();
if windows.by_id.is_empty() && windows.by_fd.is_empty() {
eprintln!("No more windows; done!");
return Ok(());
}
}
self.run_event_loop()?;
self.process_wakeups_new()?; self.process_wakeups_new()?;
self.process_paste_wakeups()?; self.process_paste_wakeups()?;
self.process_sigchld_wakeups(&mut dead_windows)?; self.process_sigchld_wakeups()?;
self.run_event_loop(&mut dead_windows)?;
for window_id in dead_windows {
self.windows_by_id.borrow_mut().remove(&window_id);
}
if self.windows_by_id.borrow().len() == 0 {
// If we have no more windows left to manage, we're done
*self.terminate.borrow_mut() = true;
}
} }
Ok(())
} }
} }