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