1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 15:04:36 +03:00

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.
This commit is contained in:
Wez Furlong 2021-07-10 17:28:21 -07:00
parent ad19b4f57b
commit db6da81272
13 changed files with 363 additions and 428 deletions

View File

@ -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<F>(subscriber: F)
pub fn subscribe_to_config_reload<F>(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<F>(&self, subscriber: F)
fn subscribe<F>(&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) {

View File

@ -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<dyn Pane>,
clipboard: ClipboardPasteSource,
) {
pub fn paste_from_clipboard(&mut self, pane: &Rc<dyn Pane>, 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();
}
}

View File

@ -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

View File

@ -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<LruCache<ShapeCacheKey, anyhow::Result<Rc<Vec<ShapedInfo<SrgbTexture2d>>>>>>,
last_blink_paint: Instant,
next_blink_paint: RefCell<Instant>,
last_status_call: Instant,
palette: Option<ColorPalette>,
event_states: HashMap<String, EventState>,
has_animation: RefCell<Option<Instant>>,
scheduled_animation: RefCell<Option<Instant>>,
gl: Option<Rc<glium::backend::Context>>,
config_subscription: Option<config::ConfigSubscription>,
}
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::<Option<WindowEvent>>::Ok(Some(event))
};
let wakeup = async {
Timer::at(sleep_interval.unwrap_or(now + Duration::from_secs(86400))).await;
anyhow::Result::<Option<WindowEvent>>::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<WindowEvent>) {
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<glium::backend::Context>,
) -> anyhow::Result<bool> {
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::<TermWindowNotif>() {
self.dispatch_notif(*notif, window)
.await
.context("dispatch_notif")?;
}
Ok(true)
@ -676,7 +583,12 @@ impl TermWindow {
}
}
fn do_paint(&mut self, gl: &Rc<glium::backend::Context>, 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<T>(e: smol::channel::SendError<T>) -> anyhow::Error {
fn dispatch_notif(&mut self, notif: TermWindowNotif, window: &Window) -> anyhow::Result<()> {
fn chan_err<T>(e: smol::channel::TrySendError<T>) -> 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<Instant>) {
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<Instant>) {
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<Instant>) {
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<dyn Pane>) -> 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<dyn Pane>) {
@ -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<dyn Pane>,
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)?;

View File

@ -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<dyn Pane>,
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;
}
}

View File

@ -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<Instant>) {
@ -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();

View File

@ -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<Window>,
gl: Option<Rc<glium::backend::Context>>,
}
impl Drop for MyWindow {
@ -15,37 +18,20 @@ impl Drop for MyWindow {
}
}
async fn spawn_window() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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(())
}

View File

@ -98,8 +98,27 @@ pub enum WindowEvent {
Notification(Box<dyn Any + Send + Sync>),
}
pub type WindowEventSender = async_channel::Sender<WindowEvent>;
pub type WindowEventReceiver = async_channel::Receiver<WindowEvent>;
pub struct WindowEventSender {
handler: Box<dyn FnMut(WindowEvent, &Window)>,
window: Option<Window>,
}
impl WindowEventSender {
pub fn new<F: 'static + FnMut(WindowEvent, &Window)>(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")]

View File

@ -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) {

View File

@ -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<F>(
class_name: &str,
name: &str,
width: usize,
height: usize,
config: Option<&ConfigHandle>,
font_config: Rc<FontConfiguration>,
) -> anyhow::Result<(Window, WindowEventReceiver)> {
event_handler: F,
) -> anyhow::Result<Window>
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<String> {
impl WaylandWindowInner {
fn close(&mut self) {
self.events.try_send(WindowEvent::Destroyed).ok();
self.events.dispatch(WindowEvent::Destroyed);
self.window.take();
}

View File

@ -380,7 +380,11 @@ impl Window {
height: usize,
config: Option<&ConfigHandle>,
_font_config: Rc<FontConfiguration>,
) -> anyhow::Result<(Window, WindowEventReceiver)> {
event_handler: F,
) -> anyhow::Result<Window>
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) {

View File

@ -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<Promise<Dimensions>>,
}
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<F>(
class_name: &str,
name: &str,
width: usize,
height: usize,
config: Option<&ConfigHandle>,
_font_config: Rc<FontConfiguration>,
) -> anyhow::Result<(Window, WindowEventReceiver)> {
event_handler: F,
) -> anyhow::Result<Window>
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) {

View File

@ -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<F>(
&self,
class_name: &str,
name: &str,
@ -54,15 +54,36 @@ impl Connection {
height: usize,
config: Option<&ConfigHandle>,
font_config: Rc<FontConfiguration>,
) -> anyhow::Result<(Window, WindowEventReceiver)> {
event_handler: F,
) -> anyhow::Result<Window>
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<F>(
class_name: &str,
name: &str,
width: usize,
height: usize,
config: Option<&ConfigHandle>,
font_config: Rc<FontConfiguration>,
) -> anyhow::Result<(Window, WindowEventReceiver)> {
event_handler: F,
) -> anyhow::Result<Window>
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
}
}