From db6da81272285fc91a3a326f277f22c3c31517b5 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sat, 10 Jul 2021 17:28:21 -0700 Subject: [PATCH] window: move away from async event queue I added this originally thinking that it would make it easier to resolve https://github.com/wez/wezterm/issues/695 and to integrate wgpu support, but it's the cause of https://github.com/wez/wezterm/issues/922 so let's take it out and more directly connect the window events to those in the terminal. This commit likely breaks mac and windows; pushing it so that I can check it out and verify on those systems. --- config/src/lib.rs | 9 +- wezterm-gui/src/termwindow/clipboard.rs | 27 +-- wezterm-gui/src/termwindow/keyevent.rs | 8 +- wezterm-gui/src/termwindow/mod.rs | 286 ++++++----------------- wezterm-gui/src/termwindow/mouseevent.rs | 9 +- wezterm-gui/src/termwindow/render.rs | 45 +++- window/examples/async.rs | 113 +++++---- window/src/lib.rs | 23 +- window/src/os/macos/window.rs | 24 +- window/src/os/wayland/window.rs | 78 +++---- window/src/os/windows/window.rs | 26 +-- window/src/os/x11/window.rs | 92 ++++---- window/src/os/x_and_wayland.rs | 51 +++- 13 files changed, 363 insertions(+), 428 deletions(-) diff --git a/config/src/lib.rs b/config/src/lib.rs index d5c8d843a..050ed2621 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -142,6 +142,7 @@ pub fn designate_this_as_the_main_thread() { }); } +#[must_use = "Cancels the subscription when dropped"] pub struct ConfigSubscription(usize); impl Drop for ConfigSubscription { @@ -150,11 +151,11 @@ impl Drop for ConfigSubscription { } } -pub fn subscribe_to_config_reload(subscriber: F) +pub fn subscribe_to_config_reload(subscriber: F) -> ConfigSubscription where F: Fn() -> bool + 'static + Send, { - CONFIG.subscribe(subscriber); + ConfigSubscription(CONFIG.subscribe(subscriber)) } /// Spawn a future that will run with an optional Lua state from the most @@ -561,12 +562,12 @@ impl Configuration { } /// Subscribe to config reload events - fn subscribe(&self, subscriber: F) + fn subscribe(&self, subscriber: F) -> usize where F: Fn() -> bool + 'static + Send, { let mut inner = self.inner.lock().unwrap(); - inner.subscribe(subscriber); + inner.subscribe(subscriber) } fn unsub(&self, sub_id: usize) { diff --git a/wezterm-gui/src/termwindow/clipboard.rs b/wezterm-gui/src/termwindow/clipboard.rs index 48a7bdbaa..e86422dde 100644 --- a/wezterm-gui/src/termwindow/clipboard.rs +++ b/wezterm-gui/src/termwindow/clipboard.rs @@ -1,3 +1,4 @@ +use crate::termwindow::TermWindowNotif; use crate::TermWindow; use config::keyassignment::{ClipboardCopyDestination, ClipboardPasteSource}; use mux::pane::Pane; @@ -88,11 +89,7 @@ impl TermWindow { } } - pub async fn paste_from_clipboard( - &mut self, - pane: &Rc, - clipboard: ClipboardPasteSource, - ) { + pub fn paste_from_clipboard(&mut self, pane: &Rc, clipboard: ClipboardPasteSource) { let pane_id = pane.pane_id(); let window = self.window.as_ref().unwrap().clone(); let clipboard = match clipboard { @@ -100,14 +97,18 @@ impl TermWindow { ClipboardPasteSource::PrimarySelection => Clipboard::PrimarySelection, }; let future = window.get_clipboard(clipboard); - - if let Ok(clip) = future.await { - if let Some(pane) = self.pane_state(pane_id).overlay.clone().or_else(|| { - let mux = Mux::get().unwrap(); - mux.get_pane(pane_id) - }) { - pane.trickle_paste(clip).ok(); + promise::spawn::spawn(async move { + if let Ok(clip) = future.await { + window.notify(TermWindowNotif::Apply(Box::new(move |myself| { + if let Some(pane) = myself.pane_state(pane_id).overlay.clone().or_else(|| { + let mux = Mux::get().unwrap(); + mux.get_pane(pane_id) + }) { + pane.trickle_paste(clip).ok(); + } + }))); } - } + }) + .detach(); } } diff --git a/wezterm-gui/src/termwindow/keyevent.rs b/wezterm-gui/src/termwindow/keyevent.rs index 7427e708d..4578e0a8d 100644 --- a/wezterm-gui/src/termwindow/keyevent.rs +++ b/wezterm-gui/src/termwindow/keyevent.rs @@ -34,7 +34,7 @@ pub enum Key { } impl super::TermWindow { - pub async fn key_event_impl(&mut self, window_key: KeyEvent, context: &dyn WindowOps) -> bool { + pub fn key_event_impl(&mut self, window_key: KeyEvent, context: &dyn WindowOps) -> bool { if !window_key.key_is_down { return false; } @@ -95,7 +95,7 @@ impl super::TermWindow { .input_map .lookup_key(&raw_code_key, window_key.raw_modifiers | leader_mod) { - self.perform_key_assignment(&pane, &assignment).await.ok(); + self.perform_key_assignment(&pane, &assignment).ok(); context.invalidate(); if leader_active { @@ -124,7 +124,7 @@ impl super::TermWindow { .input_map .lookup_key(key, window_key.raw_modifiers | leader_mod) { - self.perform_key_assignment(&pane, &assignment).await.ok(); + self.perform_key_assignment(&pane, &assignment).ok(); context.invalidate(); if leader_active { @@ -189,7 +189,7 @@ impl super::TermWindow { .input_map .lookup_key(&window_key.key, window_key.modifiers | leader_mod) { - self.perform_key_assignment(&pane, &assignment).await.ok(); + self.perform_key_assignment(&pane, &assignment).ok(); context.invalidate(); if leader_active { // A successful leader key-lookup cancels the leader diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index b8303116d..de6e198e5 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -34,7 +34,6 @@ use mux::{Mux, MuxNotification}; use portable_pty::PtySize; use serde::*; use smol::channel::Sender; -use smol::future::FutureExt; use smol::Timer; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; @@ -220,13 +219,17 @@ pub struct TermWindow { shape_cache: RefCell>>>>>, - last_blink_paint: Instant, + next_blink_paint: RefCell, last_status_call: Instant, palette: Option, event_states: HashMap, has_animation: RefCell>, + scheduled_animation: RefCell>, + + gl: Option>, + config_subscription: Option, } impl TermWindow { @@ -271,7 +274,7 @@ impl TermWindow { } } - fn focus_changed(&mut self, focused: bool) { + fn focus_changed(&mut self, focused: bool, window: &Window) { log::trace!("Setting focus to {:?}", focused); self.focused = if focused { Some(Instant::now()) } else { None }; @@ -284,7 +287,7 @@ impl TermWindow { self.prev_cursor.bump(); // force cursor to be repainted - self.window.as_ref().unwrap().invalidate(); + window.invalidate(); if let Some(pane) = self.get_active_pane_or_overlay() { pane.focus_changed(focused); @@ -445,7 +448,9 @@ impl TermWindow { let clipboard_contents = Arc::new(Mutex::new(None)); - let mut myself = Self { + let myself = Self { + config_subscription: None, + gl: None, window: None, window_background, config: config.clone(), @@ -484,19 +489,29 @@ impl TermWindow { "shape_cache.miss.rate", 65536, )), - last_blink_paint: Instant::now(), + next_blink_paint: RefCell::new(Instant::now()), last_status_call: Instant::now(), event_states: HashMap::new(), has_animation: RefCell::new(None), + scheduled_animation: RefCell::new(None), }; - let (window, events) = Window::new_window( + let tw = Rc::new(RefCell::new(myself)); + let tw_event = Rc::clone(&tw); + + let window = Window::new_window( &*WINDOW_CLASS.lock().unwrap(), "wezterm", dimensions.pixel_width, dimensions.pixel_height, Some(&config), Rc::clone(&fontconfig), + move |event, window| { + let mut tw = tw_event.borrow_mut(); + if let Err(err) = tw.dispatch_window_event(event, window) { + log::error!("dispatch_window_event: {:#}", err); + } + }, ) .await?; @@ -513,144 +528,37 @@ impl TermWindow { } }); - promise::spawn::spawn(async move { - let gl = window.enable_opengl().await?; + let gl = window.enable_opengl().await?; + { + let mut myself = tw.borrow_mut(); + myself.config_subscription.replace(config_subscription); + myself.gl.replace(Rc::clone(&gl)); myself.created(&window, Rc::clone(&gl))?; myself.subscribe_to_pane_updates(); myself.emit_status_event(); - - loop { - let mut need_invalidate = false; - let mut sleep_interval = None; - - let now = Instant::now(); - - for f in &[ - Self::maintain_status, - Self::maintain_animation, - Self::maintain_blink, - ] { - let (invalidate, next) = f(&mut myself, now); - if invalidate { - need_invalidate = true; - } - if let Some(next) = next { - let next = sleep_interval.unwrap_or(next).min(next); - sleep_interval.replace(next); - } - } - - if need_invalidate { - if !myself.do_paint(&gl, &window) { - break; - } - } - - let recv = async { - let event = events.recv().await?; - anyhow::Result::>::Ok(Some(event)) - }; - - let wakeup = async { - Timer::at(sleep_interval.unwrap_or(now + Duration::from_secs(86400))).await; - anyhow::Result::>::Ok(None) - }; - - match recv.or(wakeup).await { - Err(_) => break, - Ok(None) => {} - Ok(Some(event)) => { - let (event, peeked) = Self::coalesce_window_events(event, &events); - - match myself.dispatch_window_event(event, &window, &gl).await { - Ok(true) => {} - Ok(false) => break, - Err(err) => { - log::error!("{:#}", err); - break; - } - } - - if let Some(event) = peeked { - match myself.dispatch_window_event(event, &window, &gl).await { - Ok(true) => {} - Ok(false) => break, - Err(err) => { - log::error!("{:#}", err); - break; - } - } - } - } - } - } - - drop(config_subscription); - anyhow::Result::<()>::Ok(()) - }) - .detach(); + } crate::update::start_update_checker(); Ok(()) } - /// Collapse a series of Resized and NeedRepaint events into a single - /// Resized event, or a series of NeedRepaint into a single NeedRepaint - /// event. - /// Returns the coalesced event, and possibly a subsequent event of - /// some other type. - fn coalesce_window_events( - event: WindowEvent, - events: &WindowEventReceiver, - ) -> (WindowEvent, Option) { - if matches!( - &event, - WindowEvent::Resized { .. } | WindowEvent::NeedRepaint - ) { - let mut resize = if matches!(&event, WindowEvent::Resized { .. }) { - Some(event) - } else { - None - }; - let mut peek = None; - - while let Ok(next) = events.try_recv() { - match next { - WindowEvent::NeedRepaint => {} - e @ WindowEvent::Resized { .. } => { - resize.replace(e); - } - other => { - peek.replace(other); - break; - } - } - } - - (resize.unwrap_or(WindowEvent::NeedRepaint), peek) - } else { - (event, None) - } - } - - async fn dispatch_window_event( + fn dispatch_window_event( &mut self, event: WindowEvent, window: &Window, - gl: &Rc, ) -> anyhow::Result { match event { WindowEvent::Destroyed => Ok(false), WindowEvent::CloseRequested => { - self.close_requested(&window); + self.close_requested(window); Ok(true) } WindowEvent::FocusChanged(focused) => { - self.focus_changed(focused); + self.focus_changed(focused, window); Ok(true) } WindowEvent::MouseEvent(event) => { - self.mouse_event_impl(event, window).await; + self.mouse_event_impl(event, window); Ok(true) } WindowEvent::Resized { @@ -658,17 +566,16 @@ impl TermWindow { is_full_screen, } => { self.resize(dimensions, is_full_screen); - Ok(self.do_paint(&gl, window)) - } - WindowEvent::KeyEvent(event) => { - self.key_event_impl(event, window).await; Ok(true) } - WindowEvent::NeedRepaint => Ok(self.do_paint(&gl, window)), + WindowEvent::KeyEvent(event) => { + self.key_event_impl(event, window); + Ok(true) + } + WindowEvent::NeedRepaint => Ok(self.do_paint(window)), WindowEvent::Notification(item) => { if let Ok(notif) = item.downcast::() { self.dispatch_notif(*notif, window) - .await .context("dispatch_notif")?; } Ok(true) @@ -676,7 +583,12 @@ impl TermWindow { } } - fn do_paint(&mut self, gl: &Rc, window: &Window) -> bool { + fn do_paint(&mut self, window: &Window) -> bool { + let gl = match self.gl.as_ref() { + Some(gl) => gl, + None => return false, + }; + if gl.is_context_lost() { log::error!("opengl context was lost; should reinit"); window.close(); @@ -695,12 +607,8 @@ impl TermWindow { window.finish_frame(frame).is_ok() } - async fn dispatch_notif( - &mut self, - notif: TermWindowNotif, - window: &Window, - ) -> anyhow::Result<()> { - fn chan_err(e: smol::channel::SendError) -> anyhow::Error { + fn dispatch_notif(&mut self, notif: TermWindowNotif, window: &Window) -> anyhow::Result<()> { + fn chan_err(e: smol::channel::TrySendError) -> anyhow::Error { anyhow::anyhow!("{}", e) } @@ -718,24 +626,23 @@ impl TermWindow { .get_pane(pane_id) .ok_or_else(|| anyhow!("pane id {} is not valid", pane_id))?; self.perform_key_assignment(&pane, &assignment) - .await .context("perform_key_assignment")?; } TermWindowNotif::SetRightStatus(status) => { if status != self.right_status { self.right_status = status; self.update_title_post_status(); + } else { + self.schedule_next_status_update(); } } TermWindowNotif::GetDimensions(tx) => { - tx.send((self.dimensions, self.is_full_screen)) - .await + tx.try_send((self.dimensions, self.is_full_screen)) .map_err(chan_err) .context("send GetDimensions response")?; } TermWindowNotif::GetEffectiveConfig(tx) => { - tx.send(self.config.clone()) - .await + tx.try_send(self.config.clone()) .map_err(chan_err) .context("send GetEffectiveConfig response")?; } @@ -743,8 +650,7 @@ impl TermWindow { self.finish_window_event(&name, again); } TermWindowNotif::GetConfigOverrides(tx) => { - tx.send(self.config_overrides.clone()) - .await + tx.try_send(self.config_overrides.clone()) .map_err(chan_err) .context("send GetConfigOverrides response")?; } @@ -793,8 +699,7 @@ impl TermWindow { .get_pane(pane_id) .ok_or_else(|| anyhow!("pane id {} is not valid", pane_id))?; - tx.send(self.selection_text(&pane)) - .await + tx.try_send(self.selection_text(&pane)) .map_err(chan_err) .context("send GetSelectionForPane response")?; } @@ -1031,66 +936,6 @@ impl TermWindow { } } - fn maintain_status(&mut self, now: Instant) -> (bool, Option) { - let interval = Duration::from_millis(self.config.status_update_interval); - if now.duration_since(self.last_status_call) > interval { - self.last_status_call = now; - self.schedule_status_update(); - } - - (false, Some(self.last_status_call + interval)) - } - - /// If self.has_animation is some, then the last render detected - /// image attachments with multiple frames, so we also need to - /// invalidate the viewport when the next frame is due - fn maintain_animation(&mut self, now: Instant) -> (bool, Option) { - if self.focused.is_some() { - if let Some(next_due) = *self.has_animation.borrow() { - return (now >= next_due, Some(next_due)); - } - } - (false, None) - } - - /// If blinking is permitted, and the cursor shape is set - /// to a blinking variant, and it's been longer than the - /// blink rate interval, then invalidate and redraw - /// so that we will re-evaluate the cursor visibility. - /// This is pretty heavyweight: it would be nice to only invalidate - /// the line on which the cursor resides, and then only if the cursor - /// is within the viewport. - fn maintain_blink(&mut self, now: Instant) -> (bool, Option) { - if self.focused.is_none() || self.config.cursor_blink_rate == 0 { - return (false, None); - } - - let panes = self.get_panes_to_render(); - if panes.is_empty() { - self.window.as_ref().unwrap().close(); - return (false, None); - } - - for pos in panes { - if pos.is_active { - let shape = self - .config - .default_cursor_style - .effective_shape(pos.pane.get_cursor_position().shape); - if shape.is_blinking() { - let interval = Duration::from_millis(self.config.cursor_blink_rate); - if now.duration_since(self.last_blink_paint) > interval { - self.last_blink_paint = now; - return (true, Some(self.last_blink_paint + interval)); - } - return (false, Some(self.last_blink_paint + interval)); - } - } - } - - (false, None) - } - fn check_for_dirty_lines_and_invalidate_selection(&mut self, pane: &Rc) -> bool { let dims = pane.get_dimensions(); let viewport = self @@ -1347,6 +1192,25 @@ impl TermWindow { self.config_was_reloaded(); } } + self.schedule_next_status_update(); + } + + fn schedule_next_status_update(&mut self) { + if let Some(window) = self.window.as_ref() { + let now = Instant::now(); + if self.last_status_call <= now { + let interval = Duration::from_millis(self.config.status_update_interval); + let target = now + interval; + self.last_status_call = target; + + let window = window.clone(); + promise::spawn::spawn(async move { + Timer::at(target).await; + window.notify(TermWindowNotif::EmitStatusUpdate); + }) + .detach(); + } + } } fn update_text_cursor(&mut self, pane: &Rc) { @@ -1652,7 +1516,7 @@ impl TermWindow { self.move_tab(tab) } - pub async fn perform_key_assignment( + pub fn perform_key_assignment( &mut self, pane: &Rc, assignment: &KeyAssignment, @@ -1694,15 +1558,13 @@ impl TermWindow { self.copy_to_clipboard(*dest, text); } Paste => { - self.paste_from_clipboard(pane, ClipboardPasteSource::Clipboard) - .await; + self.paste_from_clipboard(pane, ClipboardPasteSource::Clipboard); } PastePrimarySelection => { - self.paste_from_clipboard(pane, ClipboardPasteSource::PrimarySelection) - .await; + self.paste_from_clipboard(pane, ClipboardPasteSource::PrimarySelection); } PasteFrom(source) => { - self.paste_from_clipboard(pane, *source).await; + self.paste_from_clipboard(pane, *source); } ActivateTabRelative(n) => { self.activate_tab_relative(*n)?; diff --git a/wezterm-gui/src/termwindow/mouseevent.rs b/wezterm-gui/src/termwindow/mouseevent.rs index 849f77275..a714e031f 100644 --- a/wezterm-gui/src/termwindow/mouseevent.rs +++ b/wezterm-gui/src/termwindow/mouseevent.rs @@ -18,7 +18,7 @@ use wezterm_term::input::MouseEventKind as TMEK; use wezterm_term::{LastMouseClick, StableRowIndex}; impl super::TermWindow { - pub async fn mouse_event_impl(&mut self, event: MouseEvent, context: &dyn WindowOps) { + pub fn mouse_event_impl(&mut self, event: MouseEvent, context: &dyn WindowOps) { let pane = match self.get_active_pane_or_overlay() { Some(pane) => pane, None => return, @@ -202,8 +202,7 @@ impl super::TermWindow { } else if in_scroll_bar { self.mouse_event_scroll_bar(pane, event, context); } else { - self.mouse_event_terminal(pane, x, term_y, event, context) - .await; + self.mouse_event_terminal(pane, x, term_y, event, context); } } @@ -296,7 +295,7 @@ impl super::TermWindow { context.set_cursor(Some(MouseCursor::Arrow)); } - pub async fn mouse_event_terminal( + pub fn mouse_event_terminal( &mut self, mut pane: Rc, mut x: usize, @@ -503,7 +502,7 @@ impl super::TermWindow { .input_map .lookup_mouse(event_trigger_type.clone(), modifiers) { - self.perform_key_assignment(&pane, &action).await.ok(); + self.perform_key_assignment(&pane, &action).ok(); return; } } diff --git a/wezterm-gui/src/termwindow/render.rs b/wezterm-gui/src/termwindow/render.rs index e01e07864..348f98cd1 100644 --- a/wezterm-gui/src/termwindow/render.rs +++ b/wezterm-gui/src/termwindow/render.rs @@ -17,8 +17,10 @@ use config::{ConfigHandle, HsbTransform, TextStyle}; use mux::pane::Pane; use mux::renderable::{RenderableDimensions, StableCursorPosition}; use mux::tab::{PositionedPane, PositionedSplit, SplitDirection}; +use smol::Timer; use std::ops::Range; use std::rc::Rc; +use std::time::Duration; use std::time::Instant; use termwiz::cellcluster::CellCluster; use termwiz::surface::{CursorShape, CursorVisibility}; @@ -138,6 +140,23 @@ impl super::TermWindow { metrics::histogram!("gui.paint.opengl", start.elapsed()); metrics::histogram!("gui.paint.opengl.rate", 1.); self.update_title_post_status(); + + // If self.has_animation is some, then the last render detected + // image attachments with multiple frames, so we also need to + // invalidate the viewport when the next frame is due + if self.focused.is_some() { + if let Some(next_due) = *self.has_animation.borrow() { + if Some(next_due) != *self.scheduled_animation.borrow() { + self.scheduled_animation.borrow_mut().replace(next_due); + let window = self.window.clone().take().unwrap(); + promise::spawn::spawn(async move { + Timer::at(next_due).await; + window.invalidate(); + }) + .detach(); + } + } + } } fn update_next_frame_time(&self, next_due: Option) { @@ -1133,10 +1152,34 @@ impl super::TermWindow { && params.config.cursor_blink_rate != 0 && self.focused.is_some(); if blinking { + let now = std::time::Instant::now(); + + // schedule an invalidation so that we can paint the next + // cycle at the right time. + if let Some(window) = self.window.clone() { + let interval = Duration::from_millis(params.config.cursor_blink_rate); + let next = *self.next_blink_paint.borrow(); + if next < now { + let target = next + interval; + let target = if target <= now { + now + interval + } else { + target + }; + + *self.next_blink_paint.borrow_mut() = target; + promise::spawn::spawn(async move { + Timer::at(target).await; + window.invalidate(); + }) + .detach(); + } + } + // Divide the time since we last moved by the blink rate. // If the result is even then the cursor is "on", else it // is "off" - let now = std::time::Instant::now(); + let milli_uptime = now .duration_since(self.prev_cursor.last_cursor_movement()) .as_millis(); diff --git a/window/examples/async.rs b/window/examples/async.rs index 4c8311ae4..b714f18c1 100644 --- a/window/examples/async.rs +++ b/window/examples/async.rs @@ -1,5 +1,6 @@ use ::window::*; use promise::spawn::spawn; +use std::cell::RefCell; use std::rc::Rc; use wezterm_font::FontConfiguration; @@ -7,6 +8,8 @@ struct MyWindow { allow_close: bool, cursor_pos: Point, dims: Dimensions, + win: Option, + gl: Option>, } impl Drop for MyWindow { @@ -15,37 +18,20 @@ impl Drop for MyWindow { } } -async fn spawn_window() -> Result<(), Box> { - let fontconfig = Rc::new(FontConfiguration::new( - None, - ::window::default_dpi() as usize, - )?); - let (win, events) = - Window::new_window("myclass", "the title", 800, 600, None, fontconfig).await?; +impl MyWindow { + fn dispatch(&mut self, event: WindowEvent) { + let win = match self.win.as_ref() { + Some(win) => win, + None => return, + }; - let mut state = MyWindow { - allow_close: false, - cursor_pos: Point::new(100, 200), - dims: Dimensions { - pixel_width: 800, - pixel_height: 600, - dpi: 0, - }, - }; - - eprintln!("before show"); - win.show().await?; - let gl = win.enable_opengl().await?; - eprintln!("window is visible, do loop"); - - while let Ok(event) = events.recv().await { match dbg!(event) { WindowEvent::CloseRequested => { eprintln!("can I close?"); - if state.allow_close { + if self.allow_close { win.close(); } else { - state.allow_close = true; + self.allow_close = true; } } WindowEvent::Destroyed => { @@ -57,11 +43,11 @@ async fn spawn_window() -> Result<(), Box> { is_full_screen, } => { eprintln!("resize {:?} is_full_screen={}", dimensions, is_full_screen); - state.dims = dimensions; + self.dims = dimensions; } WindowEvent::MouseEvent(event) => { - state.cursor_pos = event.coords; - win.invalidate(); + self.cursor_pos = event.coords; + // win.invalidate(); win.set_cursor(Some(MouseCursor::Arrow)); if event.kind == MouseEventKind::Press(MousePress::Left) { @@ -74,27 +60,68 @@ async fn spawn_window() -> Result<(), Box> { win.default_key_processing(key); } WindowEvent::NeedRepaint => { - if gl.is_context_lost() { - eprintln!("opengl context was lost; should reinit"); - break; + if let Some(gl) = self.gl.as_mut() { + if gl.is_context_lost() { + eprintln!("opengl context was lost; should reinit"); + return; + } + + let mut frame = glium::Frame::new( + Rc::clone(&gl), + (self.dims.pixel_width as u32, self.dims.pixel_height as u32), + ); + + use glium::Surface; + frame.clear_color_srgb(0.25, 0.125, 0.375, 1.0); + win.finish_frame(frame).unwrap(); } - - let mut frame = glium::Frame::new( - Rc::clone(&gl), - ( - state.dims.pixel_width as u32, - state.dims.pixel_height as u32, - ), - ); - - use glium::Surface; - frame.clear_color_srgb(0.25, 0.125, 0.375, 1.0); - win.finish_frame(frame)?; } WindowEvent::Notification(_) | WindowEvent::FocusChanged(_) => {} } } +} +async fn spawn_window() -> Result<(), Box> { + let fontconfig = Rc::new(FontConfiguration::new( + None, + ::window::default_dpi() as usize, + )?); + + let state = Rc::new(RefCell::new(MyWindow { + allow_close: false, + cursor_pos: Point::new(100, 200), + dims: Dimensions { + pixel_width: 800, + pixel_height: 600, + dpi: 0, + }, + win: None, + gl: None, + })); + + let cb_state = Rc::clone(&state); + let win = Window::new_window( + "myclass", + "the title", + 800, + 600, + None, + fontconfig, + move |event| { + let mut state = cb_state.borrow_mut(); + state.dispatch(event) + }, + ) + .await?; + + state.borrow_mut().win.replace(win.clone()); + + eprintln!("before show"); + win.show(); + let gl = win.enable_opengl().await?; + + state.borrow_mut().gl.replace(gl); + win.invalidate(); Ok(()) } diff --git a/window/src/lib.rs b/window/src/lib.rs index 24bd89eb6..f15594150 100644 --- a/window/src/lib.rs +++ b/window/src/lib.rs @@ -98,8 +98,27 @@ pub enum WindowEvent { Notification(Box), } -pub type WindowEventSender = async_channel::Sender; -pub type WindowEventReceiver = async_channel::Receiver; +pub struct WindowEventSender { + handler: Box, + window: Option, +} + +impl WindowEventSender { + pub fn new(handler: F) -> Self { + Self { + handler: Box::new(handler), + window: None, + } + } + + pub(crate) fn assign_window(&mut self, window: Window) { + self.window.replace(window); + } + + pub fn dispatch(&mut self, event: WindowEvent) { + (self.handler)(event, self.window.as_ref().unwrap()); + } +} #[derive(Debug, Error)] #[error("Graphics drivers lost context")] diff --git a/window/src/os/macos/window.rs b/window/src/os/macos/window.rs index e4267849a..88102fc63 100644 --- a/window/src/os/macos/window.rs +++ b/window/src/os/macos/window.rs @@ -531,13 +531,7 @@ impl WindowOps for Window { where Self: Sized, { - // If we're already on the correct thread, just queue it up - if let Some(conn) = Connection::get() { - let handle = match conn.window_by_id(self.0) { - Some(h) => h, - None => return, - }; - let inner = handle.borrow(); + Connection::with_window_inner(self.0, move |inner| { if let Some(window_view) = WindowView::get_this(unsafe { &**inner.view }) { window_view .inner @@ -546,20 +540,8 @@ impl WindowOps for Window { .try_send(WindowEvent::Notification(Box::new(t))) .ok(); } - } else { - // Otherwise, get into that thread and write to the queue - Connection::with_window_inner(self.0, move |inner| { - if let Some(window_view) = WindowView::get_this(unsafe { &**inner.view }) { - window_view - .inner - .borrow() - .events - .try_send(WindowEvent::Notification(Box::new(t))) - .ok(); - } - Ok(()) - }); - } + Ok(()) + }); } fn close(&self) { diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 6ba6edb58..111006dc3 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -6,7 +6,7 @@ use crate::os::wayland::connection::WaylandConnection; use crate::os::x11::keyboard::Keyboard; use crate::{ Clipboard, Connection, Dimensions, MouseCursor, Point, ScreenPoint, Window, WindowEvent, - WindowEventReceiver, WindowEventSender, WindowOps, + WindowEventSender, WindowOps, }; use anyhow::{anyhow, bail, Context}; use async_io::Timer; @@ -65,7 +65,7 @@ impl KeyRepeatState { } }; - let inner = handle.borrow(); + let mut inner = handle.borrow_mut(); if inner.key_repeat.as_ref().map(|k| Arc::as_ptr(k)) != Some(Arc::as_ptr(&state)) @@ -93,7 +93,7 @@ impl KeyRepeatState { event.repeat_count += 1; elapsed -= gap; } - inner.events.try_send(WindowEvent::KeyEvent(event)).ok(); + inner.events.dispatch(WindowEvent::KeyEvent(event)); st.when = Instant::now(); } @@ -189,14 +189,18 @@ impl PendingEvent { pub struct WaylandWindow(usize); impl WaylandWindow { - pub async fn new_window( + pub async fn new_window( class_name: &str, name: &str, width: usize, height: usize, config: Option<&ConfigHandle>, font_config: Rc, - ) -> anyhow::Result<(Window, WindowEventReceiver)> { + event_handler: F, + ) -> anyhow::Result + where + F: 'static + FnMut(WindowEvent, &Window), + { let conn = WaylandConnection::get() .ok_or_else(|| { anyhow!( @@ -207,7 +211,6 @@ impl WaylandWindow { let window_id = conn.next_window_id(); let pending_event = Arc::new(Mutex::new(PendingEvent::default())); - let (events, receiver) = async_channel::unbounded(); let (pending_first_configure, wait_configure) = async_channel::bounded(1); @@ -285,7 +288,7 @@ impl WaylandWindow { window_id, key_repeat: None, copy_and_paste, - events, + events: WindowEventSender::new(event_handler), surface: surface.detach(), window: Some(window), dimensions, @@ -301,12 +304,16 @@ impl WaylandWindow { })); let window_handle = Window::Wayland(WaylandWindow(window_id)); + inner + .borrow_mut() + .events + .assign_window(window_handle.clone()); conn.windows.borrow_mut().insert(window_id, inner.clone()); wait_configure.recv().await?; - Ok((window_handle, receiver)) + Ok(window_handle) } } @@ -355,7 +362,7 @@ impl WaylandWindowInner { } else { self.key_repeat.take(); } - self.events.try_send(WindowEvent::KeyEvent(event)).ok(); + self.events.dispatch(WindowEvent::KeyEvent(event)); } else { self.key_repeat.take(); } @@ -383,9 +390,7 @@ impl WaylandWindowInner { self.modifiers = Modifiers::NONE; mapper.update_modifier_state(0, 0, 0, 0); self.key_repeat.take(); - self.events - .try_send(WindowEvent::FocusChanged(focused)) - .ok(); + self.events.dispatch(WindowEvent::FocusChanged(focused)); } pub(crate) fn dispatch_pending_mouse(&mut self) { @@ -408,7 +413,7 @@ impl WaylandWindowInner { mouse_buttons: self.mouse_buttons, modifiers: self.modifiers, }; - self.events.try_send(WindowEvent::MouseEvent(event)).ok(); + self.events.dispatch(WindowEvent::MouseEvent(event)); self.refresh_frame(); } @@ -439,7 +444,7 @@ impl WaylandWindowInner { mouse_buttons: self.mouse_buttons, modifiers: self.modifiers, }; - self.events.try_send(WindowEvent::MouseEvent(event)).ok(); + self.events.dispatch(WindowEvent::MouseEvent(event)); } if let Some((value_x, value_y)) = PendingMouse::scroll(&pending_mouse) { @@ -456,7 +461,7 @@ impl WaylandWindowInner { mouse_buttons: self.mouse_buttons, modifiers: self.modifiers, }; - self.events.try_send(WindowEvent::MouseEvent(event)).ok(); + self.events.dispatch(WindowEvent::MouseEvent(event)); } let discrete_y = value_y.trunc() * factor; @@ -471,7 +476,7 @@ impl WaylandWindowInner { mouse_buttons: self.mouse_buttons, modifiers: self.modifiers, }; - self.events.try_send(WindowEvent::MouseEvent(event)).ok(); + self.events.dispatch(WindowEvent::MouseEvent(event)); } } } @@ -499,9 +504,7 @@ impl WaylandWindowInner { *pending_events = PendingEvent::default(); } if pending.close { - if self.events.try_send(WindowEvent::CloseRequested).is_err() { - self.window.take(); - } + self.events.dispatch(WindowEvent::CloseRequested); } if let Some(full_screen) = pending.full_screen.take() { @@ -549,12 +552,10 @@ impl WaylandWindowInner { if new_dimensions != self.dimensions { self.dimensions = new_dimensions; - self.events - .try_send(WindowEvent::Resized { - dimensions: self.dimensions, - is_full_screen: self.full_screen, - }) - .ok(); + self.events.dispatch(WindowEvent::Resized { + dimensions: self.dimensions, + is_full_screen: self.full_screen, + }); if let Some(wegl_surface) = self.wegl_surface.as_mut() { wegl_surface.resize(pixel_width, pixel_height, 0, 0); } @@ -629,7 +630,7 @@ impl WaylandWindowInner { } fn do_paint(&mut self) -> anyhow::Result<()> { - self.events.try_send(WindowEvent::NeedRepaint).ok(); + self.events.dispatch(WindowEvent::NeedRepaint); Ok(()) } } @@ -675,27 +676,12 @@ impl WindowOps for WaylandWindow { where Self: Sized, { - // If we're already on the correct thread, just queue it up - if let Some(conn) = Connection::get() { - let handle = match conn.wayland().window_by_id(self.0) { - Some(h) => h, - None => return, - }; - let inner = handle.borrow(); + WaylandConnection::with_window_inner(self.0, move |inner| { inner .events - .try_send(WindowEvent::Notification(Box::new(t))) - .ok(); - } else { - // Otherwise, get into that thread and write to the queue - WaylandConnection::with_window_inner(self.0, move |inner| { - inner - .events - .try_send(WindowEvent::Notification(Box::new(t))) - .ok(); - Ok(()) - }); - } + .dispatch(WindowEvent::Notification(Box::new(t))); + Ok(()) + }); } fn close(&self) { @@ -876,7 +862,7 @@ fn read_pipe_with_timeout(mut file: FileDescriptor) -> anyhow::Result { impl WaylandWindowInner { fn close(&mut self) { - self.events.try_send(WindowEvent::Destroyed).ok(); + self.events.dispatch(WindowEvent::Destroyed); self.window.take(); } diff --git a/window/src/os/windows/window.rs b/window/src/os/windows/window.rs index 8aad83444..bcf6b575d 100644 --- a/window/src/os/windows/window.rs +++ b/window/src/os/windows/window.rs @@ -380,7 +380,11 @@ impl Window { height: usize, config: Option<&ConfigHandle>, _font_config: Rc, - ) -> anyhow::Result<(Window, WindowEventReceiver)> { + event_handler: F, + ) -> anyhow::Result + where + F: 'static + FnMut(WindowEvent), + { let (events, receiver) = async_channel::unbounded(); let config = match config { Some(c) => c.clone(), @@ -580,27 +584,13 @@ impl WindowOps for Window { where Self: Sized, { - // If we're already on the correct thread, just queue it up - if let Some(conn) = Connection::get() { - let handle = match conn.get_window(self.0) { - Some(h) => h, - None => return, - }; - let inner = handle.borrow_mut(); + Connection::with_window_inner(self.0, move |inner| { inner .events .try_send(WindowEvent::Notification(Box::new(t))) .ok(); - } else { - // Otherwise, get into that thread and write to the queue - Connection::with_window_inner(self.0, move |inner| { - inner - .events - .try_send(WindowEvent::Notification(Box::new(t))) - .ok(); - Ok(()) - }); - } + Ok(()) + }); } fn close(&self) { diff --git a/window/src/os/x11/window.rs b/window/src/os/x11/window.rs index 5eaba135b..baa8329af 100644 --- a/window/src/os/x11/window.rs +++ b/window/src/os/x11/window.rs @@ -5,8 +5,7 @@ use crate::os::xkeysyms; use crate::os::{Connection, Window}; use crate::{ Clipboard, Dimensions, MouseButtons, MouseCursor, MouseEvent, MouseEventKind, MousePress, - Point, ScreenPoint, WindowDecorations, WindowEvent, WindowEventReceiver, WindowEventSender, - WindowOps, + Point, ScreenPoint, WindowDecorations, WindowEvent, WindowEventSender, WindowOps, }; use anyhow::{anyhow, Context as _}; use async_trait::async_trait; @@ -62,7 +61,6 @@ pub(crate) struct XWindowInner { cursors: CursorInfo, copy_and_paste: CopyAndPaste, config: ConfigHandle, - resize_promises: Vec>, } impl Drop for XWindowInner { @@ -123,11 +121,11 @@ impl XWindowInner { /// it to encompass both. This avoids bloating the list with a series /// of increasing rectangles when resizing larger or smaller. fn expose(&mut self, _x: u16, _y: u16, _width: u16, _height: u16) { - self.events.try_send(WindowEvent::NeedRepaint).ok(); + self.events.dispatch(WindowEvent::NeedRepaint); } fn do_mouse_event(&mut self, event: MouseEvent) -> anyhow::Result<()> { - self.events.try_send(WindowEvent::MouseEvent(event)).ok(); + self.events.dispatch(WindowEvent::MouseEvent(event)); Ok(()) } @@ -146,7 +144,7 @@ impl XWindowInner { self.dpi ); self.dpi = dpi; - let _ = self.events.try_send(WindowEvent::Resized { + self.events.dispatch(WindowEvent::Resized { dimensions: Dimensions { pixel_width: self.width as usize, pixel_height: self.height as usize, @@ -167,31 +165,37 @@ impl XWindowInner { } xcb::CONFIGURE_NOTIFY => { let cfg: &xcb::ConfigureNotifyEvent = unsafe { xcb::cast_event(event) }; - self.width = cfg.width(); - self.height = cfg.height(); - self.dpi = conn.default_dpi(); + let width = cfg.width(); + let height = cfg.height(); + let dpi = conn.default_dpi(); + + if width == self.width && height == self.height && dpi == self.dpi { + // Effectively unchanged; perhaps it was simply moved? + // Do nothing! + return Ok(()); + } + + self.width = width; + self.height = height; + self.dpi = dpi; + let dimensions = Dimensions { pixel_width: self.width as usize, pixel_height: self.height as usize, dpi: self.dpi as usize, }; - if !self.resize_promises.is_empty() { - self.resize_promises.remove(0).ok(dimensions); - } - self.events - .try_send(WindowEvent::Resized { - dimensions, - is_full_screen: self.is_fullscreen().unwrap_or(false), - }) - .ok(); + self.events.dispatch(WindowEvent::Resized { + dimensions, + is_full_screen: self.is_fullscreen().unwrap_or(false), + }); } xcb::KEY_PRESS | xcb::KEY_RELEASE => { let key_press: &xcb::KeyPressEvent = unsafe { xcb::cast_event(event) }; self.copy_and_paste.time = key_press.time(); if let Some(key) = conn.keyboard.process_key_event(key_press) { let key = key.normalize_shift(); - self.events.try_send(WindowEvent::KeyEvent(key)).ok(); + self.events.dispatch(WindowEvent::KeyEvent(key)); } } @@ -272,13 +276,11 @@ impl XWindowInner { let msg: &xcb::ClientMessageEvent = unsafe { xcb::cast_event(event) }; if msg.data().data32()[0] == conn.atom_delete() { - if self.events.try_send(WindowEvent::CloseRequested).is_err() { - xcb::destroy_window(conn.conn(), self.window_id); - } + self.events.dispatch(WindowEvent::CloseRequested); } } xcb::DESTROY_NOTIFY => { - self.events.try_send(WindowEvent::Destroyed).ok(); + self.events.dispatch(WindowEvent::Destroyed); conn.windows.borrow_mut().remove(&self.window_id); } xcb::SELECTION_CLEAR => { @@ -321,11 +323,11 @@ impl XWindowInner { } xcb::FOCUS_IN => { log::trace!("Calling focus_change(true)"); - self.events.try_send(WindowEvent::FocusChanged(true)).ok(); + self.events.dispatch(WindowEvent::FocusChanged(true)); } xcb::FOCUS_OUT => { log::trace!("Calling focus_change(false)"); - self.events.try_send(WindowEvent::FocusChanged(false)).ok(); + self.events.dispatch(WindowEvent::FocusChanged(false)); } _ => { eprintln!("unhandled: {:x}", r); @@ -645,14 +647,18 @@ impl XWindow { /// Create a new window on the specified screen with the specified /// dimensions - pub async fn new_window( + pub async fn new_window( class_name: &str, name: &str, width: usize, height: usize, config: Option<&ConfigHandle>, _font_config: Rc, - ) -> anyhow::Result<(Window, WindowEventReceiver)> { + event_handler: F, + ) -> anyhow::Result + where + F: 'static + FnMut(WindowEvent, &Window), + { let config = match config { Some(c) => c.clone(), None => config::configuration(), @@ -665,7 +671,7 @@ impl XWindow { })? .x11(); - let (events, receiver) = async_channel::unbounded(); + let mut events = WindowEventSender::new(event_handler); let window_id; let window = { @@ -727,6 +733,8 @@ impl XWindow { .request_check() .context("xcb::create_window_checked")?; + events.assign_window(Window::X11(XWindow::from_id(window_id))); + Arc::new(Mutex::new(XWindowInner { window_id, conn: Rc::downgrade(&conn), @@ -737,7 +745,6 @@ impl XWindow { copy_and_paste: CopyAndPaste::default(), cursors: CursorInfo::new(&conn), config: config.clone(), - resize_promises: vec![], })) }; @@ -768,7 +775,7 @@ impl XWindow { window_handle.set_title(name); window_handle.show(); - Ok((window_handle, receiver)) + Ok(window_handle) } } @@ -781,7 +788,7 @@ impl XWindowInner { xcb::map_window(self.conn().conn(), self.window_id); } fn invalidate(&mut self) { - self.events.try_send(WindowEvent::NeedRepaint).ok(); + self.events.dispatch(WindowEvent::NeedRepaint); } fn toggle_fullscreen(&mut self) { @@ -896,27 +903,12 @@ impl WindowOps for XWindow { where Self: Sized, { - // If we're already on the correct thread, just queue it up - if let Some(conn) = Connection::get() { - let handle = match conn.x11().window_by_id(self.0) { - Some(h) => h, - None => return, - }; - let inner = handle.lock().unwrap(); + XConnection::with_window_inner(self.0, move |inner| { inner .events - .try_send(WindowEvent::Notification(Box::new(t))) - .ok(); - } else { - // Otherwise, get into that thread and write to the queue - XConnection::with_window_inner(self.0, move |inner| { - inner - .events - .try_send(WindowEvent::Notification(Box::new(t))) - .ok(); - Ok(()) - }); - } + .dispatch(WindowEvent::Notification(Box::new(t))); + Ok(()) + }); } fn close(&self) { diff --git a/window/src/os/x_and_wayland.rs b/window/src/os/x_and_wayland.rs index 351043b5f..c6ff60c3c 100644 --- a/window/src/os/x_and_wayland.rs +++ b/window/src/os/x_and_wayland.rs @@ -7,7 +7,7 @@ use crate::os::wayland::connection::WaylandConnection; use crate::os::wayland::window::WaylandWindow; use crate::os::x11::connection::XConnection; use crate::os::x11::window::XWindow; -use crate::{Clipboard, MouseCursor, ScreenPoint, WindowEventReceiver, WindowOps}; +use crate::{Clipboard, MouseCursor, ScreenPoint, WindowEvent, WindowOps}; use async_trait::async_trait; use config::ConfigHandle; use promise::*; @@ -46,7 +46,7 @@ impl Connection { Ok(Connection::X11(Rc::new(XConnection::create_new()?))) } - pub async fn new_window( + pub async fn new_window( &self, class_name: &str, name: &str, @@ -54,15 +54,36 @@ impl Connection { height: usize, config: Option<&ConfigHandle>, font_config: Rc, - ) -> anyhow::Result<(Window, WindowEventReceiver)> { + event_handler: F, + ) -> anyhow::Result + where + F: 'static + FnMut(WindowEvent, &Window), + { match self { Self::X11(_) => { - XWindow::new_window(class_name, name, width, height, config, font_config).await + XWindow::new_window( + class_name, + name, + width, + height, + config, + font_config, + event_handler, + ) + .await } #[cfg(feature = "wayland")] Self::Wayland(_) => { - WaylandWindow::new_window(class_name, name, width, height, config, font_config) - .await + WaylandWindow::new_window( + class_name, + name, + width, + height, + config, + font_config, + event_handler, + ) + .await } } } @@ -103,17 +124,29 @@ impl ConnectionOps for Connection { } impl Window { - pub async fn new_window( + pub async fn new_window( class_name: &str, name: &str, width: usize, height: usize, config: Option<&ConfigHandle>, font_config: Rc, - ) -> anyhow::Result<(Window, WindowEventReceiver)> { + event_handler: F, + ) -> anyhow::Result + where + F: 'static + FnMut(WindowEvent, &Window), + { Connection::get() .unwrap() - .new_window(class_name, name, width, height, config, font_config) + .new_window( + class_name, + name, + width, + height, + config, + font_config, + event_handler, + ) .await } }