From 1274b1c08ff21b454cba32b82ffbc1ccd8871239 Mon Sep 17 00:00:00 2001 From: Fredemus Date: Mon, 2 Oct 2023 02:58:07 +0200 Subject: [PATCH 1/7] Add logic for CursorEntered/CursorLeft on Windows --- src/win/window.rs | 53 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index 2c68b99c..7959ad7c 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -2,23 +2,23 @@ use winapi::shared::guiddef::GUID; use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM}; use winapi::shared::windef::{HWND, RECT}; use winapi::um::combaseapi::CoCreateGuid; -use winapi::um::ole2::{RegisterDragDrop, OleInitialize, RevokeDragDrop}; +use winapi::um::ole2::{OleInitialize, RegisterDragDrop, RevokeDragDrop}; use winapi::um::oleidl::LPDROPTARGET; use winapi::um::winuser::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetDpiForWindow, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, RegisterClassW, ReleaseCapture, SetCapture, SetProcessDpiAwarenessContext, SetTimer, SetWindowLongPtrW, - SetWindowPos, TranslateMessage, UnregisterClassW, CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA, - IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, - WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, - WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, - WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, - WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, - WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, - XBUTTON1, XBUTTON2, + SetWindowPos, TrackMouseEvent, TranslateMessage, UnregisterClassW, CS_OWNDC, + GET_XBUTTON_WPARAM, GWLP_USERDATA, IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, TRACKMOUSEEVENT, + WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN, + WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, + WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, WM_RBUTTONDOWN, WM_RBUTTONUP, + WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER, + WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, + WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, XBUTTON1, XBUTTON2, }; -use std::cell::{Cell, RefCell, Ref, RefMut}; +use std::cell::{Cell, Ref, RefCell, RefMut}; use std::collections::VecDeque; use std::ffi::{c_void, OsStr}; use std::marker::PhantomData; @@ -175,22 +175,51 @@ unsafe fn wnd_proc_inner( WM_MOUSEMOVE => { let mut window = window_state.create_window(); let mut window = crate::Window::new(&mut window); + let mut handler = window_state.handler.borrow_mut(); + let handler = handler.as_mut().unwrap(); + + let mut mouse_was_outside_window = window_state.mouse_was_outside_window.borrow_mut(); + if *mouse_was_outside_window { + // this makes Windows track whether the mouse leaves the window. + // When the mouse leaves it results in a `WM_MOUSELEAVE` event. + let track_mouse = Rc::new(TRACKMOUSEEVENT { + cbSize: std::mem::size_of::() as u32, + dwFlags: winapi::um::winuser::TME_LEAVE, + hwndTrack: hwnd, + dwHoverTime: winapi::um::winuser::HOVER_DEFAULT, + }); + // Couldn't find a good way to track whether the mouse enters, + // but if `WM_MOUSEMOVE` happens, the mouse must have entered. + TrackMouseEvent(Rc::as_ptr(&track_mouse) as winapi::um::winuser::LPTRACKMOUSEEVENT); + *mouse_was_outside_window = false; + + let enter_event = Event::Mouse(MouseEvent::CursorEntered); + handler.on_event(&mut window, enter_event); + } let x = (lparam & 0xFFFF) as i16 as i32; let y = ((lparam >> 16) & 0xFFFF) as i16 as i32; let physical_pos = PhyPoint { x, y }; let logical_pos = physical_pos.to_logical(&window_state.window_info.borrow()); - let event = Event::Mouse(MouseEvent::CursorMoved { + let move_event = Event::Mouse(MouseEvent::CursorMoved { position: logical_pos, modifiers: window_state .keyboard_state .borrow() .get_modifiers_from_mouse_wparam(wparam), }); + handler.on_event(&mut window, move_event); + Some(0) + } + WM_MOUSELEAVE => { + let mut window = window_state.create_window(); + let mut window = crate::Window::new(&mut window); + let event = Event::Mouse(MouseEvent::CursorLeft); window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); + *window_state.mouse_was_outside_window.borrow_mut() = true; Some(0) } WM_MOUSEWHEEL | WM_MOUSEHWHEEL => { @@ -460,6 +489,7 @@ pub(super) struct WindowState { _parent_handle: Option, keyboard_state: RefCell, mouse_button_counter: Cell, + mouse_was_outside_window: RefCell, // Initialized late so the `Window` can hold a reference to this `WindowState` handler: RefCell>>, _drop_target: RefCell>>, @@ -678,6 +708,7 @@ impl Window<'_> { _parent_handle: parent_handle, keyboard_state: RefCell::new(KeyboardState::new()), mouse_button_counter: Cell::new(0), + mouse_was_outside_window: RefCell::new(true), // The Window refers to this `WindowState`, so this `handler` needs to be // initialized later handler: RefCell::new(None), From 475bd5f88a2e2ea72601039f69b68c0380c9d9e0 Mon Sep 17 00:00:00 2001 From: Fredemus Date: Sat, 28 Oct 2023 17:35:26 +0200 Subject: [PATCH 2/7] remove unneeded allocation --- src/win/window.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index 7959ad7c..0a92ba80 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -182,15 +182,15 @@ unsafe fn wnd_proc_inner( if *mouse_was_outside_window { // this makes Windows track whether the mouse leaves the window. // When the mouse leaves it results in a `WM_MOUSELEAVE` event. - let track_mouse = Rc::new(TRACKMOUSEEVENT { + let mut track_mouse =TRACKMOUSEEVENT { cbSize: std::mem::size_of::() as u32, dwFlags: winapi::um::winuser::TME_LEAVE, hwndTrack: hwnd, dwHoverTime: winapi::um::winuser::HOVER_DEFAULT, - }); + }; // Couldn't find a good way to track whether the mouse enters, // but if `WM_MOUSEMOVE` happens, the mouse must have entered. - TrackMouseEvent(Rc::as_ptr(&track_mouse) as winapi::um::winuser::LPTRACKMOUSEEVENT); + TrackMouseEvent(&mut track_mouse); *mouse_was_outside_window = false; let enter_event = Event::Mouse(MouseEvent::CursorEntered); From 998ced845c83002712cb414f97bdb05566b1ff13 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 24 Mar 2024 23:16:16 +0100 Subject: [PATCH 3/7] Added functional open_parented example (#172) This PR adds a simple example that allows to test and showcase the `Window::open_parented` method. That example first creates a parent window using `Window::open_blocking`, and then creates a smaller child window using `Window::open_parented`. Both window's handlers log all of their events to the console, in a similar fashion to the `open_window` example. Both windows actually do rendering (unlike the `open_window` example for now): the parent fills its window with a grey backround, and the child fills its window with a red background. This example also uses the `softbuffer` crate to perform the rendering, which allows testing it in a more portable manner and in the simplest use case possible, without having to involve OpenGL or any 3D rendering pipeline at all. --- Cargo.toml | 1 + examples/open_parented.rs | 141 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 examples/open_parented.rs diff --git a/Cargo.toml b/Cargo.toml index bd8d9d83..f4104f54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,4 @@ uuid = { version = "0.8", features = ["v4"] } [dev-dependencies] rtrb = "0.2" +softbuffer = "0.3.4" diff --git a/examples/open_parented.rs b/examples/open_parented.rs new file mode 100644 index 00000000..812c49ab --- /dev/null +++ b/examples/open_parented.rs @@ -0,0 +1,141 @@ +use baseview::{ + Event, EventStatus, PhySize, Window, WindowEvent, WindowHandle, WindowHandler, + WindowScalePolicy, +}; +use std::num::NonZeroU32; + +struct ParentWindowHandler { + ctx: softbuffer::Context, + surface: softbuffer::Surface, + current_size: PhySize, + damaged: bool, + + child_window: Option, +} + +impl ParentWindowHandler { + pub fn new(window: &mut Window) -> Self { + let ctx = unsafe { softbuffer::Context::new(window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&ctx, window) }.unwrap(); + surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap(); + + let window_open_options = baseview::WindowOpenOptions { + title: "baseview child".into(), + size: baseview::Size::new(256.0, 256.0), + scale: WindowScalePolicy::SystemScaleFactor, + + // TODO: Add an example that uses the OpenGL context + #[cfg(feature = "opengl")] + gl_config: None, + }; + let child_window = + Window::open_parented(window, window_open_options, ChildWindowHandler::new); + + // TODO: no way to query physical size initially? + Self { + ctx, + surface, + current_size: PhySize::new(512, 512), + damaged: true, + child_window: Some(child_window), + } + } +} + +impl WindowHandler for ParentWindowHandler { + fn on_frame(&mut self, _window: &mut Window) { + let mut buf = self.surface.buffer_mut().unwrap(); + if self.damaged { + buf.fill(0xFFAAAAAA); + self.damaged = false; + } + buf.present().unwrap(); + } + + fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + match event { + Event::Window(WindowEvent::Resized(info)) => { + println!("Parent Resized: {:?}", info); + let new_size = info.physical_size(); + self.current_size = new_size; + + if let (Some(width), Some(height)) = + (NonZeroU32::new(new_size.width), NonZeroU32::new(new_size.height)) + { + self.surface.resize(width, height).unwrap(); + self.damaged = true; + } + } + Event::Mouse(e) => println!("Parent Mouse event: {:?}", e), + Event::Keyboard(e) => println!("Parent Keyboard event: {:?}", e), + Event::Window(e) => println!("Parent Window event: {:?}", e), + } + + EventStatus::Captured + } +} + +struct ChildWindowHandler { + ctx: softbuffer::Context, + surface: softbuffer::Surface, + current_size: PhySize, + damaged: bool, +} + +impl ChildWindowHandler { + pub fn new(window: &mut Window) -> Self { + let ctx = unsafe { softbuffer::Context::new(window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&ctx, window) }.unwrap(); + surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap(); + + // TODO: no way to query physical size initially? + Self { ctx, surface, current_size: PhySize::new(256, 256), damaged: true } + } +} + +impl WindowHandler for ChildWindowHandler { + fn on_frame(&mut self, _window: &mut Window) { + let mut buf = self.surface.buffer_mut().unwrap(); + if self.damaged { + buf.fill(0xFFAA0000); + self.damaged = false; + } + buf.present().unwrap(); + } + + fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + match event { + Event::Window(WindowEvent::Resized(info)) => { + println!("Child Resized: {:?}", info); + let new_size = info.physical_size(); + self.current_size = new_size; + + if let (Some(width), Some(height)) = + (NonZeroU32::new(new_size.width), NonZeroU32::new(new_size.height)) + { + self.surface.resize(width, height).unwrap(); + self.damaged = true; + } + } + Event::Mouse(e) => println!("Child Mouse event: {:?}", e), + Event::Keyboard(e) => println!("Child Keyboard event: {:?}", e), + Event::Window(e) => println!("Child Window event: {:?}", e), + } + + EventStatus::Captured + } +} + +fn main() { + let window_open_options = baseview::WindowOpenOptions { + title: "baseview".into(), + size: baseview::Size::new(512.0, 512.0), + scale: WindowScalePolicy::SystemScaleFactor, + + // TODO: Add an example that uses the OpenGL context + #[cfg(feature = "opengl")] + gl_config: None, + }; + + Window::open_blocking(window_open_options, ParentWindowHandler::new); +} From fdc5d282fcce517bff65bfed6a56d51c05003d2e Mon Sep 17 00:00:00 2001 From: Micah Johnston Date: Mon, 25 Mar 2024 11:20:28 -0500 Subject: [PATCH 4/7] Switch from xcb crate to x11rb (#173) Replace the `xcb` and `xcb-util` crates with `x11rb`. We were using an old version of the `xcb` crate which had some soundness issue. `x11rb` doesn't have these issues and generally provides a safer and nicer to use API. It's possible to use `x11rb` without linking to xcb at all, using the `RustConnection` API, but unfortunately we have to use the `XCBConnection` API (which uses xcb under the hood) due to our use of the xlib GLX API for creating OpenGL contexts. In the future, it might be possible to avoid linking to xlib and xcb by replacing GLX with EGL. Getting the xlib-xcb integration to work also necessitated upgrading the version of the `x11` crate, since the version we were using was missing some necessary functionality that was previously being provided by the `xcb` crate. --- Cargo.toml | 5 +- src/x11/cursor.rs | 167 +++++++++--------- src/x11/keyboard.rs | 36 ++-- src/x11/window.rs | 350 +++++++++++++++++--------------------- src/x11/xcb_connection.rs | 164 ++++++++---------- 5 files changed, 323 insertions(+), 399 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4104f54..bed2c954 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,8 @@ keyboard-types = { version = "0.6.1", default-features = false } raw-window-handle = "0.5" [target.'cfg(target_os="linux")'.dependencies] -xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] } -x11 = { version = "2.18", features = ["xlib", "xcursor"] } -xcb-util = { version = "0.3", features = ["icccm"] } +x11rb = { version = "0.13.0", features = ["cursor", "resource_manager", "allow-unsafe-code"] } +x11 = { version = "2.21", features = ["xlib", "xcursor", "xlib_xcb"] } nix = "0.22.0" [target.'cfg(target_os="windows")'.dependencies] diff --git a/src/x11/cursor.rs b/src/x11/cursor.rs index 8e8fd88c..56ff0d2a 100644 --- a/src/x11/cursor.rs +++ b/src/x11/cursor.rs @@ -1,105 +1,100 @@ -use std::os::raw::c_char; +use std::error::Error; + +use x11rb::connection::Connection; +use x11rb::cursor::Handle as CursorHandle; +use x11rb::protocol::xproto::{ConnectionExt as _, Cursor}; +use x11rb::xcb_ffi::XCBConnection; use crate::MouseCursor; -fn create_empty_cursor(display: *mut x11::xlib::Display) -> Option { - let data = 0; - let pixmap = unsafe { - let screen = x11::xlib::XDefaultScreen(display); - let window = x11::xlib::XRootWindow(display, screen); - x11::xlib::XCreateBitmapFromData(display, window, &data, 1, 1) - }; +fn create_empty_cursor(conn: &XCBConnection, screen: usize) -> Result> { + let cursor_id = conn.generate_id()?; + let pixmap_id = conn.generate_id()?; + let root_window = conn.setup().roots[screen].root; + conn.create_pixmap(1, pixmap_id, root_window, 1, 1)?; + conn.create_cursor(cursor_id, pixmap_id, pixmap_id, 0, 0, 0, 0, 0, 0, 0, 0)?; + conn.free_pixmap(pixmap_id)?; - if pixmap == 0 { - return None; - } - - unsafe { - // We don't care about this color, since it only fills bytes - // in the pixmap which are not 0 in the mask. - let mut color: x11::xlib::XColor = std::mem::zeroed(); - - let cursor = x11::xlib::XCreatePixmapCursor( - display, - pixmap, - pixmap, - &mut color as *mut _, - &mut color as *mut _, - 0, - 0, - ); - x11::xlib::XFreePixmap(display, pixmap); - - Some(cursor as u32) - } + Ok(cursor_id) } -fn load_cursor(display: *mut x11::xlib::Display, name: &[u8]) -> Option { - let xcursor = - unsafe { x11::xcursor::XcursorLibraryLoadCursor(display, name.as_ptr() as *const c_char) }; - - if xcursor == 0 { - None +fn load_cursor( + conn: &XCBConnection, cursor_handle: &CursorHandle, name: &str, +) -> Result, Box> { + let cursor = cursor_handle.load_cursor(conn, name)?; + if cursor != x11rb::NONE { + Ok(Some(cursor)) } else { - Some(xcursor as u32) + Ok(None) } } -fn load_first_existing_cursor(display: *mut x11::xlib::Display, names: &[&[u8]]) -> Option { - names - .iter() - .map(|name| load_cursor(display, name)) - .find(|xcursor| xcursor.is_some()) - .unwrap_or(None) +fn load_first_existing_cursor( + conn: &XCBConnection, cursor_handle: &CursorHandle, names: &[&str], +) -> Result, Box> { + for name in names { + let cursor = load_cursor(conn, cursor_handle, name)?; + if cursor.is_some() { + return Ok(cursor); + } + } + + Ok(None) } -pub(super) fn get_xcursor(display: *mut x11::xlib::Display, cursor: MouseCursor) -> u32 { - let load = |name: &[u8]| load_cursor(display, name); - let loadn = |names: &[&[u8]]| load_first_existing_cursor(display, names); +pub(super) fn get_xcursor( + conn: &XCBConnection, screen: usize, cursor_handle: &CursorHandle, cursor: MouseCursor, +) -> Result> { + let load = |name: &str| load_cursor(conn, cursor_handle, name); + let loadn = |names: &[&str]| load_first_existing_cursor(conn, cursor_handle, names); let cursor = match cursor { MouseCursor::Default => None, // catch this in the fallback case below - MouseCursor::Hand => loadn(&[b"hand2\0", b"hand1\0"]), - MouseCursor::HandGrabbing => loadn(&[b"closedhand\0", b"grabbing\0"]), - MouseCursor::Help => load(b"question_arrow\0"), - - MouseCursor::Hidden => create_empty_cursor(display), - - MouseCursor::Text => loadn(&[b"text\0", b"xterm\0"]), - MouseCursor::VerticalText => load(b"vertical-text\0"), - - MouseCursor::Working => load(b"watch\0"), - MouseCursor::PtrWorking => load(b"left_ptr_watch\0"), - - MouseCursor::NotAllowed => load(b"crossed_circle\0"), - MouseCursor::PtrNotAllowed => loadn(&[b"no-drop\0", b"crossed_circle\0"]), - - MouseCursor::ZoomIn => load(b"zoom-in\0"), - MouseCursor::ZoomOut => load(b"zoom-out\0"), - - MouseCursor::Alias => load(b"link\0"), - MouseCursor::Copy => load(b"copy\0"), - MouseCursor::Move => load(b"move\0"), - MouseCursor::AllScroll => load(b"all-scroll\0"), - MouseCursor::Cell => load(b"plus\0"), - MouseCursor::Crosshair => load(b"crosshair\0"), - - MouseCursor::EResize => load(b"right_side\0"), - MouseCursor::NResize => load(b"top_side\0"), - MouseCursor::NeResize => load(b"top_right_corner\0"), - MouseCursor::NwResize => load(b"top_left_corner\0"), - MouseCursor::SResize => load(b"bottom_side\0"), - MouseCursor::SeResize => load(b"bottom_right_corner\0"), - MouseCursor::SwResize => load(b"bottom_left_corner\0"), - MouseCursor::WResize => load(b"left_side\0"), - MouseCursor::EwResize => load(b"h_double_arrow\0"), - MouseCursor::NsResize => load(b"v_double_arrow\0"), - MouseCursor::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]), - MouseCursor::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]), - MouseCursor::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]), - MouseCursor::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]), + MouseCursor::Hand => loadn(&["hand2", "hand1"])?, + MouseCursor::HandGrabbing => loadn(&["closedhand", "grabbing"])?, + MouseCursor::Help => load("question_arrow")?, + + MouseCursor::Hidden => Some(create_empty_cursor(conn, screen)?), + + MouseCursor::Text => loadn(&["text", "xterm"])?, + MouseCursor::VerticalText => load("vertical-text")?, + + MouseCursor::Working => load("watch")?, + MouseCursor::PtrWorking => load("left_ptr_watch")?, + + MouseCursor::NotAllowed => load("crossed_circle")?, + MouseCursor::PtrNotAllowed => loadn(&["no-drop", "crossed_circle"])?, + + MouseCursor::ZoomIn => load("zoom-in")?, + MouseCursor::ZoomOut => load("zoom-out")?, + + MouseCursor::Alias => load("link")?, + MouseCursor::Copy => load("copy")?, + MouseCursor::Move => load("move")?, + MouseCursor::AllScroll => load("all-scroll")?, + MouseCursor::Cell => load("plus")?, + MouseCursor::Crosshair => load("crosshair")?, + + MouseCursor::EResize => load("right_side")?, + MouseCursor::NResize => load("top_side")?, + MouseCursor::NeResize => load("top_right_corner")?, + MouseCursor::NwResize => load("top_left_corner")?, + MouseCursor::SResize => load("bottom_side")?, + MouseCursor::SeResize => load("bottom_right_corner")?, + MouseCursor::SwResize => load("bottom_left_corner")?, + MouseCursor::WResize => load("left_side")?, + MouseCursor::EwResize => load("h_double_arrow")?, + MouseCursor::NsResize => load("v_double_arrow")?, + MouseCursor::NwseResize => loadn(&["bd_double_arrow", "size_bdiag"])?, + MouseCursor::NeswResize => loadn(&["fd_double_arrow", "size_fdiag"])?, + MouseCursor::ColResize => loadn(&["split_h", "h_double_arrow"])?, + MouseCursor::RowResize => loadn(&["split_v", "v_double_arrow"])?, }; - cursor.or_else(|| load(b"left_ptr\0")).unwrap_or(0) + if let Some(cursor) = cursor { + Ok(cursor) + } else { + Ok(load("left_ptr")?.unwrap_or(x11rb::NONE)) + } } diff --git a/src/x11/keyboard.rs b/src/x11/keyboard.rs index 32201282..4985e641 100644 --- a/src/x11/keyboard.rs +++ b/src/x11/keyboard.rs @@ -18,7 +18,7 @@ //! X11 keyboard handling -use xcb::xproto; +use x11rb::protocol::xproto::{KeyButMask, KeyPressEvent, KeyReleaseEvent}; use keyboard_types::*; @@ -361,32 +361,32 @@ fn hardware_keycode_to_code(hw_keycode: u16) -> Code { } // Extracts the keyboard modifiers from, e.g., the `state` field of -// `xcb::xproto::ButtonPressEvent` -pub(super) fn key_mods(mods: u16) -> Modifiers { +// `x11rb::protocol::xproto::ButtonPressEvent` +pub(super) fn key_mods(mods: KeyButMask) -> Modifiers { let mut ret = Modifiers::default(); - let mut key_masks = [ - (xproto::MOD_MASK_SHIFT, Modifiers::SHIFT), - (xproto::MOD_MASK_CONTROL, Modifiers::CONTROL), + let key_masks = [ + (KeyButMask::SHIFT, Modifiers::SHIFT), + (KeyButMask::CONTROL, Modifiers::CONTROL), // X11's mod keys are configurable, but this seems // like a reasonable default for US keyboards, at least, // where the "windows" key seems to be MOD_MASK_4. - (xproto::MOD_MASK_1, Modifiers::ALT), - (xproto::MOD_MASK_2, Modifiers::NUM_LOCK), - (xproto::MOD_MASK_4, Modifiers::META), - (xproto::MOD_MASK_LOCK, Modifiers::CAPS_LOCK), + (KeyButMask::BUTTON1, Modifiers::ALT), + (KeyButMask::BUTTON2, Modifiers::NUM_LOCK), + (KeyButMask::BUTTON4, Modifiers::META), + (KeyButMask::LOCK, Modifiers::CAPS_LOCK), ]; - for (mask, modifiers) in &mut key_masks { - if mods & (*mask as u16) != 0 { + for (mask, modifiers) in &key_masks { + if mods.contains(*mask) { ret |= *modifiers; } } ret } -pub(super) fn convert_key_press_event(key_press: &xcb::KeyPressEvent) -> KeyboardEvent { - let hw_keycode = key_press.detail(); +pub(super) fn convert_key_press_event(key_press: &KeyPressEvent) -> KeyboardEvent { + let hw_keycode = key_press.detail; let code = hardware_keycode_to_code(hw_keycode.into()); - let modifiers = key_mods(key_press.state()); + let modifiers = key_mods(key_press.state); let key = code_to_key(code, modifiers); let location = code_to_location(code); let state = KeyState::Down; @@ -394,10 +394,10 @@ pub(super) fn convert_key_press_event(key_press: &xcb::KeyPressEvent) -> Keyboar KeyboardEvent { code, key, modifiers, location, state, repeat: false, is_composing: false } } -pub(super) fn convert_key_release_event(key_release: &xcb::KeyReleaseEvent) -> KeyboardEvent { - let hw_keycode = key_release.detail(); +pub(super) fn convert_key_release_event(key_release: &KeyReleaseEvent) -> KeyboardEvent { + let hw_keycode = key_release.detail; let code = hardware_keycode_to_code(hw_keycode.into()); - let modifiers = key_mods(key_release.state()); + let modifiers = key_mods(key_release.state); let key = code_to_key(code, modifiers); let location = code_to_location(code); let state = KeyState::Up; diff --git a/src/x11/window.rs b/src/x11/window.rs index 90ec281f..fae1f69f 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -1,4 +1,6 @@ +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; @@ -9,8 +11,15 @@ use raw_window_handle::{ HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, XlibWindowHandle, }; -use xcb::ffi::xcb_screen_t; -use xcb::StructPtr; + +use x11rb::connection::Connection; +use x11rb::protocol::xproto::{ + AtomEnum, ChangeWindowAttributesAux, ColormapAlloc, ConfigureWindowAux, ConnectionExt as _, + CreateGCAux, CreateWindowAux, EventMask, PropMode, Screen, VisualClass, Visualid, + Window as XWindow, WindowClass, +}; +use x11rb::protocol::Event as XEvent; +use x11rb::wrapper::ConnectionExt as _; use super::XcbConnection; use crate::{ @@ -89,9 +98,9 @@ impl Drop for ParentHandle { struct WindowInner { xcb_connection: XcbConnection, - window_id: u32, + window_id: XWindow, window_info: WindowInfo, - visual_id: u32, + visual_id: Visualid, mouse_cursor: MouseCursor, frame_interval: Duration, @@ -136,7 +145,8 @@ impl<'a> Window<'a> { let (parent_handle, mut window_handle) = ParentHandle::new(); thread::spawn(move || { - Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle)); + Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle)) + .unwrap(); }); let raw_window_handle = rx.recv().unwrap().unwrap(); @@ -154,7 +164,7 @@ impl<'a> Window<'a> { let (tx, rx) = mpsc::sync_channel::(1); let thread = thread::spawn(move || { - Self::window_thread(None, options, build, tx, None); + Self::window_thread(None, options, build, tx, None).unwrap(); }); let _ = rx.recv().unwrap().unwrap(); @@ -167,29 +177,28 @@ impl<'a> Window<'a> { fn window_thread( parent: Option, options: WindowOpenOptions, build: B, tx: mpsc::SyncSender, parent_handle: Option, - ) where + ) -> Result<(), Box> + where H: WindowHandler + 'static, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static, { // Connect to the X server // FIXME: baseview error type instead of unwrap() - let xcb_connection = XcbConnection::new().unwrap(); + let xcb_connection = XcbConnection::new()?; // Get screen information (?) - let setup = xcb_connection.conn.get_setup(); - let screen = setup.roots().nth(xcb_connection.xlib_display as usize).unwrap(); + let setup = xcb_connection.conn2.setup(); + let screen = &setup.roots[xcb_connection.screen]; - let foreground = xcb_connection.conn.generate_id(); + let parent_id = parent.unwrap_or_else(|| screen.root); - let parent_id = parent.unwrap_or_else(|| screen.root()); - - xcb::create_gc( - &xcb_connection.conn, - foreground, + let gc_id = xcb_connection.conn2.generate_id()?; + xcb_connection.conn2.create_gc( + gc_id, parent_id, - &[(xcb::GC_FOREGROUND, screen.black_pixel()), (xcb::GC_GRAPHICS_EXPOSURES, 0)], - ); + &CreateGCAux::new().foreground(screen.black_pixel).graphics_exposures(0), + )?; let scaling = match options.scale { WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling().unwrap_or(1.0), @@ -205,21 +214,18 @@ impl<'a> Window<'a> { // with that visual, and then finally create an OpenGL context for the window. If we don't // use OpenGL, then we'll just take a random visual with a 32-bit depth. let create_default_config = || { - Self::find_visual_for_depth(&screen, 32) + Self::find_visual_for_depth(screen, 32) .map(|visual| (32, visual)) - .unwrap_or((xcb::COPY_FROM_PARENT as u8, xcb::COPY_FROM_PARENT as u32)) + .unwrap_or((x11rb::COPY_FROM_PARENT as u8, x11rb::COPY_FROM_PARENT as u32)) }; #[cfg(feature = "opengl")] let (fb_config, (depth, visual)) = match options.gl_config { Some(gl_config) => unsafe { - platform::GlContext::get_fb_config_and_visual( - xcb_connection.conn.get_raw_dpy(), - gl_config, - ) - .map(|(fb_config, window_config)| { - (Some(fb_config), (window_config.depth, window_config.visual)) - }) - .expect("Could not fetch framebuffer config") + platform::GlContext::get_fb_config_and_visual(xcb_connection.dpy, gl_config) + .map(|(fb_config, window_config)| { + (Some(fb_config), (window_config.depth, window_config.visual)) + }) + .expect("Could not fetch framebuffer config") }, None => (None, create_default_config()), }; @@ -228,18 +234,11 @@ impl<'a> Window<'a> { // For this 32-bith depth to work, you also need to define a color map and set a border // pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818 - let colormap = xcb_connection.conn.generate_id(); - xcb::create_colormap( - &xcb_connection.conn, - xcb::COLORMAP_ALLOC_NONE as u8, - colormap, - screen.root(), - visual, - ); + let colormap = xcb_connection.conn2.generate_id()?; + xcb_connection.conn2.create_colormap(ColormapAlloc::NONE, colormap, screen.root, visual)?; - let window_id = xcb_connection.conn.generate_id(); - xcb::create_window_checked( - &xcb_connection.conn, + let window_id = xcb_connection.conn2.generate_id()?; + xcb_connection.conn2.create_window( depth, window_id, parent_id, @@ -248,56 +247,46 @@ impl<'a> Window<'a> { window_info.physical_size().width as u16, // window width window_info.physical_size().height as u16, // window height 0, // window border - xcb::WINDOW_CLASS_INPUT_OUTPUT as u16, + WindowClass::INPUT_OUTPUT, visual, - &[ - ( - xcb::CW_EVENT_MASK, - xcb::EVENT_MASK_EXPOSURE - | xcb::EVENT_MASK_POINTER_MOTION - | xcb::EVENT_MASK_BUTTON_PRESS - | xcb::EVENT_MASK_BUTTON_RELEASE - | xcb::EVENT_MASK_KEY_PRESS - | xcb::EVENT_MASK_KEY_RELEASE - | xcb::EVENT_MASK_STRUCTURE_NOTIFY - | xcb::EVENT_MASK_ENTER_WINDOW - | xcb::EVENT_MASK_LEAVE_WINDOW, - ), + &CreateWindowAux::new() + .event_mask( + EventMask::EXPOSURE + | EventMask::POINTER_MOTION + | EventMask::BUTTON_PRESS + | EventMask::BUTTON_RELEASE + | EventMask::KEY_PRESS + | EventMask::KEY_RELEASE + | EventMask::STRUCTURE_NOTIFY + | EventMask::ENTER_WINDOW + | EventMask::LEAVE_WINDOW, + ) // As mentioned above, these two values are needed to be able to create a window // with a depth of 32-bits when the parent window has a different depth - (xcb::CW_COLORMAP, colormap), - (xcb::CW_BORDER_PIXEL, 0), - ], - ) - .request_check() - .unwrap(); - - xcb::map_window(&xcb_connection.conn, window_id); + .colormap(colormap) + .border_pixel(0), + )?; + xcb_connection.conn2.map_window(window_id)?; // Change window title let title = options.title; - xcb::change_property( - &xcb_connection.conn, - xcb::PROP_MODE_REPLACE as u8, + xcb_connection.conn2.change_property8( + PropMode::REPLACE, window_id, - xcb::ATOM_WM_NAME, - xcb::ATOM_STRING, - 8, // view data as 8-bit + AtomEnum::WM_NAME, + AtomEnum::STRING, title.as_bytes(), - ); + )?; - if let Some((wm_protocols, wm_delete_window)) = - xcb_connection.atoms.wm_protocols.zip(xcb_connection.atoms.wm_delete_window) - { - xcb_util::icccm::set_wm_protocols( - &xcb_connection.conn, - window_id, - wm_protocols, - &[wm_delete_window], - ); - } + xcb_connection.conn2.change_property32( + PropMode::REPLACE, + window_id, + xcb_connection.atoms2.WM_PROTOCOLS, + AtomEnum::ATOM, + &[xcb_connection.atoms2.WM_DELETE_WINDOW], + )?; - xcb_connection.conn.flush(); + xcb_connection.conn2.flush()?; // TODO: These APIs could use a couple tweaks now that everything is internal and there is // no error handling anymore at this point. Everything is more or less unchanged @@ -307,7 +296,7 @@ impl<'a> Window<'a> { use std::ffi::c_ulong; let window = window_id as c_ulong; - let display = xcb_connection.conn.get_raw_dpy(); + let display = xcb_connection.dpy; // Because of the visual negotation we had to take some extra steps to create this context let context = unsafe { platform::GlContext::create(window, display, fb_config) } @@ -343,7 +332,9 @@ impl<'a> Window<'a> { let _ = tx.send(Ok(SendableRwh(window.raw_window_handle()))); - inner.run_event_loop(&mut handler); + inner.run_event_loop(&mut handler)?; + + Ok(()) } pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) { @@ -351,16 +342,14 @@ impl<'a> Window<'a> { return; } - let xid = self.inner.xcb_connection.get_cursor_xid(mouse_cursor); + let xid = self.inner.xcb_connection.get_cursor(mouse_cursor).unwrap(); if xid != 0 { - xcb::change_window_attributes( - &self.inner.xcb_connection.conn, + let _ = self.inner.xcb_connection.conn2.change_window_attributes( self.inner.window_id, - &[(xcb::CW_CURSOR, xid)], + &ChangeWindowAttributesAux::new().cursor(xid), ); - - self.inner.xcb_connection.conn.flush(); + let _ = self.inner.xcb_connection.conn2.flush(); } self.inner.mouse_cursor = mouse_cursor; @@ -374,15 +363,13 @@ impl<'a> Window<'a> { let scaling = self.inner.window_info.scale(); let new_window_info = WindowInfo::from_logical_size(size, scaling); - xcb::configure_window( - &self.inner.xcb_connection.conn, + let _ = self.inner.xcb_connection.conn2.configure_window( self.inner.window_id, - &[ - (xcb::CONFIG_WINDOW_WIDTH as u16, new_window_info.physical_size().width), - (xcb::CONFIG_WINDOW_HEIGHT as u16, new_window_info.physical_size().height), - ], + &ConfigureWindowAux::new() + .width(new_window_info.physical_size().width) + .height(new_window_info.physical_size().height), ); - self.inner.xcb_connection.conn.flush(); + let _ = self.inner.xcb_connection.conn2.flush(); // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` // and notify the window handler about it @@ -393,15 +380,15 @@ impl<'a> Window<'a> { self.inner.gl_context.as_ref() } - fn find_visual_for_depth(screen: &StructPtr, depth: u8) -> Option { - for candidate_depth in screen.allowed_depths() { - if candidate_depth.depth() != depth { + fn find_visual_for_depth(screen: &Screen, depth: u8) -> Option { + for candidate_depth in &screen.allowed_depths { + if candidate_depth.depth != depth { continue; } - for candidate_visual in candidate_depth.visuals() { - if candidate_visual.class() == xcb::VISUAL_CLASS_TRUE_COLOR as u8 { - return Some(candidate_visual.visual_id()); + for candidate_visual in &candidate_depth.visuals { + if candidate_visual.class == VisualClass::TRUE_COLOR { + return Some(candidate_visual.visual_id); } } } @@ -412,13 +399,13 @@ impl<'a> Window<'a> { impl WindowInner { #[inline] - fn drain_xcb_events(&mut self, handler: &mut dyn WindowHandler) { + 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() { + while let Some(event) = self.xcb_connection.conn2.poll_for_event()? { self.handle_xcb_event(handler, event); } @@ -432,19 +419,18 @@ impl WindowInner { 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) { + fn run_event_loop(&mut self, handler: &mut dyn WindowHandler) -> Result<(), Box> { 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) - }; + let xcb_fd = self.xcb_connection.conn2.as_raw_fd(); let mut last_frame = Instant::now(); self.event_loop_running = true; @@ -466,7 +452,7 @@ impl WindowInner { // Check for any events in the internal buffers // before going to sleep: - self.drain_xcb_events(handler); + self.drain_xcb_events(handler)?; // FIXME: handle errors poll(&mut fds, next_frame.duration_since(Instant::now()).subsec_millis() as i32) @@ -478,7 +464,7 @@ impl WindowInner { } if revents.contains(PollFlags::POLLIN) { - self.drain_xcb_events(handler); + self.drain_xcb_events(handler)?; } } @@ -501,6 +487,8 @@ impl WindowInner { self.close_requested = false; } } + + Ok(()) } fn handle_close_requested(&mut self, handler: &mut dyn WindowHandler) { @@ -522,9 +510,7 @@ impl WindowInner { self.event_loop_running = false; } - fn handle_xcb_event(&mut self, handler: &mut dyn WindowHandler, event: xcb::GenericEvent) { - let event_type = event.response_type() & !0x80; - + 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 @@ -545,29 +531,20 @@ impl WindowInner { // 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 { + match event { //// // window //// - xcb::CLIENT_MESSAGE => { - let event = unsafe { xcb::cast_event::(&event) }; - - // what an absolute tragedy 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] { + XEvent::ClientMessage(event) => { + if event.format == 32 + && event.data.as_data32()[0] == self.xcb_connection.atoms2.WM_DELETE_WINDOW + { self.handle_close_requested(handler); } } - xcb::CONFIGURE_NOTIFY => { - let event = unsafe { xcb::cast_event::(&event) }; - - let new_physical_size = PhySize::new(event.width() as u32, event.height() as u32); + 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() @@ -579,95 +556,80 @@ impl WindowInner { //// // mouse //// - xcb::MOTION_NOTIFY => { - let event = unsafe { xcb::cast_event::(&event) }; - let detail = event.detail(); - - if detail != 4 && detail != 5 { - 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); + 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()), - }), - ); - } + handler.on_event( + &mut crate::Window::new(Window { inner: self }), + Event::Mouse(MouseEvent::CursorMoved { + position: logical_pos, + modifiers: key_mods(event.state), + }), + ); } - xcb::ENTER_NOTIFY => { + 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 event = unsafe { xcb::cast_event::(&event) }; - let physical_pos = PhyPoint::new(event.event_x() as i32, event.event_y() as i32); + 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()), + modifiers: key_mods(event.state), }), ); } - xcb::LEAVE_NOTIFY => { + XEvent::LeaveNotify(_) => { handler.on_event( &mut crate::Window::new(Window { inner: self }), Event::Mouse(MouseEvent::CursorLeft), ); } - xcb::BUTTON_PRESS => { - let event = unsafe { xcb::cast_event::(&event) }; - let detail = event.detail(); - - match detail { - 4..=7 => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::WheelScrolled { - delta: match 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::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), + }), + ); } - } - - xcb::BUTTON_RELEASE => { - let event = unsafe { xcb::cast_event::(&event) }; - let detail = event.detail(); - - if !(4..=7).contains(&detail) { + 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()), + modifiers: key_mods(event.state), }), ); } @@ -676,21 +638,17 @@ impl WindowInner { //// // keys //// - xcb::KEY_PRESS => { - let event = unsafe { xcb::cast_event::(&event) }; - + XEvent::KeyPress(event) => { handler.on_event( &mut crate::Window::new(Window { inner: self }), - Event::Keyboard(convert_key_press_event(event)), + Event::Keyboard(convert_key_press_event(&event)), ); } - xcb::KEY_RELEASE => { - let event = unsafe { xcb::cast_event::(&event) }; - + XEvent::KeyRelease(event) => { handler.on_event( &mut crate::Window::new(Window { inner: self }), - Event::Keyboard(convert_key_release_event(event)), + Event::Keyboard(convert_key_release_event(&event)), ); } @@ -712,7 +670,7 @@ unsafe impl<'a> HasRawWindowHandle for Window<'a> { unsafe impl<'a> HasRawDisplayHandle for Window<'a> { fn raw_display_handle(&self) -> RawDisplayHandle { - let display = self.inner.xcb_connection.conn.get_raw_dpy(); + let display = self.inner.xcb_connection.dpy; let mut handle = XlibDisplayHandle::empty(); handle.display = display as *mut c_void; diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index a11f1403..34b400ab 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -1,58 +1,61 @@ -use std::collections::HashMap; -/// A very light abstraction around the XCB connection. -/// -/// Keeps track of the xcb connection itself and the xlib display ID that was used to connect. -use std::ffi::{CStr, CString}; +use std::collections::hash_map::{Entry, HashMap}; +use std::error::Error; + +use x11::{xlib, xlib::Display, xlib_xcb}; + +use x11rb::connection::Connection; +use x11rb::cursor::Handle as CursorHandle; +use x11rb::protocol::xproto::Cursor; +use x11rb::resource_manager; +use x11rb::xcb_ffi::XCBConnection; use crate::MouseCursor; use super::cursor; -pub(crate) struct Atoms { - pub wm_protocols: Option, - pub wm_delete_window: Option, +x11rb::atom_manager! { + pub Atoms2: AtomsCookie { + WM_PROTOCOLS, + WM_DELETE_WINDOW, + } } +/// A very light abstraction around the XCB connection. +/// +/// Keeps track of the xcb connection itself and the xlib display ID that was used to connect. pub struct XcbConnection { - pub conn: xcb::Connection, - pub xlib_display: i32, - - pub(crate) atoms: Atoms, - + pub(crate) dpy: *mut Display, + pub(crate) conn2: XCBConnection, + pub(crate) screen: usize, + pub(crate) atoms2: Atoms2, + pub(crate) resources: resource_manager::Database, + pub(crate) cursor_handle: CursorHandle, pub(super) cursor_cache: HashMap, } -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 { - let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display()?; - - conn.set_event_queue_owner(xcb::base::EventQueueOwner::Xcb); + pub fn new() -> Result> { + let dpy = unsafe { xlib::XOpenDisplay(std::ptr::null()) }; + assert!(!dpy.is_null()); + let xcb_connection = unsafe { xlib_xcb::XGetXCBConnection(dpy) }; + assert!(!xcb_connection.is_null()); + let screen = unsafe { xlib::XDefaultScreen(dpy) } as usize; + let conn2 = unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection, true)? }; + unsafe { + xlib_xcb::XSetEventQueueOwner(dpy, xlib_xcb::XEventQueueOwner::XCBOwnsEventQueue) + }; - let (wm_protocols, wm_delete_window) = intern_atoms!(&conn, WM_PROTOCOLS, WM_DELETE_WINDOW); + let atoms2 = Atoms2::new(&conn2)?.reply()?; + let resources = resource_manager::new_from_default(&conn2)?; + let cursor_handle = CursorHandle::new(&conn2, screen, &resources)?.reply()?; Ok(Self { - conn, - xlib_display, - - atoms: Atoms { wm_protocols, wm_delete_window }, - + dpy, + conn2, + screen, + atoms2, + resources, + cursor_handle, cursor_cache: HashMap::new(), }) } @@ -60,58 +63,21 @@ impl XcbConnection { // 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(&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 std::os::raw::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; - } - } + fn get_scaling_xft(&self) -> Result, Box> { + if let Some(dpi) = self.resources.get_value::("Xft.dpi", "")? { + Ok(Some(dpi as f64 / 96.0)) + } else { + Ok(None) } - 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(&self) -> Option { + fn get_scaling_screen_dimensions(&self) -> f64 { // Figure out screen information - let setup = self.conn.get_setup(); - let screen = setup.roots().nth(self.xlib_display as usize).unwrap(); + let setup = self.conn2.setup(); + let screen = &setup.roots[self.screen]; // Get the DPI from the screen struct // @@ -119,28 +85,34 @@ impl XcbConnection { // 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 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) + yscale } #[inline] - pub fn get_scaling(&self) -> Option { - self.get_scaling_xft().or_else(|| self.get_scaling_screen_dimensions()) + pub fn get_scaling(&self) -> Result> { + Ok(self.get_scaling_xft()?.unwrap_or(self.get_scaling_screen_dimensions())) } #[inline] - pub fn get_cursor_xid(&mut self, cursor: MouseCursor) -> u32 { - let dpy = self.conn.get_raw_dpy(); - - *self.cursor_cache.entry(cursor).or_insert_with(|| cursor::get_xcursor(dpy, cursor)) + pub fn get_cursor(&mut self, cursor: MouseCursor) -> Result> { + match self.cursor_cache.entry(cursor) { + Entry::Occupied(entry) => Ok(*entry.get()), + Entry::Vacant(entry) => { + let cursor = + cursor::get_xcursor(&self.conn2, self.screen, &self.cursor_handle, cursor)?; + entry.insert(cursor); + Ok(cursor) + } + } } } From 085ae2a27e367d355a5801d19744aeed99bbe61f Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 25 Mar 2024 18:34:10 +0200 Subject: [PATCH 5/7] add support for Window::Focused/Unfocused events on macOS (#171) trigger `WindowEvent::Focused` and `WindowEvent::Unfocused` events when the plugin window gains/loses focus. implemented by adding observers to `NSNotificationCenter::defaultCenter()` that listen to `NSWindowDidBecomeKeyNotification` and `NSWindowDidResignKeyNotification` notifications on the `NSViews`' window. tested and confirmed to work in Live, Bitwig, FL Studio, Reaper and AudioPluginHost. --- src/macos/view.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++ src/macos/window.rs | 8 +++-- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/macos/view.rs b/src/macos/view.rs index 66dd57ad..8b765b44 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -29,6 +29,12 @@ use super::{ /// Name of the field used to store the `WindowState` pointer. pub(super) const BASEVIEW_STATE_IVAR: &str = "baseview_state"; +#[link(name = "AppKit", kind = "framework")] +extern "C" { + static NSWindowDidBecomeKeyNotification: id; + static NSWindowDidResignKeyNotification: id; +} + macro_rules! add_simple_mouse_class_method { ($class:ident, $sel:ident, $event:expr) => { #[allow(non_snake_case)] @@ -94,6 +100,18 @@ macro_rules! add_simple_keyboard_class_method { }; } +unsafe fn register_notification(observer: id, notification_name: id, object: id) { + let notification_center: id = msg_send![class!(NSNotificationCenter), defaultCenter]; + + let _: () = msg_send![ + notification_center, + addObserver:observer + selector:sel!(handleNotification:) + name:notification_name + object:object + ]; +} + pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id { let class = create_view_class(); @@ -103,6 +121,9 @@ pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id { view.initWithFrame_(NSRect::new(NSPoint::new(0., 0.), NSSize::new(size.width, size.height))); + register_notification(view, NSWindowDidBecomeKeyNotification, nil); + register_notification(view, NSWindowDidResignKeyNotification, nil); + let _: id = msg_send![ view, registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType]) @@ -124,6 +145,14 @@ unsafe fn create_view_class() -> &'static Class { sel!(acceptsFirstResponder), property_yes as extern "C" fn(&Object, Sel) -> BOOL, ); + class.add_method( + sel!(becomeFirstResponder), + become_first_responder as extern "C" fn(&Object, Sel) -> BOOL, + ); + class.add_method( + sel!(resignFirstResponder), + resign_first_responder as extern "C" fn(&Object, Sel) -> BOOL, + ); class.add_method(sel!(isFlipped), property_yes as extern "C" fn(&Object, Sel) -> BOOL); class.add_method( sel!(preservesContentInLiveResize), @@ -177,6 +206,10 @@ unsafe fn create_view_class() -> &'static Class { dragging_updated as extern "C" fn(&Object, Sel, id) -> NSUInteger, ); class.add_method(sel!(draggingExited:), dragging_exited as extern "C" fn(&Object, Sel, id)); + class.add_method( + sel!(handleNotification:), + handle_notification as extern "C" fn(&Object, Sel, id), + ); add_mouse_button_class_method!(class, mouseDown, ButtonPressed, MouseButton::Left); add_mouse_button_class_method!(class, mouseUp, ButtonReleased, MouseButton::Left); @@ -208,6 +241,25 @@ extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL YES } +extern "C" fn become_first_responder(this: &Object, _sel: Sel) -> BOOL { + let state = unsafe { WindowState::from_view(this) }; + let is_key_window = unsafe { + let window: id = msg_send![this, window]; + let is_key_window: BOOL = msg_send![window, isKeyWindow]; + is_key_window == YES + }; + if is_key_window { + state.trigger_event(Event::Window(WindowEvent::Focused)); + } + YES +} + +extern "C" fn resign_first_responder(this: &Object, _sel: Sel) -> BOOL { + let state = unsafe { WindowState::from_view(this) }; + state.trigger_event(Event::Window(WindowEvent::Unfocused)); + YES +} + extern "C" fn window_should_close(this: &Object, _: Sel, _sender: id) -> BOOL { let state = unsafe { WindowState::from_view(this) }; @@ -473,3 +525,30 @@ extern "C" fn dragging_exited(this: &Object, _sel: Sel, _sender: id) { on_event(&state, MouseEvent::DragLeft); } + +extern "C" fn handle_notification(this: &Object, _cmd: Sel, notification: id) { + unsafe { + let state = WindowState::from_view(this); + + // The subject of the notication, in this case an NSWindow object. + let notification_object: id = msg_send![notification, object]; + + // The NSWindow object associated with our NSView. + let window: id = msg_send![this, window]; + + let first_responder: id = msg_send![window, firstResponder]; + + // Only trigger focus events if the NSWindow that's being notified about is our window, + // and if the window's first responder is our NSView. + // If the first responder isn't our NSView, the focus events will instead be triggered + // by the becomeFirstResponder and resignFirstResponder methods on the NSView itself. + if notification_object == window && first_responder == this as *const Object as id { + let is_key_window: BOOL = msg_send![window, isKeyWindow]; + state.trigger_event(Event::Window(if is_key_window == YES { + WindowEvent::Focused + } else { + WindowEvent::Unfocused + })); + } + } +} diff --git a/src/macos/window.rs b/src/macos/window.rs index c0412525..ea8b78f2 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -13,9 +13,8 @@ use core_foundation::runloop::{ CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode, }; use keyboard_types::KeyboardEvent; - +use objc::class; use objc::{msg_send, runtime::Object, sel, sel_impl}; - use raw_window_handle::{ AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, @@ -83,6 +82,11 @@ impl WindowInner { CFRunLoop::get_current().remove_timer(&frame_timer, kCFRunLoopDefaultMode); } + // Deregister NSView from NotificationCenter. + let notification_center: id = + msg_send![class!(NSNotificationCenter), defaultCenter]; + let () = msg_send![notification_center, removeObserver:self.ns_view]; + drop(window_state); // Close the window if in non-parented mode From bad50d886a36699bcb19078256cb1063f1166ed6 Mon Sep 17 00:00:00 2001 From: Dionysis Athinaios Date: Mon, 25 Mar 2024 22:05:16 +0000 Subject: [PATCH 6/7] Implement input focus for Mac & Windows (#170) * Implement input focus for Mac * Add stubs for Windows and Linux * Remove unnecessary call to window * Implement input focus for Windows * Add check for key window status * Rename to `has_focus` & `focus` * Use GetFocus * Fix incorrectly named var --------- Co-authored-by: Micah Johnston --- src/macos/window.rs | 25 ++++++++++++++++++++++++- src/win/window.rs | 13 ++++++++++++- src/window.rs | 8 ++++++++ src/x11/window.rs | 8 ++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/macos/window.rs b/src/macos/window.rs index ea8b78f2..15b9469e 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -7,7 +7,7 @@ use cocoa::appkit::{ NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered, NSPasteboard, NSView, NSWindow, NSWindowStyleMask, }; -use cocoa::base::{id, nil, NO, YES}; +use cocoa::base::{id, nil, BOOL, NO, YES}; use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString}; use core_foundation::runloop::{ CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode, @@ -43,6 +43,7 @@ impl WindowHandle { pub fn is_open(&self) -> bool { self.state.window_inner.open.get() } + } unsafe impl HasRawWindowHandle for WindowHandle { @@ -284,6 +285,28 @@ impl<'a> Window<'a> { self.inner.close(); } + pub fn has_focus(&mut self) -> bool { + unsafe { + let view = self.inner.ns_view.as_mut().unwrap(); + let window: id = msg_send![view, window]; + if window == nil { return false; }; + let first_responder: id = msg_send![window, firstResponder]; + let is_key_window: BOOL = msg_send![window, isKeyWindow]; + let is_focused: BOOL = msg_send![view, isEqual: first_responder]; + is_key_window == YES && is_focused == YES + } + } + + pub fn focus(&mut self) { + unsafe { + let view = self.inner.ns_view.as_mut().unwrap(); + let window: id = msg_send![view, window]; + if window != nil { + msg_send![window, makeFirstResponder:view] + } + } + } + pub fn resize(&mut self, size: Size) { if self.inner.open.get() { // NOTE: macOS gives you a personal rave if you pass in fractional pixels here. Even diff --git a/src/win/window.rs b/src/win/window.rs index ca8ec4cb..b0b72cbd 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -8,7 +8,7 @@ use winapi::um::winuser::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetDpiForWindow, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, RegisterClassW, ReleaseCapture, SetCapture, SetProcessDpiAwarenessContext, SetTimer, SetWindowLongPtrW, - SetWindowPos, TrackMouseEvent, TranslateMessage, UnregisterClassW, CS_OWNDC, + SetWindowPos, SetFocus, GetFocus, TrackMouseEvent, TranslateMessage, UnregisterClassW, CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA, IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, TRACKMOUSEEVENT, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, @@ -770,6 +770,17 @@ impl Window<'_> { } } + pub fn has_focus(&mut self) -> bool { + let focused_window = unsafe { GetFocus() }; + focused_window == self.state.hwnd + } + + pub fn focus(&mut self) { + unsafe { + SetFocus(self.state.hwnd); + } + } + pub fn resize(&mut self, size: Size) { // To avoid reentrant event handler calls we'll defer the actual resizing until after the // event has been handled diff --git a/src/window.rs b/src/window.rs index 63e3f49a..0008b7ce 100644 --- a/src/window.rs +++ b/src/window.rs @@ -102,6 +102,14 @@ impl<'a> Window<'a> { self.window.set_mouse_cursor(cursor); } + pub fn has_focus(&mut self) -> bool { + self.window.has_focus() + } + + pub fn focus(&mut self) { + self.window.focus() + } + /// If provided, then an OpenGL context will be created for this window. You'll be able to /// access this context through [crate::Window::gl_context]. #[cfg(feature = "opengl")] diff --git a/src/x11/window.rs b/src/x11/window.rs index fae1f69f..6c5d5b6d 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -359,6 +359,14 @@ impl<'a> Window<'a> { self.inner.close_requested = true; } + pub fn has_focus(&mut self) -> bool { + unimplemented!() + } + + pub fn focus(&mut self) { + unimplemented!() + } + pub fn resize(&mut self, size: Size) { let scaling = self.inner.window_info.scale(); let new_window_info = WindowInfo::from_logical_size(size, scaling); From 65d970495fc624ef0258ed96ba00cc0c31be2a50 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 26 Mar 2024 00:41:39 +0100 Subject: [PATCH 7/7] Render a background for the open_window example (#175) This PR adds code to render a basic gray background to the opened window in the `open_window` example. This also helps making the example a bit more useful, since most users will want to render to their window. And also it looks nicer. :slightly_smiling_face: This is done using the `softbuffer` crate, in the same manner of the `open_parented` introduced in #172. --- examples/open_window.rs | 65 +++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/examples/open_window.rs b/examples/open_window.rs index 1ef4320f..da76bea4 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -1,10 +1,13 @@ +use std::num::NonZeroU32; use std::time::Duration; use rtrb::{Consumer, RingBuffer}; #[cfg(target_os = "macos")] use baseview::copy_to_clipboard; -use baseview::{Event, EventStatus, MouseEvent, Window, WindowHandler, WindowScalePolicy}; +use baseview::{ + Event, EventStatus, MouseEvent, PhySize, Window, WindowEvent, WindowHandler, WindowScalePolicy, +}; #[derive(Debug, Clone)] enum Message { @@ -13,32 +16,48 @@ enum Message { struct OpenWindowExample { rx: Consumer, + + ctx: softbuffer::Context, + surface: softbuffer::Surface, + current_size: PhySize, + damaged: bool, } impl WindowHandler for OpenWindowExample { fn on_frame(&mut self, _window: &mut Window) { + let mut buf = self.surface.buffer_mut().unwrap(); + if self.damaged { + buf.fill(0xFFAAAAAA); + self.damaged = false; + } + buf.present().unwrap(); + while let Ok(message) = self.rx.pop() { println!("Message: {:?}", message); } } fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { - match event { - Event::Mouse(e) => { - println!("Mouse event: {:?}", e); - - #[cfg(target_os = "macos")] - match e { - MouseEvent::ButtonPressed { .. } => { - copy_to_clipboard(&"This is a test!") - } - _ => (), + match &event { + #[cfg(target_os = "macos")] + Event::Mouse(MouseEvent::ButtonPressed { .. }) => copy_to_clipboard(&"This is a test!"), + Event::Window(WindowEvent::Resized(info)) => { + println!("Resized: {:?}", info); + let new_size = info.physical_size(); + self.current_size = new_size; + + if let (Some(width), Some(height)) = + (NonZeroU32::new(new_size.width), NonZeroU32::new(new_size.height)) + { + self.surface.resize(width, height).unwrap(); + self.damaged = true; } } - Event::Keyboard(e) => println!("Keyboard event: {:?}", e), - Event::Window(e) => println!("Window event: {:?}", e), + _ => {} } + log_event(&event); + EventStatus::Captured } } @@ -56,13 +75,27 @@ fn main() { let (mut tx, rx) = RingBuffer::new(128); - ::std::thread::spawn(move || loop { - ::std::thread::sleep(Duration::from_secs(5)); + std::thread::spawn(move || loop { + std::thread::sleep(Duration::from_secs(5)); if let Err(_) = tx.push(Message::Hello) { println!("Failed sending message"); } }); - Window::open_blocking(window_open_options, |_| OpenWindowExample { rx }); + Window::open_blocking(window_open_options, |window| { + let ctx = unsafe { softbuffer::Context::new(window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&ctx, window) }.unwrap(); + surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap(); + + OpenWindowExample { ctx, surface, rx, current_size: PhySize::new(512, 512), damaged: true } + }); +} + +fn log_event(event: &Event) { + match event { + Event::Mouse(e) => println!("Mouse event: {:?}", e), + Event::Keyboard(e) => println!("Keyboard event: {:?}", e), + Event::Window(e) => println!("Window event: {:?}", e), + } }