From 54f8bc734520769d0fc73fac200bf8fd18165126 Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 15:40:19 +0200 Subject: [PATCH 1/6] x11: split event loop and XCB event handling into separate fns --- src/x11/window.rs | 213 ++++++++++++++++++++++++---------------------- 1 file changed, 110 insertions(+), 103 deletions(-) diff --git a/src/x11/window.rs b/src/x11/window.rs index 2974de6..bece3f4 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -12,6 +12,10 @@ pub struct Window { scaling: f64, } +// FIXME: move to outer crate context +pub struct WindowHandle; + + impl Window { pub fn open(options: WindowOpenOptions) -> WindowHandle { // Convert the parent to a X11 window ID if we're given one @@ -101,10 +105,115 @@ impl Window { let mut handler = H::build(&mut window); - run_event_loop(&mut window, &mut handler); + window.run_event_loop(&mut handler); WindowHandle } + + // Event loop + fn run_event_loop(&mut self, handler: &mut H) { + loop { + let ev = self.xcb_connection.conn.wait_for_event(); + if let Some(event) = ev { + self.handle_xcb_event(handler, event); + } + } + } + + fn handle_xcb_event(&mut self, handler: &mut H, event: xcb::GenericEvent) { + let event_type = event.response_type() & !0x80; + + // 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_type { + xcb::EXPOSE => { + handler.draw(self); + } + xcb::MOTION_NOTIFY => { + let event = unsafe { xcb::cast_event::(&event) }; + let detail = event.detail(); + + if detail != 4 && detail != 5 { + handler.on_event( + self, + Event::CursorMotion(event.event_x() as i32, event.event_y() as i32), + ); + } + } + xcb::BUTTON_PRESS => { + let event = unsafe { xcb::cast_event::(&event) }; + let detail = event.detail(); + + match detail { + 4 => { + handler.on_event( + self, + Event::MouseScroll(MouseScroll { + x_delta: 0.0, + y_delta: 1.0, + }), + ); + } + 5 => { + handler.on_event( + self, + Event::MouseScroll(MouseScroll { + x_delta: 0.0, + y_delta: -1.0, + }), + ); + } + detail => { + let button_id = mouse_id(detail); + handler.on_event(self, Event::MouseDown(button_id)); + } + } + } + xcb::BUTTON_RELEASE => { + let event = unsafe { xcb::cast_event::(&event) }; + let detail = event.detail(); + + if detail != 4 && detail != 5 { + let button_id = mouse_id(detail); + handler.on_event(self, Event::MouseUp(button_id)); + } + } + xcb::KEY_PRESS => { + let event = unsafe { xcb::cast_event::(&event) }; + let detail = event.detail(); + + handler.on_event(self, Event::KeyDown(detail)); + } + xcb::KEY_RELEASE => { + let event = unsafe { xcb::cast_event::(&event) }; + let detail = event.detail(); + + handler.on_event(self, Event::KeyUp(detail)); + } + _ => { + println!("Unhandled event type: {:?}", event_type); + } + } + } + } unsafe impl HasRawWindowHandle for Window { @@ -117,108 +226,6 @@ unsafe impl HasRawWindowHandle for Window { } } -pub struct WindowHandle; - -// Event loop -fn run_event_loop(window: &mut Window, handler: &mut H) { - loop { - let ev = window.xcb_connection.conn.wait_for_event(); - if let Some(event) = ev { - let event_type = event.response_type() & !0x80; - - // 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_type { - xcb::EXPOSE => { - handler.draw(window); - } - xcb::MOTION_NOTIFY => { - let event = unsafe { xcb::cast_event::(&event) }; - let detail = event.detail(); - - if detail != 4 && detail != 5 { - handler.on_event( - window, - Event::CursorMotion(event.event_x() as i32, event.event_y() as i32), - ); - } - } - xcb::BUTTON_PRESS => { - let event = unsafe { xcb::cast_event::(&event) }; - let detail = event.detail(); - - match detail { - 4 => { - handler.on_event( - window, - Event::MouseScroll(MouseScroll { - x_delta: 0.0, - y_delta: 1.0, - }), - ); - } - 5 => { - handler.on_event( - window, - Event::MouseScroll(MouseScroll { - x_delta: 0.0, - y_delta: -1.0, - }), - ); - } - detail => { - let button_id = mouse_id(detail); - handler.on_event(window, Event::MouseDown(button_id)); - } - } - } - xcb::BUTTON_RELEASE => { - let event = unsafe { xcb::cast_event::(&event) }; - let detail = event.detail(); - - if detail != 4 && detail != 5 { - let button_id = mouse_id(detail); - handler.on_event(window, Event::MouseUp(button_id)); - } - } - xcb::KEY_PRESS => { - let event = unsafe { xcb::cast_event::(&event) }; - let detail = event.detail(); - - handler.on_event(window, Event::KeyDown(detail)); - } - xcb::KEY_RELEASE => { - let event = unsafe { xcb::cast_event::(&event) }; - let detail = event.detail(); - - handler.on_event(window, Event::KeyUp(detail)); - } - _ => { - println!("Unhandled event type: {:?}", event_type); - } - } - } - } -} - // Try to get the scaling with this function first. // If this gives you `None`, fall back to `get_scaling_screen_dimensions`. // If neither work, I guess just assume 96.0 and don't do any scaling. From da2c12dd25deba999812fca9557ac357651d5ce3 Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 15:44:01 +0200 Subject: [PATCH 2/6] x11: move scaling determination funcs into xcb_connection.rs --- src/x11/window.rs | 86 +----------------------------------- src/x11/xcb_connection.rs | 91 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 85 deletions(-) diff --git a/src/x11/window.rs b/src/x11/window.rs index bece3f4..4951a01 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::os::raw::{c_ulong, c_void}; use super::XcbConnection; @@ -93,9 +92,7 @@ impl Window { xcb_connection.conn.flush(); - let scaling = get_scaling_xft(&xcb_connection) - .or(get_scaling_screen_dimensions(&xcb_connection)) - .unwrap_or(1.0); + let scaling = xcb_connection.get_scaling().unwrap_or(1.0); let mut window = Self { xcb_connection, @@ -226,87 +223,6 @@ unsafe impl HasRawWindowHandle for Window { } } -// Try to get the scaling with this function first. -// If this gives you `None`, fall back to `get_scaling_screen_dimensions`. -// If neither work, I guess just assume 96.0 and don't do any scaling. -fn get_scaling_xft(xcb_connection: &XcbConnection) -> Option { - use std::ffi::CString; - use x11::xlib::{ - XResourceManagerString, XrmDestroyDatabase, XrmGetResource, XrmGetStringDatabase, XrmValue, - }; - - let display = xcb_connection.conn.get_raw_dpy(); - unsafe { - let rms = XResourceManagerString(display); - if !rms.is_null() { - let db = XrmGetStringDatabase(rms); - if !db.is_null() { - let mut value = XrmValue { - size: 0, - addr: std::ptr::null_mut(), - }; - - let mut value_type: *mut libc::c_char = std::ptr::null_mut(); - let name_c_str = CString::new("Xft.dpi").unwrap(); - let c_str = CString::new("Xft.Dpi").unwrap(); - - let dpi = if XrmGetResource( - db, - name_c_str.as_ptr(), - c_str.as_ptr(), - &mut value_type, - &mut value, - ) != 0 - && !value.addr.is_null() - { - let value_addr: &CStr = CStr::from_ptr(value.addr); - value_addr.to_str().ok(); - let value_str = value_addr.to_str().ok()?; - let value_f64: f64 = value_str.parse().ok()?; - let dpi_to_scale = value_f64 / 96.0; - Some(dpi_to_scale) - } else { - None - }; - XrmDestroyDatabase(db); - - return dpi; - } - } - } - None -} - -// Try to get the scaling with `get_scaling_xft` first. -// Only use this function as a fallback. -// If neither work, I guess just assume 96.0 and don't do any scaling. -fn get_scaling_screen_dimensions(xcb_connection: &XcbConnection) -> Option { - // Figure out screen information - let setup = xcb_connection.conn.get_setup(); - let screen = setup - .roots() - .nth(xcb_connection.xlib_display as usize) - .unwrap(); - - // Get the DPI from the screen struct - // - // there are 2.54 centimeters to an inch; so there are 25.4 millimeters. - // dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch)) - // = N pixels / (M inch / 25.4) - // = N * 25.4 pixels / M inch - let width_px = screen.width_in_pixels() as f64; - let width_mm = screen.width_in_millimeters() as f64; - let height_px = screen.height_in_pixels() as f64; - let height_mm = screen.height_in_millimeters() as f64; - let _xres = width_px * 25.4 / width_mm; - let yres = height_px * 25.4 / height_mm; - - let yscale = yres / 96.0; - - // TODO: choose between `xres` and `yres`? (probably both are the same?) - Some(yscale) -} - fn mouse_id(id: u8) -> MouseButtonID { match id { 1 => MouseButtonID::Left, diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index 113b9f2..e0725f6 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -2,6 +2,11 @@ /// /// Keeps track of the xcb connection itself and the xlib display ID that was used to connect. +use std::ffi::{ + CString, + CStr +}; + pub struct XcbConnection { pub conn: xcb::Connection, pub xlib_display: i32, @@ -12,4 +17,90 @@ impl XcbConnection { let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display().unwrap(); Self { conn, xlib_display } } + + // Try to get the scaling with this function first. + // If this gives you `None`, fall back to `get_scaling_screen_dimensions`. + // If neither work, I guess just assume 96.0 and don't do any scaling. + pub fn get_scaling_xft(&self) -> Option { + use x11::xlib::{ + XResourceManagerString, XrmDestroyDatabase, XrmGetResource, XrmGetStringDatabase, XrmValue, + }; + + let display = self.conn.get_raw_dpy(); + unsafe { + let rms = XResourceManagerString(display); + if !rms.is_null() { + let db = XrmGetStringDatabase(rms); + if !db.is_null() { + let mut value = XrmValue { + size: 0, + addr: std::ptr::null_mut(), + }; + + let mut value_type: *mut libc::c_char = std::ptr::null_mut(); + let name_c_str = CString::new("Xft.dpi").unwrap(); + let c_str = CString::new("Xft.Dpi").unwrap(); + + let dpi = if XrmGetResource( + db, + name_c_str.as_ptr(), + c_str.as_ptr(), + &mut value_type, + &mut value, + ) != 0 + && !value.addr.is_null() + { + let value_addr: &CStr = CStr::from_ptr(value.addr); + value_addr.to_str().ok(); + let value_str = value_addr.to_str().ok()?; + let value_f64: f64 = value_str.parse().ok()?; + let dpi_to_scale = value_f64 / 96.0; + Some(dpi_to_scale) + } else { + None + }; + XrmDestroyDatabase(db); + + return dpi; + } + } + } + None + } + + // Try to get the scaling with `get_scaling_xft` first. + // Only use this function as a fallback. + // If neither work, I guess just assume 96.0 and don't do any scaling. + pub fn get_scaling_screen_dimensions(&self) -> Option { + // Figure out screen information + let setup = self.conn.get_setup(); + let screen = setup + .roots() + .nth(self.xlib_display as usize) + .unwrap(); + + // Get the DPI from the screen struct + // + // there are 2.54 centimeters to an inch; so there are 25.4 millimeters. + // dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch)) + // = N pixels / (M inch / 25.4) + // = N * 25.4 pixels / M inch + let width_px = screen.width_in_pixels() as f64; + let width_mm = screen.width_in_millimeters() as f64; + let height_px = screen.height_in_pixels() as f64; + let height_mm = screen.height_in_millimeters() as f64; + let _xres = width_px * 25.4 / width_mm; + let yres = height_px * 25.4 / height_mm; + + let yscale = yres / 96.0; + + // TODO: choose between `xres` and `yres`? (probably both are the same?) + Some(yscale) + } + + #[inline] + pub fn get_scaling(&self) -> Option { + self.get_scaling_xft() + .or(self.get_scaling_screen_dimensions()) + } } From b5dfbd946e53e35270117213f7c642cb25708fda Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 15:47:00 +0200 Subject: [PATCH 3/6] x11: pass XCB conn error back through XcbConnection::new() --- src/x11/window.rs | 3 ++- src/x11/xcb_connection.rs | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/x11/window.rs b/src/x11/window.rs index 4951a01..51a2dd0 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -25,7 +25,8 @@ impl Window { }; // Connect to the X server - let xcb_connection = XcbConnection::new(); + // FIXME: baseview error type instead of unwrap() + let xcb_connection = XcbConnection::new().unwrap(); // Get screen information (?) let setup = xcb_connection.conn.get_setup(); diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index e0725f6..f4bab15 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -13,15 +13,19 @@ pub struct XcbConnection { } impl XcbConnection { - pub fn new() -> Self { - let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display().unwrap(); - Self { conn, xlib_display } + pub fn new() -> Result { + xcb::Connection::connect_with_xlib_display() + .map(|(conn, xlib_display)| + Self { + conn, + xlib_display + }) } // Try to get the scaling with this function first. // If this gives you `None`, fall back to `get_scaling_screen_dimensions`. // If neither work, I guess just assume 96.0 and don't do any scaling. - pub fn get_scaling_xft(&self) -> Option { + fn get_scaling_xft(&self) -> Option { use x11::xlib::{ XResourceManagerString, XrmDestroyDatabase, XrmGetResource, XrmGetStringDatabase, XrmValue, }; @@ -71,7 +75,7 @@ impl XcbConnection { // Try to get the scaling with `get_scaling_xft` first. // Only use this function as a fallback. // If neither work, I guess just assume 96.0 and don't do any scaling. - pub fn get_scaling_screen_dimensions(&self) -> Option { + fn get_scaling_screen_dimensions(&self) -> Option { // Figure out screen information let setup = self.conn.get_setup(); let screen = setup From c76f089c96c54784f07288f0936ab0e1eebdca51 Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 15:50:10 +0200 Subject: [PATCH 4/6] x11/window: nitpicky code aesthetics --- src/x11/window.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/x11/window.rs b/src/x11/window.rs index 51a2dd0..d035266 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -17,13 +17,6 @@ pub struct WindowHandle; impl Window { pub fn open(options: WindowOpenOptions) -> WindowHandle { - // Convert the parent to a X11 window ID if we're given one - let parent = match options.parent { - Parent::None => None, - Parent::AsIfParented => None, // TODO: ??? - Parent::WithParent(p) => Some(p as u32), - }; - // Connect to the X server // FIXME: baseview error type instead of unwrap() let xcb_connection = XcbConnection::new().unwrap(); @@ -38,10 +31,9 @@ impl Window { let foreground = xcb_connection.conn.generate_id(); // Convert parent into something that X understands - let parent_id = if let Some(p) = parent { - p - } else { - screen.root() + let parent_id = match options.parent { + Parent::WithParent(p) => p as u32, + Parent::None | Parent::AsIfParented => screen.root(), }; xcb::create_gc( @@ -77,6 +69,7 @@ impl Window { | xcb::EVENT_MASK_KEY_RELEASE, )], ); + xcb::map_window(&xcb_connection.conn, window_id); // Change window title From dcb99e5c43b7903ce8d6d95fd376129cd2ddfa11 Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 16:13:32 +0200 Subject: [PATCH 5/6] x11: switch from wait_for_event() to directly using poll() --- Cargo.toml | 1 + src/x11/window.rs | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c4c2ed0..516adbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ raw-window-handle = "0.3.3" xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] } x11 = { version = "2.18", features = ["xlib"] } libc = "0.2" +nix = "0.18" [target.'cfg(target_os="windows")'.dependencies] winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi"] } diff --git a/src/x11/window.rs b/src/x11/window.rs index d035266..679bfd0 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -1,9 +1,10 @@ use std::os::raw::{c_ulong, c_void}; +use raw_window_handle::{unix::XlibHandle, HasRawWindowHandle, RawWindowHandle}; + use super::XcbConnection; use crate::{Event, MouseButtonID, MouseScroll, Parent, WindowHandler, WindowOpenOptions}; -use raw_window_handle::{unix::XlibHandle, HasRawWindowHandle, RawWindowHandle}; pub struct Window { xcb_connection: XcbConnection, @@ -101,12 +102,41 @@ impl Window { WindowHandle } + #[inline] + fn drain_xcb_events(&mut self, handler: &mut H) { + while let Some(event) = self.xcb_connection.conn.poll_for_event() { + self.handle_xcb_event(handler, event); + } + } + // 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 H) { + use nix::poll::*; + + let xcb_fd = unsafe { + let raw_conn = self.xcb_connection.conn.get_raw_conn(); + xcb::ffi::xcb_get_file_descriptor(raw_conn) + }; + loop { - let ev = self.xcb_connection.conn.wait_for_event(); - if let Some(event) = ev { - self.handle_xcb_event(handler, event); + let mut fds = [ + PollFd::new(xcb_fd, PollFlags::POLLIN) + ]; + + // FIXME: handle errors + poll(&mut fds, -1).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); + } } } } From b650bf772f32e8b623ccd05216833a82a4b7d33e Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 16:32:21 +0200 Subject: [PATCH 6/6] x11: frame/draw callbacks currently fixed at 15ms (just above 60fps), but easily configurable and something we can query the display server for in the future. --- examples/open_window.rs | 2 +- src/x11/window.rs | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/examples/open_window.rs b/examples/open_window.rs index 1371354..ff64ccc 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -20,7 +20,7 @@ impl WindowHandler for MyProgram { Self {} } - fn draw(&mut self, window: &mut Window) {} + fn draw(&mut self, _window: &mut Window) {} fn on_event(&mut self, window: &mut Window, event: Event) { match event { diff --git a/src/x11/window.rs b/src/x11/window.rs index 679bfd0..807d59f 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -1,4 +1,5 @@ use std::os::raw::{c_ulong, c_void}; +use std::time::*; use raw_window_handle::{unix::XlibHandle, HasRawWindowHandle, RawWindowHandle}; @@ -10,6 +11,9 @@ pub struct Window { xcb_connection: XcbConnection, window_id: u32, scaling: f64, + + frame_interval: Duration, + event_loop_running: bool } // FIXME: move to outer crate context @@ -93,6 +97,9 @@ impl Window { xcb_connection, window_id, scaling, + + frame_interval: Duration::from_millis(15), + event_loop_running: false }; let mut handler = H::build(&mut window); @@ -121,13 +128,28 @@ impl Window { xcb::ffi::xcb_get_file_descriptor(raw_conn) }; - loop { + let mut next_frame = Instant::now() + self.frame_interval; + self.event_loop_running = true; + + while self.event_loop_running { + let now = Instant::now(); + let until_next_frame = + if now > next_frame { + handler.draw(self); + + next_frame = now + self.frame_interval; + self.frame_interval + } else { + next_frame - now + }; + let mut fds = [ PollFd::new(xcb_fd, PollFlags::POLLIN) ]; // FIXME: handle errors - poll(&mut fds, -1).unwrap(); + poll(&mut fds, until_next_frame.subsec_millis() as i32) + .unwrap(); if let Some(revents) = fds[0].revents() { if revents.contains(PollFlags::POLLERR) { @@ -234,7 +256,6 @@ impl Window { } } } - } unsafe impl HasRawWindowHandle for Window {