1
0
Fork 0

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.
This commit is contained in:
Micah Johnston 2024-03-25 11:20:28 -05:00 committed by GitHub
parent 998ced845c
commit fdc5d282fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 316 additions and 392 deletions

View file

@ -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]

View file

@ -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<u32> {
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<Cursor, Box<dyn Error>> {
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<u32> {
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<Option<Cursor>, Box<dyn Error>> {
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<u32> {
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<Option<Cursor>, Box<dyn Error>> {
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<Cursor, Box<dyn Error>> {
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::Hand => loadn(&["hand2", "hand1"])?,
MouseCursor::HandGrabbing => loadn(&["closedhand", "grabbing"])?,
MouseCursor::Help => load("question_arrow")?,
MouseCursor::Hidden => create_empty_cursor(display),
MouseCursor::Hidden => Some(create_empty_cursor(conn, screen)?),
MouseCursor::Text => loadn(&[b"text\0", b"xterm\0"]),
MouseCursor::VerticalText => load(b"vertical-text\0"),
MouseCursor::Text => loadn(&["text", "xterm"])?,
MouseCursor::VerticalText => load("vertical-text")?,
MouseCursor::Working => load(b"watch\0"),
MouseCursor::PtrWorking => load(b"left_ptr_watch\0"),
MouseCursor::Working => load("watch")?,
MouseCursor::PtrWorking => load("left_ptr_watch")?,
MouseCursor::NotAllowed => load(b"crossed_circle\0"),
MouseCursor::PtrNotAllowed => loadn(&[b"no-drop\0", b"crossed_circle\0"]),
MouseCursor::NotAllowed => load("crossed_circle")?,
MouseCursor::PtrNotAllowed => loadn(&["no-drop", "crossed_circle"])?,
MouseCursor::ZoomIn => load(b"zoom-in\0"),
MouseCursor::ZoomOut => load(b"zoom-out\0"),
MouseCursor::ZoomIn => load("zoom-in")?,
MouseCursor::ZoomOut => load("zoom-out")?,
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::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(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::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))
}
}

View file

@ -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;

View file

@ -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::<WindowOpenResult>(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<H, B>(
parent: Option<u32>, options: WindowOpenOptions, build: B,
tx: mpsc::SyncSender<WindowOpenResult>, parent_handle: Option<ParentHandle>,
) where
) -> Result<(), Box<dyn Error>>
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<xcb_screen_t>, depth: u8) -> Option<u32> {
for candidate_depth in screen.allowed_depths() {
if candidate_depth.depth() != depth {
fn find_visual_for_depth(screen: &Screen, depth: u8) -> Option<Visualid> {
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<dyn Error>> {
// 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<dyn Error>> {
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::<xcb::ClientMessageEvent>(&event) };
// what an absolute tragedy this all is
let data = event.data().data;
let (_, data32, _) = unsafe { data.align_to::<u32>() };
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::<xcb::ConfigureNotifyEvent>(&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::<xcb::MotionNotifyEvent>(&event) };
let detail = event.detail();
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);
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);
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::<xcb::EnterNotifyEvent>(&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::<xcb::ButtonPressEvent>(&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::<xcb::ButtonPressEvent>(&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::<xcb::KeyPressEvent>(&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::<xcb::KeyReleaseEvent>(&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;

View file

@ -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<u32>,
pub wm_delete_window: Option<u32>,
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<MouseCursor, u32>,
}
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<Self, xcb::base::ConnError> {
let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display()?;
pub fn new() -> Result<Self, Box<dyn Error>> {
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)
};
conn.set_event_queue_owner(xcb::base::EventQueueOwner::Xcb);
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<f64> {
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<Option<f64>, Box<dyn Error>> {
if let Some(dpi) = self.resources.get_value::<u32>("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<f64> {
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<f64> {
self.get_scaling_xft().or_else(|| self.get_scaling_screen_dimensions())
pub fn get_scaling(&self) -> Result<f64, Box<dyn Error>> {
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<Cursor, Box<dyn Error>> {
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)
}
}
}
}