gpui: Switch to x11rb (#9113)

Switch to using `x11rb` crate instead of current `xcb` crate for gpui's
x11 platform.

Also fixes the crash on resize, and white flashing on resize.

Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Rajesh Malviya 2024-03-13 10:17:08 +05:30 committed by GitHub
parent 47afc70979
commit 80b80dfa78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 288 additions and 276 deletions

16
Cargo.lock generated
View file

@ -4409,7 +4409,7 @@ dependencies = [
"wayland-cursor", "wayland-cursor",
"wayland-protocols", "wayland-protocols",
"windows 0.53.0", "windows 0.53.0",
"xcb", "x11rb",
"xkbcommon", "xkbcommon",
] ]
@ -12516,7 +12516,9 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a"
dependencies = [ dependencies = [
"as-raw-xcb-connection",
"gethostname", "gethostname",
"libc",
"rustix 0.38.30", "rustix 0.38.30",
"x11rb-protocol", "x11rb-protocol",
] ]
@ -12536,18 +12538,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "xcb"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d27b37e69b8c05bfadcd968eb1a4fe27c9c52565b727f88512f43b89567e262"
dependencies = [
"as-raw-xcb-connection",
"bitflags 1.3.2",
"libc",
"quick-xml 0.30.0",
]
[[package]] [[package]]
name = "xcursor" name = "xcursor"
version = "0.3.5" version = "0.3.5"

View file

@ -108,7 +108,7 @@ copypasta = "0.10.1"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
open = "5.0.1" open = "5.0.1"
ashpd = "0.7.0" ashpd = "0.7.0"
xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] } x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "xkb", "randr"] }
wayland-client = { version = "0.31.2" } wayland-client = { version = "0.31.2" }
wayland-cursor = "0.31.1" wayland-cursor = "0.31.1"
wayland-protocols = { version = "0.31.2", features = [ wayland-protocols = { version = "0.31.2", features = [

View file

@ -2,18 +2,25 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use xcb::{x, Xid as _};
use xkbcommon::xkb;
use collections::HashMap; use collections::HashMap;
use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext}; use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext};
use copypasta::ClipboardProvider; use copypasta::ClipboardProvider;
use x11rb::connection::{Connection, RequestConnection};
use x11rb::errors::ConnectionError;
use x11rb::protocol::randr::ConnectionExt as _;
use x11rb::protocol::xkb::ConnectionExt as _;
use x11rb::protocol::xproto::ConnectionExt as _;
use x11rb::protocol::{randr, xkb, xproto, Event};
use x11rb::xcb_ffi::XCBConnection;
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
use xkbcommon::xkb as xkbc;
use crate::platform::linux::client::Client; use crate::platform::linux::client::Client;
use crate::platform::{LinuxPlatformInner, PlatformWindow}; use crate::platform::{LinuxPlatformInner, PlatformWindow};
use crate::{ use crate::{
AnyWindowHandle, Bounds, CursorStyle, DisplayId, PlatformDisplay, PlatformInput, Point, AnyWindowHandle, Bounds, CursorStyle, DisplayId, PlatformDisplay, PlatformInput, Point,
ScrollDelta, Size, TouchPhase, ScrollDelta, Size, TouchPhase, WindowParams,
}; };
use super::{X11Display, X11Window, X11WindowState, XcbAtoms}; use super::{X11Display, X11Window, X11WindowState, XcbAtoms};
@ -28,55 +35,56 @@ struct WindowRef {
} }
struct X11ClientState { struct X11ClientState {
windows: HashMap<x::Window, WindowRef>, windows: HashMap<xproto::Window, WindowRef>,
xkb: xkbcommon::xkb::State, xkb: xkbc::State,
clipboard: Rc<RefCell<X11ClipboardContext<Clipboard>>>, clipboard: Rc<RefCell<X11ClipboardContext<Clipboard>>>,
primary: Rc<RefCell<X11ClipboardContext<Primary>>>, primary: Rc<RefCell<X11ClipboardContext<Primary>>>,
} }
pub(crate) struct X11Client { pub(crate) struct X11Client {
platform_inner: Rc<LinuxPlatformInner>, platform_inner: Rc<LinuxPlatformInner>,
xcb_connection: Rc<xcb::Connection>, xcb_connection: Rc<XCBConnection>,
x_root_index: i32, x_root_index: usize,
atoms: XcbAtoms, atoms: XcbAtoms,
state: RefCell<X11ClientState>, state: RefCell<X11ClientState>,
} }
impl X11Client { impl X11Client {
pub(crate) fn new(inner: Rc<LinuxPlatformInner>) -> Rc<Self> { pub(crate) fn new(inner: Rc<LinuxPlatformInner>) -> Rc<Self> {
let (xcb_connection, x_root_index) = xcb::Connection::connect_with_extensions( let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
None, xcb_connection
&[xcb::Extension::RandR, xcb::Extension::Xkb], .prefetch_extension_information(xkb::X11_EXTENSION_NAME)
&[], .unwrap();
) xcb_connection
.unwrap(); .prefetch_extension_information(randr::X11_EXTENSION_NAME)
let xkb_ver = xcb_connection
.wait_for_reply(xcb_connection.send_request(&xcb::xkb::UseExtension {
wanted_major: xcb::xkb::MAJOR_VERSION as u16,
wanted_minor: xcb::xkb::MINOR_VERSION as u16,
}))
.unwrap(); .unwrap();
assert!(xkb_ver.supported());
let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap(); let atoms = XcbAtoms::new(&xcb_connection).unwrap();
let xcb_connection = Rc::new(xcb_connection); let xkb = xcb_connection
.xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION)
.unwrap();
let atoms = atoms.reply().unwrap();
let xkb = xkb.reply().unwrap();
assert!(xkb.supported);
let xkb_state = { let xkb_state = {
let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
let xkb_device_id = xkb::x11::get_core_keyboard_device_id(&xcb_connection); let xkb_device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_connection);
let xkb_keymap = xkb::x11::keymap_new_from_device( let xkb_keymap = xkbc::x11::keymap_new_from_device(
&xkb_context, &xkb_context,
&xcb_connection, &xcb_connection,
xkb_device_id, xkb_device_id,
xkb::KEYMAP_COMPILE_NO_FLAGS, xkbc::KEYMAP_COMPILE_NO_FLAGS,
); );
xkb::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id) xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
}; };
let clipboard = X11ClipboardContext::<Clipboard>::new().unwrap(); let clipboard = X11ClipboardContext::<Clipboard>::new().unwrap();
let primary = X11ClipboardContext::<Primary>::new().unwrap(); let primary = X11ClipboardContext::<Primary>::new().unwrap();
let xcb_connection = Rc::new(xcb_connection);
let client: Rc<X11Client> = Rc::new(Self { let client: Rc<X11Client> = Rc::new(Self {
platform_inner: inner.clone(), platform_inner: inner.clone(),
xcb_connection: Rc::clone(&xcb_connection), xcb_connection: Rc::clone(&xcb_connection),
@ -96,7 +104,7 @@ impl X11Client {
inner inner
.loop_handle .loop_handle
.insert_source( .insert_source(
Generic::new_with_error::<xcb::Error>( Generic::new_with_error::<ConnectionError>(
fd, fd,
calloop::Interest::READ, calloop::Interest::READ,
calloop::Mode::Level, calloop::Mode::Level,
@ -116,69 +124,68 @@ impl X11Client {
client client
} }
fn get_window(&self, win: x::Window) -> Option<Rc<X11WindowState>> { fn get_window(&self, win: xproto::Window) -> Option<Rc<X11WindowState>> {
let state = self.state.borrow(); let state = self.state.borrow();
state.windows.get(&win).map(|wr| Rc::clone(&wr.state)) state.windows.get(&win).map(|wr| Rc::clone(&wr.state))
} }
fn handle_event(&self, event: xcb::Event) -> Option<()> { fn handle_event(&self, event: Event) -> Option<()> {
match event { match event {
xcb::Event::X(x::Event::ClientMessage(event)) => { Event::ClientMessage(event) => {
if let x::ClientMessageData::Data32([atom, ..]) = event.data() { let [atom, ..] = event.data.as_data32();
if atom == self.atoms.wm_del_window.resource_id() { if atom == self.atoms.WM_DELETE_WINDOW {
// window "x" button clicked by user, we gracefully exit // window "x" button clicked by user, we gracefully exit
let window_ref = self let window_ref = self
.state .state
.borrow_mut() .borrow_mut()
.windows .windows
.remove(&event.window()) .remove(&event.window)
.unwrap(); .unwrap();
self.platform_inner self.platform_inner
.loop_handle .loop_handle
.remove(window_ref.refresh_event_token); .remove(window_ref.refresh_event_token);
window_ref.state.destroy(); window_ref.state.destroy();
if self.state.borrow().windows.is_empty() { if self.state.borrow().windows.is_empty() {
self.platform_inner.loop_signal.stop(); self.platform_inner.loop_signal.stop();
}
} }
} }
} }
xcb::Event::X(x::Event::ConfigureNotify(event)) => { Event::ConfigureNotify(event) => {
let bounds = Bounds { let bounds = Bounds {
origin: Point { origin: Point {
x: event.x().into(), x: event.x.into(),
y: event.y().into(), y: event.y.into(),
}, },
size: Size { size: Size {
width: event.width().into(), width: event.width.into(),
height: event.height().into(), height: event.height.into(),
}, },
}; };
let window = self.get_window(event.window())?; let window = self.get_window(event.window)?;
window.configure(bounds); window.configure(bounds);
} }
xcb::Event::X(x::Event::Expose(event)) => { Event::Expose(event) => {
let window = self.get_window(event.window())?; let window = self.get_window(event.window)?;
window.refresh(); window.refresh();
} }
xcb::Event::X(x::Event::FocusIn(event)) => { Event::FocusIn(event) => {
let window = self.get_window(event.event())?; let window = self.get_window(event.event)?;
window.set_focused(true); window.set_focused(true);
} }
xcb::Event::X(x::Event::FocusOut(event)) => { Event::FocusOut(event) => {
let window = self.get_window(event.event())?; let window = self.get_window(event.event)?;
window.set_focused(false); window.set_focused(false);
} }
xcb::Event::X(x::Event::KeyPress(event)) => { Event::KeyPress(event) => {
let window = self.get_window(event.event())?; let window = self.get_window(event.event)?;
let modifiers = super::modifiers_from_state(event.state()); let modifiers = super::modifiers_from_state(event.state);
let keystroke = { let keystroke = {
let code = event.detail().into(); let code = event.detail.into();
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code); let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkb::KeyDirection::Down); state.xkb.update_key(code, xkbc::KeyDirection::Down);
keystroke keystroke
}; };
@ -187,36 +194,34 @@ impl X11Client {
is_held: false, is_held: false,
})); }));
} }
xcb::Event::X(x::Event::KeyRelease(event)) => { Event::KeyRelease(event) => {
let window = self.get_window(event.event())?; let window = self.get_window(event.event)?;
let modifiers = super::modifiers_from_state(event.state()); let modifiers = super::modifiers_from_state(event.state);
let keystroke = { let keystroke = {
let code = event.detail().into(); let code = event.detail.into();
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code); let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkb::KeyDirection::Up); state.xkb.update_key(code, xkbc::KeyDirection::Up);
keystroke keystroke
}; };
window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke })); window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
} }
xcb::Event::X(x::Event::ButtonPress(event)) => { Event::ButtonPress(event) => {
let window = self.get_window(event.event())?; let window = self.get_window(event.event)?;
let modifiers = super::modifiers_from_state(event.state()); let modifiers = super::modifiers_from_state(event.state);
let position = Point::new( let position =
(event.event_x() as f32).into(), Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
(event.event_y() as f32).into(), if let Some(button) = super::button_of_key(event.detail) {
);
if let Some(button) = super::button_of_key(event.detail()) {
window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent { window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
button, button,
position, position,
modifiers, modifiers,
click_count: 1, click_count: 1,
})); }));
} else if event.detail() >= 4 && event.detail() <= 5 { } else if event.detail >= 4 && event.detail <= 5 {
// https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11 // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
let delta_x = if event.detail() == 4 { 1.0 } else { -1.0 }; let delta_x = if event.detail == 4 { 1.0 } else { -1.0 };
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent { window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
position, position,
delta: ScrollDelta::Lines(Point::new(0.0, delta_x)), delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
@ -227,14 +232,12 @@ impl X11Client {
log::warn!("Unknown button press: {event:?}"); log::warn!("Unknown button press: {event:?}");
} }
} }
xcb::Event::X(x::Event::ButtonRelease(event)) => { Event::ButtonRelease(event) => {
let window = self.get_window(event.event())?; let window = self.get_window(event.event)?;
let modifiers = super::modifiers_from_state(event.state()); let modifiers = super::modifiers_from_state(event.state);
let position = Point::new( let position =
(event.event_x() as f32).into(), Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
(event.event_y() as f32).into(), if let Some(button) = super::button_of_key(event.detail) {
);
if let Some(button) = super::button_of_key(event.detail()) {
window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent { window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
button, button,
position, position,
@ -243,28 +246,24 @@ impl X11Client {
})); }));
} }
} }
xcb::Event::X(x::Event::MotionNotify(event)) => { Event::MotionNotify(event) => {
let window = self.get_window(event.event())?; let window = self.get_window(event.event)?;
let pressed_button = super::button_from_state(event.state()); let pressed_button = super::button_from_state(event.state);
let position = Point::new( let position =
(event.event_x() as f32).into(), Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
(event.event_y() as f32).into(), let modifiers = super::modifiers_from_state(event.state);
);
let modifiers = super::modifiers_from_state(event.state());
window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent { window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
pressed_button, pressed_button,
position, position,
modifiers, modifiers,
})); }));
} }
xcb::Event::X(x::Event::LeaveNotify(event)) => { Event::LeaveNotify(event) => {
let window = self.get_window(event.event())?; let window = self.get_window(event.event)?;
let pressed_button = super::button_from_state(event.state()); let pressed_button = super::button_from_state(event.state);
let position = Point::new( let position =
(event.event_x() as f32).into(), Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
(event.event_y() as f32).into(), let modifiers = super::modifiers_from_state(event.state);
);
let modifiers = super::modifiers_from_state(event.state());
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent { window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
pressed_button, pressed_button,
position, position,
@ -280,21 +279,23 @@ impl X11Client {
impl Client for X11Client { impl Client for X11Client {
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> { fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
let setup = self.xcb_connection.get_setup(); let setup = self.xcb_connection.setup();
setup setup
.roots() .roots
.iter()
.enumerate() .enumerate()
.filter_map(|(root_id, _)| { .filter_map(|(root_id, _)| {
Some( Some(Rc::new(X11Display::new(&self.xcb_connection, root_id)?)
Rc::new(X11Display::new(&self.xcb_connection, root_id as i32)?) as Rc<dyn PlatformDisplay>)
as Rc<dyn PlatformDisplay>,
)
}) })
.collect() .collect()
} }
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> { fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
Some(Rc::new(X11Display::new(&self.xcb_connection, id.0 as i32)?)) Some(Rc::new(X11Display::new(
&self.xcb_connection,
id.0 as usize,
)?))
} }
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> { fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
@ -307,36 +308,44 @@ impl Client for X11Client {
fn open_window( fn open_window(
&self, &self,
_handle: AnyWindowHandle, _handle: AnyWindowHandle,
params: crate::WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> Box<dyn PlatformWindow> {
let x_window = self.xcb_connection.generate_id(); let x_window = self.xcb_connection.generate_id().unwrap();
let window_ptr = Rc::new(X11WindowState::new( let window_ptr = Rc::new(X11WindowState::new(
params, options,
&self.xcb_connection, &self.xcb_connection,
self.x_root_index, self.x_root_index,
x_window, x_window,
&self.atoms, &self.atoms,
)); ));
let cookie = self let screen_resources = self
.xcb_connection .xcb_connection
.send_request(&xcb::randr::GetScreenResourcesCurrent { window: x_window }); .randr_get_screen_resources(x_window)
let screen_resources = self.xcb_connection.wait_for_reply(cookie).expect("TODO"); .unwrap()
.reply()
.expect("TODO");
let mode = screen_resources let mode = screen_resources
.crtcs() .crtcs
.iter() .iter()
.find_map(|crtc| { .find_map(|crtc| {
let cookie = self.xcb_connection.send_request(&xcb::randr::GetCrtcInfo { let crtc_info = self
crtc: crtc.to_owned(), .xcb_connection
config_timestamp: xcb::x::Time::CurrentTime as u32, .randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
}); .ok()?
let crtc_info = self.xcb_connection.wait_for_reply(cookie).expect("TODO"); .reply()
.ok()?;
let mode_id = crtc_info.mode().resource_id(); screen_resources
screen_resources.modes().iter().find(|m| m.id == mode_id) .modes
.iter()
.find(|m| m.id == crtc_info.mode)
}) })
.expect("Missing screen mode for crtc specified mode id"); .expect("Unable to find screen refresh rate");
// .expect("Missing screen mode for crtc specified mode id");
let refresh_event_token = self let refresh_event_token = self
.platform_inner .platform_inner
@ -345,13 +354,24 @@ impl Client for X11Client {
let refresh_duration = mode_refresh_rate(mode); let refresh_duration = mode_refresh_rate(mode);
let xcb_connection = Rc::clone(&self.xcb_connection); let xcb_connection = Rc::clone(&self.xcb_connection);
move |mut instant, (), _| { move |mut instant, (), _| {
xcb_connection.send_request(&x::SendEvent { xcb_connection
propagate: false, .send_event(
destination: x::SendEventDest::Window(x_window), false,
event_mask: x::EventMask::EXPOSURE, x_window,
event: &x::ExposeEvent::new(x_window, 0, 0, 0, 0, 1), xproto::EventMask::EXPOSURE,
}); xproto::ExposeEvent {
let _ = xcb_connection.flush(); response_type: xproto::EXPOSE_EVENT,
sequence: 0,
window: x_window,
x: 0,
y: 0,
width: 0,
height: 0,
count: 1,
},
)
.unwrap();
let _ = xcb_connection.flush().unwrap();
// Take into account that some frames have been skipped // Take into account that some frames have been skipped
let now = time::Instant::now(); let now = time::Instant::now();
while instant < now { while instant < now {
@ -384,7 +404,7 @@ impl Client for X11Client {
// Adatpted from: // Adatpted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111 // https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate(mode: &xcb::randr::ModeInfo) -> Duration { pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64); let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
let micros = 1_000_000_000 / millihertz; let micros = 1_000_000_000 / millihertz;
log::info!("Refreshing at {} micros", micros); log::info!("Refreshing at {} micros", micros);

View file

@ -1,25 +1,26 @@
use anyhow::Result; use anyhow::Result;
use uuid::Uuid; use uuid::Uuid;
use x11rb::{connection::Connection as _, xcb_ffi::XCBConnection};
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size}; use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct X11Display { pub(crate) struct X11Display {
x_screen_index: i32, x_screen_index: usize,
bounds: Bounds<GlobalPixels>, bounds: Bounds<GlobalPixels>,
uuid: Uuid, uuid: Uuid,
} }
impl X11Display { impl X11Display {
pub(crate) fn new(xc: &xcb::Connection, x_screen_index: i32) -> Option<Self> { pub(crate) fn new(xc: &XCBConnection, x_screen_index: usize) -> Option<Self> {
let screen = xc.get_setup().roots().nth(x_screen_index as usize)?; let screen = xc.setup().roots.get(x_screen_index).unwrap();
Some(Self { Some(Self {
x_screen_index, x_screen_index: x_screen_index,
bounds: Bounds { bounds: Bounds {
origin: Default::default(), origin: Default::default(),
size: Size { size: Size {
width: GlobalPixels(screen.width_in_pixels() as f32), width: GlobalPixels(screen.width_in_pixels as f32),
height: GlobalPixels(screen.height_in_pixels() as f32), height: GlobalPixels(screen.height_in_pixels as f32),
}, },
}, },
uuid: Uuid::from_bytes([0; 16]), uuid: Uuid::from_bytes([0; 16]),

View file

@ -1,8 +1,8 @@
use xcb::x; use x11rb::protocol::xproto;
use crate::{Modifiers, MouseButton, NavigationDirection}; use crate::{Modifiers, MouseButton, NavigationDirection};
pub(crate) fn button_of_key(detail: x::Button) -> Option<MouseButton> { pub(crate) fn button_of_key(detail: xproto::Button) -> Option<MouseButton> {
Some(match detail { Some(match detail {
1 => MouseButton::Left, 1 => MouseButton::Left,
2 => MouseButton::Middle, 2 => MouseButton::Middle,
@ -13,22 +13,22 @@ pub(crate) fn button_of_key(detail: x::Button) -> Option<MouseButton> {
}) })
} }
pub(crate) fn modifiers_from_state(state: x::KeyButMask) -> Modifiers { pub(crate) fn modifiers_from_state(state: xproto::KeyButMask) -> Modifiers {
Modifiers { Modifiers {
control: state.contains(x::KeyButMask::CONTROL), control: state.contains(xproto::KeyButMask::CONTROL),
alt: state.contains(x::KeyButMask::MOD1), alt: state.contains(xproto::KeyButMask::MOD1),
shift: state.contains(x::KeyButMask::SHIFT), shift: state.contains(xproto::KeyButMask::SHIFT),
command: state.contains(x::KeyButMask::MOD4), command: state.contains(xproto::KeyButMask::MOD4),
function: false, function: false,
} }
} }
pub(crate) fn button_from_state(state: x::KeyButMask) -> Option<MouseButton> { pub(crate) fn button_from_state(state: xproto::KeyButMask) -> Option<MouseButton> {
Some(if state.contains(x::KeyButMask::BUTTON1) { Some(if state.contains(xproto::KeyButMask::BUTTON1) {
MouseButton::Left MouseButton::Left
} else if state.contains(x::KeyButMask::BUTTON2) { } else if state.contains(xproto::KeyButMask::BUTTON2) {
MouseButton::Middle MouseButton::Middle
} else if state.contains(x::KeyButMask::BUTTON3) { } else if state.contains(xproto::KeyButMask::BUTTON3) {
MouseButton::Right MouseButton::Right
} else { } else {
return None; return None;

View file

@ -9,10 +9,11 @@ use crate::{
use blade_graphics as gpu; use blade_graphics as gpu;
use parking_lot::Mutex; use parking_lot::Mutex;
use raw_window_handle as rwh; use raw_window_handle as rwh;
use x11rb::{
use xcb::{ connection::Connection,
x::{self, StackMode}, protocol::xproto::{self, ConnectionExt as _, CreateWindowAux},
Xid as _, wrapper::ConnectionExt,
xcb_ffi::XCBConnection,
}; };
use std::{ use std::{
@ -40,14 +41,13 @@ struct Callbacks {
appearance_changed: Option<Box<dyn FnMut()>>, appearance_changed: Option<Box<dyn FnMut()>>,
} }
xcb::atoms_struct! { x11rb::atom_manager! {
#[derive(Debug)] pub XcbAtoms: AtomsCookie {
pub(crate) struct XcbAtoms { WM_PROTOCOLS,
pub wm_protocols => b"WM_PROTOCOLS", WM_DELETE_WINDOW,
pub wm_del_window => b"WM_DELETE_WINDOW", _NET_WM_STATE,
wm_state => b"_NET_WM_STATE", _NET_WM_STATE_MAXIMIZED_VERT,
wm_state_maxv => b"_NET_WM_STATE_MAXIMIZED_VERT", _NET_WM_STATE_MAXIMIZED_HORZ,
wm_state_maxh => b"_NET_WM_STATE_MAXIMIZED_HORZ",
} }
} }
@ -68,30 +68,31 @@ impl LinuxWindowInner {
} }
} }
fn query_render_extent(xcb_connection: &xcb::Connection, x_window: x::Window) -> gpu::Extent { fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
let cookie = xcb_connection.send_request(&x::GetGeometry { let reply = xcb_connection
drawable: x::Drawable::Window(x_window), .get_geometry(x_window)
}); .unwrap()
let reply = xcb_connection.wait_for_reply(cookie).unwrap(); .reply()
.unwrap();
gpu::Extent { gpu::Extent {
width: reply.width() as u32, width: reply.width as u32,
height: reply.height() as u32, height: reply.height as u32,
depth: 1, depth: 1,
} }
} }
struct RawWindow { struct RawWindow {
connection: *mut c_void, connection: *mut c_void,
screen_id: i32, screen_id: usize,
window_id: u32, window_id: u32,
visual_id: u32, visual_id: u32,
} }
pub(crate) struct X11WindowState { pub(crate) struct X11WindowState {
xcb_connection: Rc<xcb::Connection>, xcb_connection: Rc<XCBConnection>,
display: Rc<dyn PlatformDisplay>, display: Rc<dyn PlatformDisplay>,
raw: RawWindow, raw: RawWindow,
x_window: x::Window, x_window: xproto::Window,
callbacks: RefCell<Callbacks>, callbacks: RefCell<Callbacks>,
inner: RefCell<LinuxWindowInner>, inner: RefCell<LinuxWindowInner>,
} }
@ -112,7 +113,7 @@ unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle { fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
let mut dh = blade_rwh::XcbDisplayHandle::empty(); let mut dh = blade_rwh::XcbDisplayHandle::empty();
dh.connection = self.connection; dh.connection = self.connection;
dh.screen = self.screen_id; dh.screen = self.screen_id as i32;
dh.into() dh.into()
} }
} }
@ -130,7 +131,7 @@ impl rwh::HasDisplayHandle for X11Window {
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> { fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
Ok(unsafe { Ok(unsafe {
let non_zero = NonNull::new(self.0.raw.connection).unwrap(); let non_zero = NonNull::new(self.0.raw.connection).unwrap();
let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.0.raw.screen_id); let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.0.raw.screen_id as i32);
rwh::DisplayHandle::borrow_raw(handle.into()) rwh::DisplayHandle::borrow_raw(handle.into())
}) })
} }
@ -139,76 +140,76 @@ impl rwh::HasDisplayHandle for X11Window {
impl X11WindowState { impl X11WindowState {
pub fn new( pub fn new(
params: WindowParams, params: WindowParams,
xcb_connection: &Rc<xcb::Connection>, xcb_connection: &Rc<XCBConnection>,
x_main_screen_index: i32, x_main_screen_index: usize,
x_window: x::Window, x_window: xproto::Window,
atoms: &XcbAtoms, atoms: &XcbAtoms,
) -> Self { ) -> Self {
let x_screen_index = params let x_screen_index = params
.display_id .display_id
.map_or(x_main_screen_index, |did| did.0 as i32); .map_or(x_main_screen_index, |did| did.0 as usize);
let screen = xcb_connection let screen = xcb_connection.setup().roots.get(x_screen_index).unwrap();
.get_setup()
.roots() let win_aux = xproto::CreateWindowAux::new().event_mask(
.nth(x_screen_index as usize) xproto::EventMask::EXPOSURE
| xproto::EventMask::STRUCTURE_NOTIFY
| xproto::EventMask::ENTER_WINDOW
| xproto::EventMask::LEAVE_WINDOW
| xproto::EventMask::FOCUS_CHANGE
| xproto::EventMask::KEY_PRESS
| xproto::EventMask::KEY_RELEASE
| xproto::EventMask::BUTTON_PRESS
| xproto::EventMask::BUTTON_RELEASE
| xproto::EventMask::POINTER_MOTION
| xproto::EventMask::BUTTON1_MOTION
| xproto::EventMask::BUTTON2_MOTION
| xproto::EventMask::BUTTON3_MOTION
| xproto::EventMask::BUTTON4_MOTION
| xproto::EventMask::BUTTON5_MOTION
| xproto::EventMask::BUTTON_MOTION,
);
xcb_connection
.create_window(
x11rb::COPY_FROM_PARENT as _,
x_window,
screen.root,
params.bounds.origin.x.0 as i16,
params.bounds.origin.y.0 as i16,
params.bounds.size.width.0 as u16,
params.bounds.size.height.0 as u16,
0,
xproto::WindowClass::INPUT_OUTPUT,
screen.root_visual,
&win_aux,
)
.unwrap(); .unwrap();
let xcb_values = [
x::Cw::BackPixel(screen.white_pixel()),
x::Cw::EventMask(
x::EventMask::EXPOSURE
| x::EventMask::STRUCTURE_NOTIFY
| x::EventMask::ENTER_WINDOW
| x::EventMask::LEAVE_WINDOW
| x::EventMask::FOCUS_CHANGE
| x::EventMask::KEY_PRESS
| x::EventMask::KEY_RELEASE
| x::EventMask::BUTTON_PRESS
| x::EventMask::BUTTON_RELEASE
| x::EventMask::POINTER_MOTION
| x::EventMask::BUTTON1_MOTION
| x::EventMask::BUTTON2_MOTION
| x::EventMask::BUTTON3_MOTION
| x::EventMask::BUTTON4_MOTION
| x::EventMask::BUTTON5_MOTION
| x::EventMask::BUTTON_MOTION,
),
];
xcb_connection.send_request(&x::CreateWindow {
depth: x::COPY_FROM_PARENT as u8,
wid: x_window,
parent: screen.root(),
x: params.bounds.origin.x.0 as i16,
y: params.bounds.origin.y.0 as i16,
width: params.bounds.size.width.0 as u16,
height: params.bounds.size.height.0 as u16,
border_width: 0,
class: x::WindowClass::InputOutput,
visual: screen.root_visual(),
value_list: &xcb_values,
});
if let Some(titlebar) = params.titlebar { if let Some(titlebar) = params.titlebar {
if let Some(title) = titlebar.title { if let Some(title) = titlebar.title {
xcb_connection.send_request(&x::ChangeProperty { xcb_connection
mode: x::PropMode::Replace, .change_property8(
window: x_window, xproto::PropMode::REPLACE,
property: x::ATOM_WM_NAME, x_window,
r#type: x::ATOM_STRING, xproto::AtomEnum::WM_NAME,
data: title.as_bytes(), xproto::AtomEnum::STRING,
}); title.as_bytes(),
)
.unwrap();
} }
} }
xcb_connection.send_request(&x::ChangeProperty {
mode: x::PropMode::Replace,
window: x_window,
property: atoms.wm_protocols,
r#type: x::ATOM_ATOM,
data: &[atoms.wm_del_window],
});
xcb_connection.send_request(&x::MapWindow { window: x_window }); xcb_connection
.change_property32(
xproto::PropMode::REPLACE,
x_window,
atoms.WM_PROTOCOLS,
xproto::AtomEnum::ATOM,
&[atoms.WM_DELETE_WINDOW],
)
.unwrap();
xcb_connection.map_window(x_window).unwrap();
xcb_connection.flush().unwrap(); xcb_connection.flush().unwrap();
let raw = RawWindow { let raw = RawWindow {
@ -216,8 +217,8 @@ impl X11WindowState {
xcb_connection, xcb_connection,
) as *mut _, ) as *mut _,
screen_id: x_screen_index, screen_id: x_screen_index,
window_id: x_window.resource_id(), window_id: x_window,
visual_id: screen.root_visual(), visual_id: screen.root_visual,
}; };
let gpu = Arc::new( let gpu = Arc::new(
unsafe { unsafe {
@ -254,12 +255,8 @@ impl X11WindowState {
pub fn destroy(&self) { pub fn destroy(&self) {
self.inner.borrow_mut().renderer.destroy(); self.inner.borrow_mut().renderer.destroy();
self.xcb_connection.send_request(&x::UnmapWindow { self.xcb_connection.unmap_window(self.x_window).unwrap();
window: self.x_window, self.xcb_connection.destroy_window(self.x_window).unwrap();
});
self.xcb_connection.send_request(&x::DestroyWindow {
window: self.x_window,
});
if let Some(fun) = self.callbacks.borrow_mut().close.take() { if let Some(fun) = self.callbacks.borrow_mut().close.take() {
fun(); fun();
} }
@ -359,14 +356,14 @@ impl PlatformWindow for X11Window {
} }
fn mouse_position(&self) -> Point<Pixels> { fn mouse_position(&self) -> Point<Pixels> {
let cookie = self.0.xcb_connection.send_request(&x::QueryPointer { let reply = self
window: self.0.x_window, .0
}); .xcb_connection
let reply: x::QueryPointerReply = self.0.xcb_connection.wait_for_reply(cookie).unwrap(); .query_pointer(self.0.x_window)
Point::new( .unwrap()
(reply.root_x() as u32).into(), .reply()
(reply.root_y() as u32).into(), .unwrap();
) Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
} }
// todo(linux) // todo(linux)
@ -397,20 +394,24 @@ impl PlatformWindow for X11Window {
} }
fn activate(&self) { fn activate(&self) {
self.0.xcb_connection.send_request(&x::ConfigureWindow { let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
window: self.0.x_window, self.0
value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)], .xcb_connection
}); .configure_window(self.0.x_window, &win_aux)
.unwrap();
} }
fn set_title(&mut self, title: &str) { fn set_title(&mut self, title: &str) {
self.0.xcb_connection.send_request(&x::ChangeProperty { self.0
mode: x::PropMode::Replace, .xcb_connection
window: self.0.x_window, .change_property8(
property: x::ATOM_WM_NAME, xproto::PropMode::REPLACE,
r#type: x::ATOM_STRING, self.0.x_window,
data: title.as_bytes(), xproto::AtomEnum::WM_NAME,
}); xproto::AtomEnum::STRING,
title.as_bytes(),
)
.unwrap();
} }
// todo(linux) // todo(linux)
@ -443,7 +444,7 @@ impl PlatformWindow for X11Window {
// todo(linux) // todo(linux)
fn is_full_screen(&self) -> bool { fn is_full_screen(&self) -> bool {
unimplemented!() false
} }
fn on_request_frame(&self, callback: Box<dyn FnMut()>) { fn on_request_frame(&self, callback: Box<dyn FnMut()>) {