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

window: separate gui window state from app state

Removes the callbacks type and replaces event dispatch with
an async capable channel.

This makes it a bit simpler to model some of the window internals,
and to prepare for a wgpu enabled future.

This changes have been tested only on linux so far.
This commit is contained in:
Wez Furlong 2021-05-04 08:38:43 -07:00
parent f9a6e265e9
commit d56bfd0b7f
8 changed files with 264 additions and 303 deletions

2
Cargo.lock generated
View File

@ -4658,7 +4658,9 @@ name = "window"
version = "0.1.0"
dependencies = [
"anyhow",
"async-channel",
"async-task",
"async-trait",
"bitflags",
"cgl",
"clipboard",

View File

@ -15,7 +15,9 @@ pretty_env_logger = "0.4"
gl_generator = "0.14"
[dependencies]
async-channel = "1.6"
async-task = "4.0"
async-trait = "0.1"
anyhow = "1.0"
config = { path = "../config" }
thiserror = "1.0"

View File

@ -1,10 +1,11 @@
use ::window::*;
use promise::spawn::spawn;
use std::any::Any;
use std::rc::Rc;
struct MyWindow {
allow_close: bool,
cursor_pos: Point,
dims: Dimensions,
}
impl Drop for MyWindow {
@ -13,76 +14,81 @@ impl Drop for MyWindow {
}
}
impl WindowCallbacks for MyWindow {
fn can_close(&mut self) -> bool {
eprintln!("can I close?");
if self.allow_close {
true
} else {
self.allow_close = true;
false
}
}
fn destroy(&mut self) {
eprintln!("destroy was called!");
Connection::get().unwrap().terminate_message_loop();
}
fn resize(&mut self, dims: Dimensions, is_full_screen: bool) {
eprintln!("resize {:?} is_full_screen={}", dims, is_full_screen);
}
fn key_event(&mut self, key: &KeyEvent, ctx: &dyn WindowOps) -> bool {
eprintln!("{:?}", key);
ctx.set_cursor(Some(MouseCursor::Text));
false
}
fn mouse_event(&mut self, event: &MouseEvent, ctx: &dyn WindowOps) {
self.cursor_pos = event.coords;
ctx.invalidate();
ctx.set_cursor(Some(MouseCursor::Arrow));
if event.kind == MouseEventKind::Press(MousePress::Left) {
eprintln!("{:?}", event);
}
}
fn as_any(&mut self) -> &mut dyn Any {
self
}
}
async fn spawn_window() -> Result<(), Box<dyn std::error::Error>> {
let win = Window::new_window(
"myclass",
"the title",
800,
600,
Box::new(MyWindow {
allow_close: false,
cursor_pos: Point::new(100, 200),
}),
None,
)
.await?;
let (win, events) = Window::new_window("myclass", "the title", 800, 600, None).await?;
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?;
eprintln!("after show");
win.apply(|myself, _win| {
eprintln!("doing apply");
if let Some(myself) = myself.downcast_ref::<MyWindow>() {
eprintln!(
"got myself; allow_close={}, cursor_pos:{:?}",
myself.allow_close, myself.cursor_pos
);
let gl = win.enable_opengl().await?;
eprintln!("window is visible, do loop");
while let Ok(event) = events.recv().await {
match event {
WindowEvent::CloseRequested => {
eprintln!("can I close?");
if state.allow_close {
win.close();
} else {
state.allow_close = true;
}
}
WindowEvent::Destroyed => {
eprintln!("destroy was called!");
Connection::get().unwrap().terminate_message_loop();
}
WindowEvent::Resized {
dimensions,
is_full_screen,
} => {
eprintln!("resize {:?} is_full_screen={}", dimensions, is_full_screen);
state.dims = dimensions;
}
WindowEvent::MouseEvent(event) => {
state.cursor_pos = event.coords;
win.invalidate();
win.set_cursor(Some(MouseCursor::Arrow));
if event.kind == MouseEventKind::Press(MousePress::Left) {
eprintln!("{:?}", event);
}
}
WindowEvent::KeyEvent(key) => {
eprintln!("{:?}", key);
win.set_cursor(Some(MouseCursor::Text));
win.default_key_processing(key);
}
WindowEvent::NeedRepaint => {
if gl.is_context_lost() {
eprintln!("opengl context was lost; should reinit");
break;
}
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::FocusChanged(_) => {}
}
Ok(())
})
.await?;
eprintln!("done with spawn_window");
}
Ok(())
}
@ -90,7 +96,7 @@ fn main() -> anyhow::Result<()> {
let conn = Connection::init()?;
spawn(async {
eprintln!("running this async block");
spawn_window().await.ok();
dbg!(spawn_window().await).ok();
eprintln!("end of async block");
})
.detach();

View File

@ -1,5 +1,7 @@
use async_trait::async_trait;
use promise::Future;
use std::any::Any;
use std::rc::Rc;
use thiserror::Error;
pub mod bitmaps;
pub mod color;
mod configuration;
@ -59,70 +61,61 @@ pub enum MouseCursor {
SizeLeftRight,
}
#[allow(unused_variables)]
pub trait WindowCallbacks: Any {
#[derive(Debug, PartialEq, Eq)]
pub enum WindowEvent {
/// Called when the window close button is clicked.
/// Return true to allow the close to continue, false to
/// prevent it from closing.
fn can_close(&mut self) -> bool {
true
}
/// The window closure is deferred and this event is
/// sent to your application to decide whether it will
/// really close the window.
CloseRequested,
/// Called when the window is being destroyed by the gui system
fn destroy(&mut self) {}
/// Called when the window is being destroyed by the window system
Destroyed,
/// Called when the window is resized, or when the dpi has changed
fn resize(&mut self, dimensions: Dimensions, is_full_screen: bool) {}
/// Called when the window has been resized
Resized {
dimensions: Dimensions,
is_full_screen: bool,
},
/// Called when window gains/loses focus
fn focus_change(&mut self, focused: bool) {}
/// Called when the window has been invalidated and needs to
/// be repainted
NeedRepaint,
/// Called when the window has opengl mode enabled and the window
/// contents need painting.
fn paint(&mut self, frame: &mut glium::Frame) {
use glium::Surface;
frame.clear_color_srgb(0.25, 0.125, 0.375, 1.0);
}
/// Called if the opengl context is lost
fn opengl_context_lost(&mut self, _window: &dyn WindowOps) -> anyhow::Result<()> {
Ok(())
}
/// Called when the window gains/loses focus
FocusChanged(bool),
/// Called to handle a key event.
/// If your window didn't handle the event, you must return false.
/// This is particularly important for eg: ALT keys on windows,
/// otherwise standard key assignments may not function in your window.
fn key_event(&mut self, key: &KeyEvent, context: &dyn WindowOps) -> bool {
false
}
/// If you didn't handle this event, then you must call
/// window.default_key_processing(key) to allow the system to perform
/// the default key handling.
/// This is important on Windows for ALT keys to continue working
/// correctly.
KeyEvent(KeyEvent),
fn mouse_event(&mut self, event: &MouseEvent, context: &dyn WindowOps) {
context.set_cursor(Some(MouseCursor::Arrow));
}
/// Called when the window is created and allows the embedding
/// app to reference the window and operate upon it.
fn created(
&mut self,
_window: &Window,
_context: std::rc::Rc<glium::backend::Context>,
) -> anyhow::Result<()> {
Ok(())
}
/// An unfortunate bit of boilerplate; you need to provie an impl
/// of this method that returns `self` in order for the downcast_ref
/// method of the Any trait to be usable on WindowCallbacks.
/// https://stackoverflow.com/q/46045298/149111 and others have
/// some rationale on why Rust works this way.
fn as_any(&mut self) -> &mut dyn Any;
MouseEvent(MouseEvent),
}
pub type WindowEventSender = async_channel::Sender<WindowEvent>;
pub type WindowEventReceiver = async_channel::Receiver<WindowEvent>;
#[derive(Debug, Error)]
#[error("Graphics drivers lost context")]
pub struct GraphicsDriversLostContext {}
#[async_trait(?Send)]
pub trait WindowOps {
/// Show a hidden window
fn show(&self) -> Future<()>;
/// Setup opengl for rendering
async fn enable_opengl(&self) -> anyhow::Result<Rc<glium::backend::Context>>;
/// Advise the window that a frame is finished
fn finish_frame(&self, frame: glium::Frame) -> anyhow::Result<()> {
frame.finish()?;
Ok(())
}
/// Hide a visible window
fn hide(&self) -> Future<()>;
@ -139,6 +132,8 @@ pub trait WindowOps {
/// Change the titlebar text for the window
fn set_title(&self, title: &str) -> Future<()>;
fn default_key_processing(&self, _key: KeyEvent) {}
/// Resize the inner or client area of the window
fn set_inner_size(&self, width: usize, height: usize) -> Future<Dimensions>;
@ -156,18 +151,6 @@ pub trait WindowOps {
Future::ok(())
}
/// Schedule a callback on the data associated with the window.
/// The `Any` that is passed in corresponds to the WindowCallbacks
/// impl you passed to `new_window`, pre-converted to Any so that
/// you can `downcast_ref` or `downcast_mut` it and operate on it.
fn apply<R, F: Send + 'static + FnMut(&mut dyn Any, &dyn WindowOps) -> anyhow::Result<R>>(
&self,
func: F,
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static;
/// Initiate textual transfer from the clipboard
fn get_clipboard(&self, clipboard: Clipboard) -> Future<String>;

View File

@ -5,15 +5,15 @@ use crate::connection::ConnectionOps;
use crate::os::wayland::connection::WaylandConnection;
use crate::os::xkeysyms::keysym_to_keycode;
use crate::{
Clipboard, Connection, Dimensions, MouseCursor, Point, ScreenPoint, Window, WindowCallbacks,
WindowOps,
Clipboard, Connection, Dimensions, MouseCursor, Point, ScreenPoint, Window, WindowEvent,
WindowEventReceiver, WindowEventSender, WindowOps,
};
use anyhow::{anyhow, bail, Context};
use async_trait::async_trait;
use config::ConfigHandle;
use filedescriptor::FileDescriptor;
use promise::{Future, Promise};
use smithay_client_toolkit as toolkit;
use std::any::Any;
use std::cell::RefCell;
use std::convert::TryInto;
use std::io::{Read, Write};
@ -79,19 +79,18 @@ fn frame_config() -> ConceptConfig {
}
pub struct WaylandWindowInner {
window_id: usize,
callbacks: Box<dyn WindowCallbacks>,
events: WindowEventSender,
surface: WlSurface,
copy_and_paste: Arc<Mutex<CopyAndPaste>>,
window: Option<toolkit::window::Window<ConceptFrame>>,
dimensions: Dimensions,
need_paint: bool,
full_screen: bool,
last_mouse_coords: Point,
mouse_buttons: MouseButtons,
modifiers: Modifiers,
pending_event: Arc<Mutex<PendingEvent>>,
pending_mouse: Arc<Mutex<PendingMouse>>,
pending_first_configure: Option<async_channel::Sender<()>>,
// wegl_surface is listed before gl_state because it
// must be dropped before gl_state otherwise the underlying
// libraries will segfault on shutdown
@ -166,9 +165,8 @@ impl WaylandWindow {
name: &str,
width: usize,
height: usize,
callbacks: Box<dyn WindowCallbacks>,
_config: Option<&ConfigHandle>,
) -> anyhow::Result<Window> {
) -> anyhow::Result<(Window, WindowEventReceiver)> {
let conn = WaylandConnection::get()
.ok_or_else(|| {
anyhow!(
@ -179,6 +177,9 @@ 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);
let surface = conn
.environment
@ -247,18 +248,17 @@ impl WaylandWindow {
let inner = Rc::new(RefCell::new(WaylandWindowInner {
copy_and_paste,
window_id,
callbacks,
events,
surface: surface.detach(),
window: Some(window),
dimensions,
need_paint: true,
full_screen: false,
last_mouse_coords: Point::new(0, 0),
mouse_buttons: MouseButtons::NONE,
modifiers: Modifiers::NONE,
pending_event,
pending_mouse,
pending_first_configure: Some(pending_first_configure),
gl_state: None,
wegl_surface: None,
}));
@ -267,7 +267,9 @@ impl WaylandWindow {
conn.windows.borrow_mut().insert(window_id, inner.clone());
Ok(window_handle)
wait_configure.recv().await?;
Ok((window_handle, receiver))
}
}
@ -320,8 +322,7 @@ impl WaylandWindowInner {
repeat_count: 1,
}
.normalize_shift();
self.callbacks
.key_event(&key_event, &Window::Wayland(WaylandWindow(self.window_id)));
self.events.try_send(WindowEvent::KeyEvent(key_event)).ok();
}
KeyboardEvent::Modifiers { modifiers } => self.modifiers = modifiers,
// Clear the modifiers when we change focus, otherwise weird
@ -331,11 +332,11 @@ impl WaylandWindowInner {
// be left in a broken state.
KeyboardEvent::Enter { .. } => {
self.modifiers = Modifiers::NONE;
self.callbacks.focus_change(true)
self.events.try_send(WindowEvent::FocusChanged(true)).ok();
}
KeyboardEvent::Leave { .. } => {
self.modifiers = Modifiers::NONE;
self.callbacks.focus_change(false)
self.events.try_send(WindowEvent::FocusChanged(false)).ok();
}
}
}
@ -360,8 +361,7 @@ impl WaylandWindowInner {
mouse_buttons: self.mouse_buttons,
modifiers: self.modifiers,
};
self.callbacks
.mouse_event(&event, &Window::Wayland(WaylandWindow(self.window_id)));
self.events.try_send(WindowEvent::MouseEvent(event)).ok();
self.refresh_frame();
}
@ -391,8 +391,7 @@ impl WaylandWindowInner {
mouse_buttons: self.mouse_buttons,
modifiers: self.modifiers,
};
self.callbacks
.mouse_event(&event, &Window::Wayland(WaylandWindow(self.window_id)));
self.events.try_send(WindowEvent::MouseEvent(event)).ok();
}
if let Some((value_x, value_y)) = PendingMouse::scroll(&pending_mouse) {
@ -409,8 +408,7 @@ impl WaylandWindowInner {
mouse_buttons: self.mouse_buttons,
modifiers: self.modifiers,
};
self.callbacks
.mouse_event(&event, &Window::Wayland(WaylandWindow(self.window_id)));
self.events.try_send(WindowEvent::MouseEvent(event)).ok();
}
let discrete_y = value_y.trunc() * factor;
@ -425,8 +423,7 @@ impl WaylandWindowInner {
mouse_buttons: self.mouse_buttons,
modifiers: self.modifiers,
};
self.callbacks
.mouse_event(&event, &Window::Wayland(WaylandWindow(self.window_id)));
self.events.try_send(WindowEvent::MouseEvent(event)).ok();
}
}
}
@ -453,9 +450,10 @@ impl WaylandWindowInner {
pending = pending_events.clone();
*pending_events = PendingEvent::default();
}
if pending.close && self.callbacks.can_close() {
self.callbacks.destroy();
self.window.take();
if pending.close {
if self.events.try_send(WindowEvent::CloseRequested).is_err() {
self.window.take();
}
}
if let Some(full_screen) = pending.full_screen.take() {
@ -503,22 +501,29 @@ impl WaylandWindowInner {
if new_dimensions != self.dimensions {
self.dimensions = new_dimensions;
self.callbacks.resize(self.dimensions, self.full_screen);
self.events
.try_send(WindowEvent::Resized {
dimensions: self.dimensions,
is_full_screen: self.full_screen,
})
.ok();
if let Some(wegl_surface) = self.wegl_surface.as_mut() {
wegl_surface.resize(pixel_width, pixel_height, 0, 0);
}
}
self.refresh_frame();
self.need_paint = true;
self.do_paint().unwrap();
}
}
if pending.refresh_decorations && self.window.is_some() {
self.refresh_frame();
}
if pending.had_configure_event && self.window.is_some() && self.wegl_surface.is_none() {
self.enable_opengl().unwrap();
if pending.had_configure_event && self.window.is_some() {
if let Some(notify) = self.pending_first_configure.take() {
// Allow window creation to complete
notify.try_send(()).ok();
}
}
}
@ -529,8 +534,7 @@ impl WaylandWindowInner {
}
}
fn enable_opengl(&mut self) -> anyhow::Result<()> {
let window = Window::Wayland(WaylandWindow(self.window_id));
fn enable_opengl(&mut self) -> anyhow::Result<Rc<glium::backend::Context>> {
let wayland_conn = Connection::get().unwrap().wayland();
let mut wegl_surface = None;
@ -573,38 +577,39 @@ impl WaylandWindowInner {
self.gl_state.replace(gl_state.clone());
self.wegl_surface = wegl_surface;
self.callbacks.created(&window, gl_state)
Ok(gl_state)
}
fn do_paint(&mut self) -> anyhow::Result<()> {
if let Some(gl_context) = self.gl_state.as_ref() {
if gl_context.is_context_lost() {
log::error!("opengl context was lost; should reinit");
drop(self.gl_state.take());
self.enable_opengl()?;
return self.do_paint();
}
let mut frame = glium::Frame::new(
Rc::clone(&gl_context),
(
self.dimensions.pixel_width as u32,
self.dimensions.pixel_height as u32,
),
);
self.callbacks.paint(&mut frame);
frame.finish()?;
// self.damage();
self.refresh_frame();
self.need_paint = false;
}
self.events.try_send(WindowEvent::NeedRepaint)?;
Ok(())
}
}
#[async_trait(?Send)]
impl WindowOps for WaylandWindow {
async fn enable_opengl(&self) -> anyhow::Result<Rc<glium::backend::Context>> {
let window = self.0;
promise::spawn::spawn(async move {
if let Some(handle) = Connection::get().unwrap().wayland().window_by_id(window) {
let mut inner = handle.borrow_mut();
inner.enable_opengl()
} else {
anyhow::bail!("invalid window");
}
})
.await
}
fn finish_frame(&self, frame: glium::Frame) -> anyhow::Result<()> {
frame.finish()?;
WaylandConnection::with_window_inner(self.0, |inner| {
inner.refresh_frame();
Ok(())
});
Ok(())
}
fn close(&self) -> Future<()> {
WaylandConnection::with_window_inner(self.0, |inner| {
inner.close();
@ -668,20 +673,6 @@ impl WindowOps for WaylandWindow {
})
}
fn apply<R, F: Send + 'static + FnMut(&mut dyn Any, &dyn WindowOps) -> anyhow::Result<R>>(
&self,
mut func: F,
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
WaylandConnection::with_window_inner(self.0, move |inner| {
let window = Window::Wayland(WaylandWindow(inner.window_id));
func(inner.callbacks.as_any(), &window)
})
}
fn get_clipboard(&self, _clipboard: Clipboard) -> Future<String> {
let mut promise = Promise::new();
let future = promise.get_future().unwrap();
@ -797,7 +788,7 @@ fn read_pipe_with_timeout(mut file: FileDescriptor) -> anyhow::Result<String> {
impl WaylandWindowInner {
fn close(&mut self) {
self.callbacks.destroy();
self.events.try_send(WindowEvent::Destroyed).ok();
self.window.take();
}
@ -821,17 +812,9 @@ impl WaylandWindowInner {
if self.window.is_none() {
return;
}
let conn = Connection::get().unwrap().wayland();
if !conn
.environment
.borrow()
.get_shell()
.unwrap()
.needs_configure()
{
self.do_paint().unwrap();
}
// The window won't be visible until we've done our first paint,
// so we unconditionally queue a NeedRepaint event
self.do_paint().unwrap();
}
fn set_cursor(&mut self, cursor: Option<MouseCursor>) {
@ -848,7 +831,6 @@ impl WaylandWindowInner {
}
fn invalidate(&mut self) {
self.need_paint = true;
self.do_paint().unwrap();
}

View File

@ -292,7 +292,10 @@ impl XConnection {
Ok(())
}
fn window_by_id(&self, window_id: xcb::xproto::Window) -> Option<Arc<Mutex<XWindowInner>>> {
pub(crate) fn window_by_id(
&self,
window_id: xcb::xproto::Window,
) -> Option<Arc<Mutex<XWindowInner>>> {
self.windows.borrow().get(&window_id).map(Arc::clone)
}

View File

@ -5,12 +5,13 @@ use crate::os::xkeysyms;
use crate::os::{Connection, Window};
use crate::{
Clipboard, Dimensions, MouseButtons, MouseCursor, MouseEvent, MouseEventKind, MousePress,
Point, Rect, ScreenPoint, Size, WindowCallbacks, WindowDecorations, WindowOps,
Point, Rect, ScreenPoint, Size, WindowDecorations, WindowEvent, WindowEventReceiver,
WindowEventSender, WindowOps,
};
use anyhow::{anyhow, Context as _};
use async_trait::async_trait;
use config::ConfigHandle;
use promise::{Future, Promise};
use std::any::Any;
use std::collections::VecDeque;
use std::convert::TryInto;
use std::rc::{Rc, Weak};
@ -51,7 +52,7 @@ impl CopyAndPaste {
pub(crate) struct XWindowInner {
window_id: xcb::xproto::Window,
conn: Weak<XConnection>,
callbacks: Box<dyn WindowCallbacks>,
events: WindowEventSender,
width: u16,
height: u16,
dpi: f64,
@ -83,7 +84,7 @@ impl Drop for XWindowInner {
}
impl XWindowInner {
fn enable_opengl(&mut self) -> anyhow::Result<()> {
fn enable_opengl(&mut self) -> anyhow::Result<Rc<glium::backend::Context>> {
let conn = self.conn();
let gl_state = match conn.gl_connection.borrow().as_ref() {
@ -114,8 +115,7 @@ impl XWindowInner {
})?;
self.gl_state.replace(gl_state.clone());
let window_handle = Window::X11(XWindow::from_id(self.window_id));
self.callbacks.created(&window_handle, gl_state)
Ok(gl_state)
}
pub fn paint(&mut self) -> anyhow::Result<()> {
@ -124,24 +124,7 @@ impl XWindowInner {
}
self.paint_all = false;
self.expose.clear();
if let Some(gl_context) = self.gl_state.as_ref() {
if gl_context.is_context_lost() {
log::error!("opengl context was lost; should reinit");
drop(self.gl_state.take());
self.enable_opengl()?;
return self.paint();
}
let mut frame = glium::Frame::new(
Rc::clone(&gl_context),
(u32::from(self.width), u32::from(self.height)),
);
self.callbacks.paint(&mut frame);
frame.finish()?;
}
self.events.try_send(WindowEvent::NeedRepaint)?;
Ok(())
}
@ -164,9 +147,8 @@ impl XWindowInner {
self.expose.push_back(expose);
}
fn do_mouse_event(&mut self, event: &MouseEvent) -> anyhow::Result<()> {
self.callbacks
.mouse_event(&event, &XWindow::from_id(self.window_id));
fn do_mouse_event(&mut self, event: MouseEvent) -> anyhow::Result<()> {
self.events.try_send(WindowEvent::MouseEvent(event))?;
Ok(())
}
@ -185,14 +167,14 @@ impl XWindowInner {
self.dpi
);
self.dpi = dpi;
self.callbacks.resize(
Dimensions {
let _ = self.events.try_send(WindowEvent::Resized {
dimensions: Dimensions {
pixel_width: self.width as usize,
pixel_height: self.height as usize,
dpi: self.dpi as usize,
},
self.is_fullscreen().unwrap_or(false),
);
is_full_screen: self.is_fullscreen().unwrap_or(false),
});
}
}
@ -218,16 +200,17 @@ impl XWindowInner {
self.resize_promises.remove(0).ok(dimensions);
}
self.callbacks
.resize(dimensions, self.is_fullscreen().unwrap_or(false))
self.events.try_send(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.callbacks
.key_event(&key, &XWindow::from_id(self.window_id));
self.events.try_send(WindowEvent::KeyEvent(key))?;
}
}
@ -247,7 +230,7 @@ impl XWindowInner {
modifiers: xkeysyms::modifiers_from_state(motion.state()),
mouse_buttons: MouseButtons::default(),
};
self.do_mouse_event(&event)?;
self.do_mouse_event(event)?;
}
xcb::BUTTON_PRESS | xcb::BUTTON_RELEASE => {
let button_press: &xcb::ButtonPressEvent = unsafe { xcb::cast_event(event) };
@ -302,16 +285,19 @@ impl XWindowInner {
modifiers: xkeysyms::modifiers_from_state(button_press.state()),
mouse_buttons: MouseButtons::default(),
};
self.do_mouse_event(&event)?;
self.do_mouse_event(event)?;
}
xcb::CLIENT_MESSAGE => {
let msg: &xcb::ClientMessageEvent = unsafe { xcb::cast_event(event) };
if msg.data().data32()[0] == conn.atom_delete() && self.callbacks.can_close() {
xcb::destroy_window(conn.conn(), self.window_id);
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);
}
}
}
xcb::DESTROY_NOTIFY => {
self.callbacks.destroy();
self.events.try_send(WindowEvent::Destroyed)?;
conn.windows.borrow_mut().remove(&self.window_id);
}
xcb::SELECTION_CLEAR => {
@ -354,11 +340,11 @@ impl XWindowInner {
}
xcb::FOCUS_IN => {
log::trace!("Calling focus_change(true)");
self.callbacks.focus_change(true);
self.events.try_send(WindowEvent::FocusChanged(true))?;
}
xcb::FOCUS_OUT => {
log::trace!("Calling focus_change(false)");
self.callbacks.focus_change(false);
self.events.try_send(WindowEvent::FocusChanged(false))?;
}
_ => {
eprintln!("unhandled: {:x}", r);
@ -683,9 +669,8 @@ impl XWindow {
name: &str,
width: usize,
height: usize,
callbacks: Box<dyn WindowCallbacks>,
config: Option<&ConfigHandle>,
) -> anyhow::Result<Window> {
) -> anyhow::Result<(Window, WindowEventReceiver)> {
let config = match config {
Some(c) => c.clone(),
None => config::configuration(),
@ -698,6 +683,8 @@ impl XWindow {
})?
.x11();
let (events, receiver) = async_channel::unbounded();
let window_id;
let window = {
let setup = conn.conn().get_setup();
@ -761,7 +748,7 @@ impl XWindow {
Arc::new(Mutex::new(XWindowInner {
window_id,
conn: Rc::downgrade(&conn),
callbacks,
events,
width: width.try_into()?,
height: height.try_into()?,
dpi: conn.default_dpi(),
@ -797,14 +784,12 @@ impl XWindow {
let window_handle = Window::X11(XWindow::from_id(window_id));
window.lock().unwrap().enable_opengl()?;
conn.windows.borrow_mut().insert(window_id, window);
window_handle.set_title(name);
window_handle.show();
Ok(window_handle)
Ok((window_handle, receiver))
}
}
@ -900,7 +885,21 @@ impl XWindowInner {
}
}
#[async_trait(?Send)]
impl WindowOps for XWindow {
async fn enable_opengl(&self) -> anyhow::Result<Rc<glium::backend::Context>> {
let window = self.0;
promise::spawn::spawn(async move {
if let Some(handle) = Connection::get().unwrap().x11().window_by_id(window) {
let mut inner = handle.lock().unwrap();
inner.enable_opengl()
} else {
anyhow::bail!("invalid window");
}
})
.await
}
fn close(&self) -> Future<()> {
XConnection::with_window_inner(self.0, |inner| {
inner.close();
@ -997,20 +996,6 @@ impl WindowOps for XWindow {
})
}
fn apply<R, F: Send + 'static + FnMut(&mut dyn Any, &dyn WindowOps) -> anyhow::Result<R>>(
&self,
mut func: F,
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
XConnection::with_window_inner(self.0, move |inner| {
let window = XWindow(inner.window_id);
func(inner.callbacks.as_any(), &window)
})
}
/// Initiate textual transfer from the clipboard
fn get_clipboard(&self, clipboard: Clipboard) -> Future<String> {
let mut promise = Promise::new();

View File

@ -7,10 +7,10 @@ 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, Dimensions, MouseCursor, ScreenPoint, WindowCallbacks, WindowOps};
use crate::{Clipboard, Dimensions, MouseCursor, ScreenPoint, WindowEventReceiver, WindowOps};
use async_trait::async_trait;
use config::ConfigHandle;
use promise::*;
use std::any::Any;
use std::rc::Rc;
pub enum Connection {
@ -51,16 +51,13 @@ impl Connection {
name: &str,
width: usize,
height: usize,
callbacks: Box<dyn WindowCallbacks>,
config: Option<&ConfigHandle>,
) -> anyhow::Result<Window> {
) -> anyhow::Result<(Window, WindowEventReceiver)> {
match self {
Self::X11(_) => {
XWindow::new_window(class_name, name, width, height, callbacks, config).await
}
Self::X11(_) => XWindow::new_window(class_name, name, width, height, config).await,
#[cfg(feature = "wayland")]
Self::Wayland(_) => {
WaylandWindow::new_window(class_name, name, width, height, callbacks, config).await
WaylandWindow::new_window(class_name, name, width, height, config).await
}
}
}
@ -113,17 +110,33 @@ impl Window {
name: &str,
width: usize,
height: usize,
callbacks: Box<dyn WindowCallbacks>,
config: Option<&ConfigHandle>,
) -> anyhow::Result<Window> {
) -> anyhow::Result<(Window, WindowEventReceiver)> {
Connection::get()
.unwrap()
.new_window(class_name, name, width, height, callbacks, config)
.new_window(class_name, name, width, height, config)
.await
}
}
#[async_trait(?Send)]
impl WindowOps for Window {
async fn enable_opengl(&self) -> anyhow::Result<Rc<glium::backend::Context>> {
match self {
Self::X11(x) => x.enable_opengl().await,
#[cfg(feature = "wayland")]
Self::Wayland(w) => w.enable_opengl().await,
}
}
fn finish_frame(&self, frame: glium::Frame) -> anyhow::Result<()> {
match self {
Self::X11(x) => x.finish_frame(frame),
#[cfg(feature = "wayland")]
Self::Wayland(w) => w.finish_frame(frame),
}
}
fn close(&self) -> Future<()> {
match self {
Self::X11(x) => x.close(),
@ -212,21 +225,6 @@ impl WindowOps for Window {
}
}
fn apply<R, F: Send + 'static + FnMut(&mut dyn Any, &dyn WindowOps) -> anyhow::Result<R>>(
&self,
func: F,
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
match self {
Self::X11(x) => x.apply(func),
#[cfg(feature = "wayland")]
Self::Wayland(w) => w.apply(func),
}
}
fn get_clipboard(&self, clipboard: Clipboard) -> Future<String> {
match self {
Self::X11(x) => x.get_clipboard(clipboard),