diff --git a/Cargo.toml b/Cargo.toml index bd8d9d83..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] @@ -40,3 +39,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); +} 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), + } } 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..15b9469e 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -7,15 +7,14 @@ 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, }; 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, @@ -44,6 +43,7 @@ impl WindowHandle { pub fn is_open(&self) -> bool { self.state.window_inner.open.get() } + } unsafe impl HasRawWindowHandle for WindowHandle { @@ -83,6 +83,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 @@ -280,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 3c653fce..8e1ad407 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -8,14 +8,14 @@ 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, 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, + 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, Ref, RefCell, RefMut}; @@ -170,21 +170,47 @@ unsafe fn wnd_proc_inner( WM_MOUSEMOVE => { let mut window = crate::Window::new(window_state.create_window()); + 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 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(&mut track_mouse); + *mouse_was_outside_window = false; + + let enter_event = Event::Mouse(MouseEvent::CursorEntered); + window_state.handler.borrow_mut().as_mut().unwrap().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), }); + window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, move_event); + Some(0) + } + WM_MOUSELEAVE => { + let mut window = crate::Window::new(window_state.create_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 => { @@ -448,6 +474,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>>, @@ -652,6 +679,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), @@ -739,6 +767,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/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..6c5d5b6d 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; @@ -370,19 +359,25 @@ 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); - 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 +388,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 +407,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 +427,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 +460,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 +472,7 @@ impl WindowInner { } if revents.contains(PollFlags::POLLIN) { - self.drain_xcb_events(handler); + self.drain_xcb_events(handler)?; } } @@ -501,6 +495,8 @@ impl WindowInner { self.close_requested = false; } } + + Ok(()) } fn handle_close_requested(&mut self, handler: &mut dyn WindowHandler) { @@ -522,9 +518,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 +539,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 +564,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 +646,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 +678,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) + } + } } }