diff --git a/src/x11/event_loop.rs b/src/x11/event_loop.rs new file mode 100644 index 0000000..6b6ecd3 --- /dev/null +++ b/src/x11/event_loop.rs @@ -0,0 +1,303 @@ +use crate::x11::keyboard::{convert_key_press_event, convert_key_release_event, key_mods}; +use crate::x11::{ParentHandle, Window, WindowInner}; +use crate::{ + Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, WindowEvent, WindowHandler, + WindowInfo, +}; +use std::error::Error; +use std::os::fd::AsRawFd; +use std::time::{Duration, Instant}; +use x11rb::connection::Connection; +use x11rb::protocol::Event as XEvent; + +pub(super) struct EventLoop { + handler: Box, + window: WindowInner, + parent_handle: Option, + + new_physical_size: Option, + frame_interval: Duration, + event_loop_running: bool, +} + +impl EventLoop { + pub fn new( + window: WindowInner, handler: impl WindowHandler + 'static, + parent_handle: Option, + ) -> Self { + Self { + window, + handler: Box::new(handler), + parent_handle, + frame_interval: Duration::from_millis(15), + event_loop_running: false, + new_physical_size: None, + } + } + + #[inline] + fn drain_xcb_events(&mut self) -> Result<(), Box> { + // the X server has a tendency to send spurious/extraneous configure notify events when a + // window is resized, and we need to batch those together and just send one resize event + // when they've all been coalesced. + self.new_physical_size = None; + + while let Some(event) = self.window.xcb_connection.conn.poll_for_event()? { + self.handle_xcb_event(event); + } + + if let Some(size) = self.new_physical_size.take() { + self.window.window_info = + WindowInfo::from_physical_size(size, self.window.window_info.scale()); + + let window_info = self.window.window_info; + + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Window(WindowEvent::Resized(window_info)), + ); + } + + Ok(()) + } + + // Event loop + // FIXME: poll() acts fine on linux, sometimes funky on *BSD. XCB upstream uses a define to + // switch between poll() and select() (the latter of which is fine on *BSD), and we should do + // the same. + pub fn run(&mut self) -> Result<(), Box> { + use nix::poll::*; + + let xcb_fd = self.window.xcb_connection.conn.as_raw_fd(); + + let mut last_frame = Instant::now(); + self.event_loop_running = true; + + while self.event_loop_running { + // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in + // the expected frame time, this will throttle down to prevent multiple frames from + // being queued up. The conditional here is needed because event handling and frame + // drawing is interleaved. The `poll()` function below will wait until the next frame + // can be drawn, or until the window receives an event. We thus need to manually check + // if it's already time to draw a new frame. + let next_frame = last_frame + self.frame_interval; + if Instant::now() >= next_frame { + self.handler.on_frame(&mut crate::Window::new(Window { inner: &self.window })); + last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval); + } + + let mut fds = [PollFd::new(xcb_fd, PollFlags::POLLIN)]; + + // Check for any events in the internal buffers + // before going to sleep: + self.drain_xcb_events()?; + + // FIXME: handle errors + poll(&mut fds, next_frame.duration_since(Instant::now()).subsec_millis() as i32) + .unwrap(); + + if let Some(revents) = fds[0].revents() { + if revents.contains(PollFlags::POLLERR) { + panic!("xcb connection poll error"); + } + + if revents.contains(PollFlags::POLLIN) { + self.drain_xcb_events()?; + } + } + + // Check if the parents's handle was dropped (such as when the host + // requested the window to close) + // + // FIXME: This will need to be changed from just setting an atomic to somehow + // synchronizing with the window being closed (using a synchronous channel, or + // by joining on the event loop thread). + if let Some(parent_handle) = &self.parent_handle { + if parent_handle.parent_did_drop() { + self.handle_must_close(); + self.window.close_requested.set(false); + } + } + + // Check if the user has requested the window to close + if self.window.close_requested.get() { + self.handle_must_close(); + self.window.close_requested.set(false); + } + } + + Ok(()) + } + + fn handle_xcb_event(&mut self, event: XEvent) { + // For all the keyboard and mouse events, you can fetch + // `x`, `y`, `detail`, and `state`. + // - `x` and `y` are the position inside the window where the cursor currently is + // when the event happened. + // - `detail` will tell you which keycode was pressed/released (for keyboard events) + // or which mouse button was pressed/released (for mouse events). + // For mouse events, here's what the value means (at least on my current mouse): + // 1 = left mouse button + // 2 = middle mouse button (scroll wheel) + // 3 = right mouse button + // 4 = scroll wheel up + // 5 = scroll wheel down + // 8 = lower side button ("back" button) + // 9 = upper side button ("forward" button) + // Note that you *will* get a "button released" event for even the scroll wheel + // events, which you can probably ignore. + // - `state` will tell you the state of the main three mouse buttons and some of + // the keyboard modifier keys at the time of the event. + // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 + + match event { + //// + // window + //// + XEvent::ClientMessage(event) => { + if event.format == 32 + && event.data.as_data32()[0] + == self.window.xcb_connection.atoms.WM_DELETE_WINDOW + { + self.handle_close_requested(); + } + } + + XEvent::ConfigureNotify(event) => { + let new_physical_size = PhySize::new(event.width as u32, event.height as u32); + + if self.new_physical_size.is_some() + || new_physical_size != self.window.window_info.physical_size() + { + self.new_physical_size = Some(new_physical_size); + } + } + + //// + // mouse + //// + XEvent::MotionNotify(event) => { + let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); + let logical_pos = physical_pos.to_logical(&self.window.window_info); + + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::CursorMoved { + position: logical_pos, + modifiers: key_mods(event.state), + }), + ); + } + + XEvent::EnterNotify(event) => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::CursorEntered), + ); + // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, + // we generate a CursorMoved as well, so the mouse position from here isn't lost + let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); + let logical_pos = physical_pos.to_logical(&self.window.window_info); + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::CursorMoved { + position: logical_pos, + modifiers: key_mods(event.state), + }), + ); + } + + XEvent::LeaveNotify(_) => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::CursorLeft), + ); + } + + XEvent::ButtonPress(event) => match event.detail { + 4..=7 => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::WheelScrolled { + delta: match event.detail { + 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, + 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, + 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, + 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, + _ => unreachable!(), + }, + modifiers: key_mods(event.state), + }), + ); + } + detail => { + let button_id = mouse_id(detail); + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::ButtonPressed { + button: button_id, + modifiers: key_mods(event.state), + }), + ); + } + }, + + XEvent::ButtonRelease(event) => { + if !(4..=7).contains(&event.detail) { + let button_id = mouse_id(event.detail); + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::ButtonReleased { + button: button_id, + modifiers: key_mods(event.state), + }), + ); + } + } + + //// + // keys + //// + XEvent::KeyPress(event) => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Keyboard(convert_key_press_event(&event)), + ); + } + + XEvent::KeyRelease(event) => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Keyboard(convert_key_release_event(&event)), + ); + } + + _ => {} + } + } + + fn handle_close_requested(&mut self) { + // FIXME: handler should decide whether window stays open or not + self.handle_must_close(); + } + + fn handle_must_close(&mut self) { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Window(WindowEvent::WillClose), + ); + + self.event_loop_running = false; + } +} + +fn mouse_id(id: u8) -> MouseButton { + match id { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + 8 => MouseButton::Back, + 9 => MouseButton::Forward, + id => MouseButton::Other(id), + } +} diff --git a/src/x11/mod.rs b/src/x11/mod.rs index 71faf04..149df0b 100644 --- a/src/x11/mod.rs +++ b/src/x11/mod.rs @@ -5,5 +5,6 @@ mod window; pub use window::*; mod cursor; +mod event_loop; mod keyboard; mod visual_info; diff --git a/src/x11/window.rs b/src/x11/window.rs index 7658d7e..5b801ec 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -1,12 +1,10 @@ use std::cell::Cell; use std::error::Error; use std::ffi::c_void; -use std::os::fd::AsRawFd; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc; use std::sync::Arc; use std::thread; -use std::time::*; use raw_window_handle::{ HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, @@ -18,19 +16,17 @@ use x11rb::protocol::xproto::{ AtomEnum, ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt as _, CreateGCAux, CreateWindowAux, EventMask, PropMode, Visualid, Window as XWindow, WindowClass, }; -use x11rb::protocol::Event as XEvent; use x11rb::wrapper::ConnectionExt as _; use super::XcbConnection; use crate::{ - Event, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent, - WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, + Event, MouseCursor, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, + WindowScalePolicy, }; -use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mods}; - #[cfg(feature = "opengl")] use crate::gl::{platform, GlContext}; +use crate::x11::event_loop::EventLoop; use crate::x11::visual_info::WindowVisualConfig; pub struct WindowHandle { @@ -67,7 +63,7 @@ unsafe impl HasRawWindowHandle for WindowHandle { } } -struct ParentHandle { +pub(crate) struct ParentHandle { close_requested: Arc, is_open: Arc, } @@ -97,26 +93,21 @@ impl Drop for ParentHandle { } } -struct WindowInner { - xcb_connection: XcbConnection, +pub(crate) struct WindowInner { + pub(crate) xcb_connection: XcbConnection, window_id: XWindow, - window_info: WindowInfo, + pub(crate) window_info: WindowInfo, visual_id: Visualid, mouse_cursor: Cell, - frame_interval: Duration, - event_loop_running: bool, - close_requested: Cell, - - new_physical_size: Option, - parent_handle: Option, + pub(crate) close_requested: Cell, #[cfg(feature = "opengl")] gl_context: Option, } pub struct Window<'a> { - inner: &'a WindowInner, + pub(crate) inner: &'a WindowInner, } // Hack to allow sending a RawWindowHandle between threads. Do not make public @@ -287,13 +278,8 @@ impl<'a> Window<'a> { visual_id: visual_info.visual_id, mouse_cursor: Cell::new(MouseCursor::default()), - frame_interval: Duration::from_millis(15), - event_loop_running: false, close_requested: Cell::new(false), - new_physical_size: None, - parent_handle, - #[cfg(feature = "opengl")] gl_context, }; @@ -308,7 +294,7 @@ impl<'a> Window<'a> { let _ = tx.send(Ok(SendableRwh(window.raw_window_handle()))); - inner.run_event_loop(&mut handler)?; + EventLoop::new(inner, handler, parent_handle).run()?; Ok(()) } @@ -365,266 +351,6 @@ impl<'a> Window<'a> { } } -impl WindowInner { - #[inline] - fn drain_xcb_events(&mut self, handler: &mut dyn WindowHandler) -> Result<(), Box> { - // the X server has a tendency to send spurious/extraneous configure notify events when a - // window is resized, and we need to batch those together and just send one resize event - // when they've all been coalesced. - self.new_physical_size = None; - - while let Some(event) = self.xcb_connection.conn.poll_for_event()? { - self.handle_xcb_event(handler, event); - } - - if let Some(size) = self.new_physical_size.take() { - self.window_info = WindowInfo::from_physical_size(size, self.window_info.scale()); - - let window_info = self.window_info; - - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Window(WindowEvent::Resized(window_info)), - ); - } - - Ok(()) - } - - // Event loop - // FIXME: poll() acts fine on linux, sometimes funky on *BSD. XCB upstream uses a define to - // switch between poll() and select() (the latter of which is fine on *BSD), and we should do - // the same. - fn run_event_loop(&mut self, handler: &mut dyn WindowHandler) -> Result<(), Box> { - use nix::poll::*; - - let xcb_fd = self.xcb_connection.conn.as_raw_fd(); - - let mut last_frame = Instant::now(); - self.event_loop_running = true; - - while self.event_loop_running { - // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in - // the expected frame time, this will throttle down to prevent multiple frames from - // being queued up. The conditional here is needed because event handling and frame - // drawing is interleaved. The `poll()` function below will wait until the next frame - // can be drawn, or until the window receives an event. We thus need to manually check - // if it's already time to draw a new frame. - let next_frame = last_frame + self.frame_interval; - if Instant::now() >= next_frame { - handler.on_frame(&mut crate::Window::new(Window { inner: self })); - last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval); - } - - let mut fds = [PollFd::new(xcb_fd, PollFlags::POLLIN)]; - - // Check for any events in the internal buffers - // before going to sleep: - self.drain_xcb_events(handler)?; - - // FIXME: handle errors - poll(&mut fds, next_frame.duration_since(Instant::now()).subsec_millis() as i32) - .unwrap(); - - if let Some(revents) = fds[0].revents() { - if revents.contains(PollFlags::POLLERR) { - panic!("xcb connection poll error"); - } - - if revents.contains(PollFlags::POLLIN) { - self.drain_xcb_events(handler)?; - } - } - - // Check if the parents's handle was dropped (such as when the host - // requested the window to close) - // - // FIXME: This will need to be changed from just setting an atomic to somehow - // synchronizing with the window being closed (using a synchronous channel, or - // by joining on the event loop thread). - if let Some(parent_handle) = &self.parent_handle { - if parent_handle.parent_did_drop() { - self.handle_must_close(handler); - self.close_requested.set(false); - } - } - - // Check if the user has requested the window to close - if self.close_requested.get() { - self.handle_must_close(handler); - self.close_requested.set(false); - } - } - - Ok(()) - } - - fn handle_close_requested(&mut self, handler: &mut dyn WindowHandler) { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Window(WindowEvent::WillClose), - ); - - // FIXME: handler should decide whether window stays open or not - self.event_loop_running = false; - } - - fn handle_must_close(&mut self, handler: &mut dyn WindowHandler) { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Window(WindowEvent::WillClose), - ); - - self.event_loop_running = false; - } - - fn handle_xcb_event(&mut self, handler: &mut dyn WindowHandler, event: XEvent) { - // For all of the keyboard and mouse events, you can fetch - // `x`, `y`, `detail`, and `state`. - // - `x` and `y` are the position inside the window where the cursor currently is - // when the event happened. - // - `detail` will tell you which keycode was pressed/released (for keyboard events) - // or which mouse button was pressed/released (for mouse events). - // For mouse events, here's what the value means (at least on my current mouse): - // 1 = left mouse button - // 2 = middle mouse button (scroll wheel) - // 3 = right mouse button - // 4 = scroll wheel up - // 5 = scroll wheel down - // 8 = lower side button ("back" button) - // 9 = upper side button ("forward" button) - // Note that you *will* get a "button released" event for even the scroll wheel - // events, which you can probably ignore. - // - `state` will tell you the state of the main three mouse buttons and some of - // the keyboard modifier keys at the time of the event. - // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 - - match event { - //// - // window - //// - XEvent::ClientMessage(event) => { - if event.format == 32 - && event.data.as_data32()[0] == self.xcb_connection.atoms.WM_DELETE_WINDOW - { - self.handle_close_requested(handler); - } - } - - XEvent::ConfigureNotify(event) => { - let new_physical_size = PhySize::new(event.width as u32, event.height as u32); - - if self.new_physical_size.is_some() - || new_physical_size != self.window_info.physical_size() - { - self.new_physical_size = Some(new_physical_size); - } - } - - //// - // mouse - //// - XEvent::MotionNotify(event) => { - let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); - let logical_pos = physical_pos.to_logical(&self.window_info); - - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::CursorMoved { - position: logical_pos, - modifiers: key_mods(event.state), - }), - ); - } - - XEvent::EnterNotify(event) => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::CursorEntered), - ); - // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, - // we generate a CursorMoved as well, so the mouse position from here isn't lost - let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); - let logical_pos = physical_pos.to_logical(&self.window_info); - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::CursorMoved { - position: logical_pos, - modifiers: key_mods(event.state), - }), - ); - } - - XEvent::LeaveNotify(_) => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::CursorLeft), - ); - } - - XEvent::ButtonPress(event) => match event.detail { - 4..=7 => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::WheelScrolled { - delta: match event.detail { - 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, - 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, - 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, - 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, - _ => unreachable!(), - }, - modifiers: key_mods(event.state), - }), - ); - } - detail => { - let button_id = mouse_id(detail); - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::ButtonPressed { - button: button_id, - modifiers: key_mods(event.state), - }), - ); - } - }, - - XEvent::ButtonRelease(event) => { - if !(4..=7).contains(&event.detail) { - let button_id = mouse_id(event.detail); - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::ButtonReleased { - button: button_id, - modifiers: key_mods(event.state), - }), - ); - } - } - - //// - // keys - //// - XEvent::KeyPress(event) => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Keyboard(convert_key_press_event(&event)), - ); - } - - XEvent::KeyRelease(event) => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Keyboard(convert_key_release_event(&event)), - ); - } - - _ => {} - } - } -} - unsafe impl<'a> HasRawWindowHandle for Window<'a> { fn raw_window_handle(&self) -> RawWindowHandle { let mut handle = XlibWindowHandle::empty(); @@ -648,17 +374,6 @@ unsafe impl<'a> HasRawDisplayHandle for Window<'a> { } } -fn mouse_id(id: u8) -> MouseButton { - match id { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - 8 => MouseButton::Back, - 9 => MouseButton::Forward, - id => MouseButton::Other(id), - } -} - pub fn copy_to_clipboard(_data: &str) { todo!() }