From 54f8bc734520769d0fc73fac200bf8fd18165126 Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 15:40:19 +0200 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 { From 072918cb3f48895413f907db7bfbc10771cbee58 Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 16:49:55 +0200 Subject: [PATCH 07/10] rename WindowHandler.draw() to WindowHandler.on_frame() also remove the `Window` ref argument because `on_frame()` shouldn't be doing any window system ops (this is my opinion and i am happy to backpedal if it turns out to be wrong). --- examples/open_window.rs | 8 ++++---- src/lib.rs | 2 +- src/x11/window.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/open_window.rs b/examples/open_window.rs index ff64ccc..2f22de9 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -16,13 +16,13 @@ struct MyProgram {} impl WindowHandler for MyProgram { type Message = (); - fn build(window: &mut Window) -> Self { + fn build(_window: &mut Window) -> Self { Self {} } - fn draw(&mut self, _window: &mut Window) {} + fn on_frame(&mut self) {} - fn on_event(&mut self, window: &mut Window, event: Event) { + fn on_event(&mut self, _window: &mut Window, event: Event) { match event { Event::CursorMotion(x, y) => { println!("Cursor moved, x: {}, y: {}", x, y); @@ -63,5 +63,5 @@ impl WindowHandler for MyProgram { } } - fn on_message(&mut self, window: &mut Window, _message: Self::Message) {} + fn on_message(&mut self, _window: &mut Window, _message: Self::Message) {} } diff --git a/src/lib.rs b/src/lib.rs index c4ed84f..9cae115 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ pub trait WindowHandler { fn build(window: &mut Window) -> Self; - fn draw(&mut self, window: &mut Window); + fn on_frame(&mut self); fn on_event(&mut self, window: &mut Window, event: Event); fn on_message(&mut self, window: &mut Window, message: Self::Message); } diff --git a/src/x11/window.rs b/src/x11/window.rs index 807d59f..1553c34 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -135,7 +135,7 @@ impl Window { let now = Instant::now(); let until_next_frame = if now > next_frame { - handler.draw(self); + handler.on_frame(); next_frame = now + self.frame_interval; self.frame_interval @@ -188,7 +188,7 @@ impl Window { match event_type { xcb::EXPOSE => { - handler.draw(self); + handler.on_frame(); } xcb::MOTION_NOTIFY => { let event = unsafe { xcb::cast_event::(&event) }; From b64183fb196cc27b87a7f0557f0f2a183441667e Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 18:02:22 +0200 Subject: [PATCH 08/10] x11: Event::WillClose support this is a nightmare of ICCCM protocols but hey it's done now. --- Cargo.toml | 1 + examples/open_window.rs | 2 +- src/x11/window.rs | 46 ++++++++++++++++++++++++++++++++++++++ src/x11/xcb_connection.rs | 47 ++++++++++++++++++++++++++++++++++----- 4 files changed, 89 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 516adbf..1b0453c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ raw-window-handle = "0.3.3" [target.'cfg(target_os="linux")'.dependencies] xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] } x11 = { version = "2.18", features = ["xlib"] } +xcb-util = { version = "0.3", features = ["icccm"] } libc = "0.2" nix = "0.18" diff --git a/examples/open_window.rs b/examples/open_window.rs index 2f22de9..107d79a 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -22,7 +22,7 @@ impl WindowHandler for MyProgram { fn on_frame(&mut self) {} - fn on_event(&mut self, _window: &mut Window, event: Event) { + fn on_event(&mut self, window: &mut Window, event: Event) { match event { Event::CursorMotion(x, y) => { println!("Cursor moved, x: {}, y: {}", x, y); diff --git a/src/x11/window.rs b/src/x11/window.rs index 1553c34..9950cd2 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -89,6 +89,17 @@ impl Window { title.as_bytes(), ); + xcb_connection.atoms.wm_protocols + .zip(xcb_connection.atoms.wm_delete_window) + .map(|(wm_protocols, wm_delete_window)| { + xcb_util::icccm::set_wm_protocols( + &xcb_connection.conn, + window_id, + wm_protocols, + &[wm_delete_window] + ); + }); + xcb_connection.conn.flush(); let scaling = xcb_connection.get_scaling().unwrap_or(1.0); @@ -187,9 +198,35 @@ impl Window { // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 match event_type { + //// + // keys + //// + xcb::EXPOSE => { handler.on_frame(); } + + xcb::CLIENT_MESSAGE => { + let event = unsafe { xcb::cast_event::(&event) }; + + // what an absolute trajedy this all is + let data = event.data().data; + let (_, data32, _) = unsafe { data.align_to::() }; + + let wm_delete_window = self.xcb_connection.atoms.wm_delete_window.unwrap_or(xcb::NONE); + + if wm_delete_window == data32[0] { + handler.on_event(self, Event::WillClose); + + // FIXME: handler should decide whether window stays open or not + self.event_loop_running = false; + } + } + + //// + // mouse + //// + xcb::MOTION_NOTIFY => { let event = unsafe { xcb::cast_event::(&event) }; let detail = event.detail(); @@ -201,6 +238,7 @@ impl Window { ); } } + xcb::BUTTON_PRESS => { let event = unsafe { xcb::cast_event::(&event) }; let detail = event.detail(); @@ -230,6 +268,7 @@ impl Window { } } } + xcb::BUTTON_RELEASE => { let event = unsafe { xcb::cast_event::(&event) }; let detail = event.detail(); @@ -239,18 +278,25 @@ impl Window { handler.on_event(self, Event::MouseUp(button_id)); } } + + //// + // keys + //// + 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); } diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index f4bab15..ec6fd60 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -7,19 +7,54 @@ use std::ffi::{ CStr }; +pub(crate) struct Atoms { + pub wm_protocols: Option, + pub wm_delete_window: Option +} + pub struct XcbConnection { pub conn: xcb::Connection, pub xlib_display: i32, + + pub(crate) atoms: Atoms } +macro_rules! intern_atoms { + ($conn:expr, $( $name:ident ),+ ) => {{ + $( + #[allow(non_snake_case)] + let $name = xcb::intern_atom($conn, true, stringify!($name)); + )+ + + // splitting request and reply to improve throughput + + ( + $( $name.get_reply() + .map(|r| r.atom()) + .ok()),+ + ) + }}; +} + + impl XcbConnection { pub fn new() -> Result { - xcb::Connection::connect_with_xlib_display() - .map(|(conn, xlib_display)| - Self { - conn, - xlib_display - }) + let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display()?; + + let (wm_protocols, wm_delete_window) = + intern_atoms!(&conn, + WM_PROTOCOLS, + WM_DELETE_WINDOW); + + Ok(Self { + conn, + xlib_display, + + atoms: Atoms { + wm_protocols, + wm_delete_window + } + }) } // Try to get the scaling with this function first. From 7bb68d7d59de7c7581eb39ee74ba766c9c435e46 Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 18:06:13 +0200 Subject: [PATCH 09/10] .github/workflows: add libxcb-icccm4-dev to ubuntu runner --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b875447..871b27a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,7 +14,7 @@ jobs: - name: Install XCB and GL dependencies run: | sudo apt update - sudo apt install libx11-xcb-dev libxcb-dri2-0-dev libgl1-mesa-dev + sudo apt install libx11-xcb-dev libxcb-dri2-0-dev libgl1-mesa-dev libxcb-icccm4-dev if: contains(matrix.os, 'ubuntu') - name: Install rust stable uses: actions-rs/toolchain@v1 From 2f02e0bf5c3ac6a1be47f940198c11b1d8bd191b Mon Sep 17 00:00:00 2001 From: William Light Date: Fri, 11 Sep 2020 18:07:02 +0200 Subject: [PATCH 10/10] README: update Discord channel name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 83a2ccb..4823408 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A low-level windowing system geared towards making audio plugin UIs. `baseview` abstracts the platform-specific windowing APIs (winapi, cocoa, xcb) into a platform-independent API, but otherwise gets out of your way so you can write plugin UIs. -Interested in learning more about the project? Join us on [discord](https://discord.gg/b3hjnGw), channel `#vst2-gui`. +Interested in learning more about the project? Join us on [discord](https://discord.gg/b3hjnGw), channel `#plugin-gui`. ## Roadmap