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) 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), } }