Merge pull request #371 from i509VCB/x-client

This commit is contained in:
Victor Brekenfeld 2021-10-18 18:44:51 +02:00 committed by GitHub
commit 7ad40d3f3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2903 additions and 38 deletions

View File

@ -126,6 +126,7 @@ jobs:
- backend_session
- backend_session_logind
- backend_session_libseat
- backend_x11
- renderer_gl
- wayland_frontend
- xwayland
@ -199,6 +200,7 @@ jobs:
- winit
- udev
- logind
- x11
- default
- test_all_features

View File

@ -25,6 +25,13 @@
- Support for `xdg_wm_base` protocol version 3
- Added the option to initialize the dmabuf global with a client filter
#### Backends
- New `x11` backend to run the compositor as an X11 client. Enabled through the `backend_x11` feature.
- `x11rb` event source integration used in anvil's XWayland implementation is now part of smithay at `utils::x11rb`. Enabled through the `x11rb_event_source` feature.
- `KeyState`, `MouseButton`, `ButtonState` and `Axis` in `backend::input` now derive `Hash`.
- New `DrmNode` type in drm backend. This is primarily for use a backend which needs to run as client inside another session.
### Bugfixes
- EGLBufferReader now checks if buffers are alive before using them.
@ -32,6 +39,7 @@
### Anvil
- Anvil now implements the x11 backend in smithay. Run by passing `--x11` into the arguments when launching.
- Passing `ANVIL_MUTEX_LOG` in environment variables now uses the slower `Mutex` logging drain.
## version 0.3.0 (2021-07-25)

View File

@ -30,7 +30,7 @@ drm-ffi = { version = "0.2.0", optional = true }
gbm = { version = "0.7.0", optional = true, default-features = false, features = ["drm-support"] }
input = { version = "0.6", default-features = false, features=["libinput_1_14"], optional = true }
lazy_static = "1"
libc = "0.2.70"
libc = "0.2.103"
libseat= { version = "0.1.1", optional = true }
libloading = { version="0.7.0", optional = true }
nix = "0.22"
@ -46,6 +46,7 @@ wayland-protocols = { version = "0.29.0", features = ["unstable_protocols", "sta
wayland-server = { version = "0.29.0", optional = true }
wayland-sys = { version = "0.29.0", optional = true }
winit = { version = "0.25.0", optional = true }
x11rb = { version = "0.9.0", optional = true }
xkbcommon = "0.4.0"
scan_fmt = { version = "0.2.3", default-features = false }
@ -57,8 +58,9 @@ gl_generator = { version = "0.14", optional = true }
pkg-config = { version = "0.3.17", optional = true }
[features]
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog"]
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"]
backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl"]
backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm"]
backend_drm = ["drm", "drm-ffi"]
backend_gbm = ["gbm"]
backend_egl = ["gl_generator", "libloading"]
@ -71,6 +73,7 @@ backend_session_libseat = ["backend_session", "libseat"]
renderer_gl = ["gl_generator", "backend_egl"]
use_system_lib = ["wayland_frontend", "wayland-sys", "wayland-server/use_system_lib"]
wayland_frontend = ["wayland-server", "wayland-commons", "wayland-protocols", "tempfile"]
x11rb_event_source = ["x11rb"]
xwayland = ["wayland_frontend"]
test_all_features = ["default", "use_system_lib", "wayland-server/dlopen"]

View File

@ -39,6 +39,7 @@ cargo run -- --{backend}
The currently available backends are:
- `--x11`: start anvil as an X11 client. This allows you to run the compositor inside an X11 session or any compositor supporting XWayland. Should be preferred over the winit backend where possible.
- `--winit`: start anvil as a [Winit](https://github.com/tomaka/winit) application. This allows you to run it
inside of an other X11 or Wayland session.
- `--tty-udev`: start anvil in a tty with udev support. This is the "traditional" launch of a Wayland

View File

@ -28,7 +28,7 @@ features = [ "wayland_frontend", "slog-stdlog" ]
[dependencies.x11rb]
optional = true
version = "0.8"
version = "0.9.0"
default-features = false
features = [ "composite" ]
@ -36,13 +36,14 @@ features = [ "composite" ]
gl_generator = "0.14"
[features]
default = [ "winit", "udev", "logind", "egl", "xwayland" ]
default = [ "winit", "udev", "logind", "egl", "xwayland", "x11" ]
egl = [ "smithay/use_system_lib", "smithay/backend_egl" ]
winit = [ "smithay/backend_winit" ]
udev = [ "smithay/backend_libinput", "smithay/backend_udev", "smithay/backend_drm", "smithay/backend_gbm", "smithay/backend_egl", "smithay/backend_session", "input", "image", "smithay/renderer_gl", "xcursor" ]
logind = [ "smithay/backend_session_logind" ]
elogind = ["logind", "smithay/backend_session_elogind" ]
libseat = ["smithay/backend_session_libseat" ]
xwayland = [ "smithay/xwayland", "x11rb" ]
xwayland = [ "smithay/xwayland", "x11rb", "smithay/x11rb_event_source" ]
x11 = [ "smithay/backend_x11", "x11rb", "egl", "smithay/renderer_gl" ]
debug = [ "fps_ticker", "image/png" ]
test_all_features = ["default", "debug"]

View File

@ -1,10 +1,13 @@
use std::{process::Command, sync::atomic::Ordering};
use crate::AnvilState;
#[cfg(feature = "udev")]
use crate::udev::UdevData;
#[cfg(feature = "winit")]
use crate::winit::WinitData;
use crate::AnvilState;
#[cfg(feature = "x11")]
use crate::x11::X11Data;
use smithay::{
backend::input::{
@ -18,7 +21,7 @@ use smithay::{
},
};
#[cfg(feature = "winit")]
#[cfg(any(feature = "winit", feature = "x11"))]
use smithay::{backend::input::PointerMotionAbsoluteEvent, wayland::output::Mode};
#[cfg(feature = "udev")]
@ -507,6 +510,87 @@ impl AnvilState<UdevData> {
}
}
#[cfg(feature = "x11")]
impl AnvilState<X11Data> {
pub fn process_input_event<B: InputBackend>(&mut self, event: InputEvent<B>) {
match event {
InputEvent::Keyboard { event } => match self.keyboard_key_to_action::<B>(event) {
KeyAction::None => (),
KeyAction::Quit => {
info!(self.log, "Quitting.");
self.running.store(false, Ordering::SeqCst);
}
KeyAction::Run(cmd) => {
info!(self.log, "Starting program"; "cmd" => cmd.clone());
if let Err(e) = Command::new(&cmd).spawn() {
error!(self.log,
"Failed to start program";
"cmd" => cmd,
"err" => format!("{:?}", e)
);
}
}
KeyAction::ScaleUp => {
let current_scale = {
self.output_map
.borrow()
.find_by_name(crate::x11::OUTPUT_NAME)
.map(|o| o.scale())
.unwrap_or(1.0)
};
self.output_map
.borrow_mut()
.update_scale_by_name(current_scale + 0.25f32, crate::x11::OUTPUT_NAME);
}
KeyAction::ScaleDown => {
let current_scale = {
self.output_map
.borrow()
.find_by_name(crate::x11::OUTPUT_NAME)
.map(|o| o.scale())
.unwrap_or(1.0)
};
self.output_map.borrow_mut().update_scale_by_name(
f32::max(1.0f32, current_scale + 0.25f32),
crate::x11::OUTPUT_NAME,
);
}
action => {
warn!(self.log, "Key action {:?} unsupported on x11 backend.", action);
}
},
InputEvent::PointerMotionAbsolute { event } => self.on_pointer_move_absolute::<B>(event),
InputEvent::PointerButton { event } => self.on_pointer_button::<B>(event),
InputEvent::PointerAxis { event } => self.on_pointer_axis::<B>(event),
_ => (), // other events are not handled in anvil (yet)
}
}
fn on_pointer_move_absolute<B: InputBackend>(&mut self, evt: B::PointerMotionAbsoluteEvent) {
let output_size = self
.output_map
.borrow()
.find_by_name(crate::x11::OUTPUT_NAME)
.map(|o| o.size())
.unwrap();
let pos = evt.position_transformed(output_size);
self.pointer_location = pos;
let serial = SCOUNTER.next_serial();
let under = self.window_map.borrow().get_surface_under(pos);
self.pointer.motion(pos, under, serial, evt.time());
}
}
/// Possible results of a keyboard action
#[derive(Debug)]
enum KeyAction {

View File

@ -14,7 +14,7 @@ pub mod cursor;
pub mod drawing;
pub mod input_handler;
pub mod output_map;
#[cfg(any(feature = "udev", feature = "winit"))]
#[cfg(any(feature = "udev", feature = "winit", feature = "x11"))]
pub mod render;
pub mod shell;
pub mod state;
@ -23,6 +23,8 @@ pub mod udev;
pub mod window_map;
#[cfg(feature = "winit")]
pub mod winit;
#[cfg(feature = "x11")]
pub mod x11;
#[cfg(feature = "xwayland")]
pub mod xwayland;

View File

@ -5,6 +5,8 @@ static POSSIBLE_BACKENDS: &[&str] = &[
"--winit : Run anvil as a X11 or Wayland client using winit.",
#[cfg(feature = "udev")]
"--tty-udev : Run anvil as a tty udev client (requires root if without logind).",
#[cfg(feature = "x11")]
"--x11 : Run anvil as an X11 client.",
];
fn main() {
@ -33,6 +35,11 @@ fn main() {
slog::info!(log, "Starting anvil on a tty using udev");
anvil::udev::run_udev(log);
}
#[cfg(feature = "x11")]
Some("--x11") => {
slog::info!(log, "Starting anvil with x11 backend");
anvil::x11::run_x11(log);
}
Some(other) => {
crit!(log, "Unknown backend: {}", other);
}

View File

@ -20,6 +20,7 @@ use smithay::{
use crate::shell::SurfaceData;
#[derive(Debug)]
pub struct Output {
name: String,
output: output::Output,
@ -124,6 +125,7 @@ impl Drop for Output {
}
}
#[derive(Debug)]
pub struct OutputMap {
display: Rc<RefCell<Display>>,
outputs: Vec<Output>,

View File

@ -28,6 +28,7 @@ use smithay::xwayland::{XWayland, XWaylandEvent};
use crate::{output_map::OutputMap, shell::init_shell, window_map::WindowMap};
#[derive(Debug)]
pub struct AnvilState<BackendData> {
pub backend_data: BackendData,
pub socket_name: Option<String>,

View File

@ -24,7 +24,7 @@ use crate::xwayland::X11Surface;
mod layer_map;
pub use layer_map::{LayerMap, LayerSurface};
#[derive(Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub enum Kind {
Xdg(ToplevelSurface),
Wl(ShellSurface),
@ -68,7 +68,7 @@ impl Kind {
}
}
#[derive(Clone)]
#[derive(Debug, Clone)]
pub enum PopupKind {
Xdg(PopupSurface),
}
@ -125,6 +125,7 @@ impl PopupKind {
}
}
#[derive(Debug)]
struct Window {
location: Point<i32, Logical>,
/// A bounding box over this window and its children.
@ -241,11 +242,12 @@ impl Window {
}
}
#[derive(Debug)]
pub struct Popup {
popup: PopupKind,
}
#[derive(Default)]
#[derive(Debug, Default)]
pub struct WindowMap {
windows: Vec<Window>,
popups: Vec<Popup>,

View File

@ -11,6 +11,7 @@ use smithay::{
use crate::{output_map::Output, shell::SurfaceData};
#[derive(Debug)]
pub struct LayerSurface {
pub surface: wlr_layer::LayerSurface,
pub location: Point<i32, Logical>,
@ -122,7 +123,7 @@ impl LayerSurface {
}
}
#[derive(Default)]
#[derive(Debug, Default)]
pub struct LayerMap {
surfaces: Vec<LayerSurface>,
}

335
anvil/src/x11.rs Normal file
View File

@ -0,0 +1,335 @@
use std::{cell::RefCell, rc::Rc, sync::atomic::Ordering, time::Duration};
use slog::Logger;
#[cfg(feature = "egl")]
use smithay::{backend::renderer::ImportDma, wayland::dmabuf::init_dmabuf_global};
use smithay::{
backend::{
egl::{EGLContext, EGLDisplay},
renderer::{gles2::Gles2Renderer, Bind, ImportEgl, Renderer, Transform, Unbind},
x11::{X11Backend, X11Event, X11Surface},
SwapBuffersError,
},
reexports::{
calloop::EventLoop,
wayland_server::{protocol::wl_output, Display},
},
wayland::{
output::{Mode, PhysicalProperties},
seat::CursorImageStatus,
},
};
use crate::{
drawing::{draw_cursor, draw_dnd_icon},
render::render_layers_and_windows,
state::Backend,
AnvilState,
};
#[cfg(feature = "debug")]
use smithay::backend::renderer::gles2::Gles2Texture;
pub const OUTPUT_NAME: &str = "x11";
#[derive(Debug)]
pub struct X11Data {
render: bool,
mode: Mode,
surface: X11Surface,
#[cfg(feature = "debug")]
fps_texture: Gles2Texture,
#[cfg(feature = "debug")]
fps: fps_ticker::Fps,
}
impl Backend for X11Data {
fn seat_name(&self) -> String {
"x11".to_owned()
}
}
pub fn run_x11(log: Logger) {
let mut event_loop = EventLoop::try_new().unwrap();
let display = Rc::new(RefCell::new(Display::new()));
let (backend, surface) =
X11Backend::with_title("Anvil", log.clone()).expect("Failed to initialize X11 backend");
let window = backend.window();
// Initialize EGL using the GBM device setup earlier.
let egl = EGLDisplay::new(&surface, log.clone()).expect("Failed to create EGLDisplay");
let context = EGLContext::new(&egl, log.clone()).expect("Failed to create EGLContext");
let renderer =
unsafe { Gles2Renderer::new(context, log.clone()) }.expect("Failed to initialize renderer");
let renderer = Rc::new(RefCell::new(renderer));
#[cfg(feature = "egl")]
{
if renderer.borrow_mut().bind_wl_display(&*display.borrow()).is_ok() {
info!(log, "EGL hardware-acceleration enabled");
let dmabuf_formats = renderer
.borrow_mut()
.dmabuf_formats()
.cloned()
.collect::<Vec<_>>();
let renderer = renderer.clone();
init_dmabuf_global(
&mut *display.borrow_mut(),
dmabuf_formats,
move |buffer, _| renderer.borrow_mut().import_dmabuf(buffer).is_ok(),
log.clone(),
);
}
}
let size = {
let s = backend.window().size();
(s.w as i32, s.h as i32).into()
};
let mode = Mode {
size,
refresh: 60_000,
};
let data = X11Data {
render: true,
mode,
surface,
#[cfg(feature = "debug")]
fps_texture: {
use crate::drawing::{import_bitmap, FPS_NUMBERS_PNG};
import_bitmap(
&mut *renderer.borrow_mut(),
&image::io::Reader::with_format(
std::io::Cursor::new(FPS_NUMBERS_PNG),
image::ImageFormat::Png,
)
.decode()
.unwrap()
.to_rgba8(),
)
.expect("Unable to upload FPS texture")
},
#[cfg(feature = "debug")]
fps: fps_ticker::Fps::default(),
};
let mut state = AnvilState::init(display.clone(), event_loop.handle(), data, log.clone(), true);
state.output_map.borrow_mut().add(
OUTPUT_NAME,
PhysicalProperties {
size: (0, 0).into(),
subpixel: wl_output::Subpixel::Unknown,
make: "Smithay".into(),
model: "X11".into(),
},
mode,
);
event_loop
.handle()
.insert_source(backend, |event, _window, state| match event {
X11Event::CloseRequested => {
state.running.store(false, Ordering::SeqCst);
}
X11Event::Resized(size) => {
let size = { (size.w as i32, size.h as i32).into() };
state.backend_data.mode = Mode {
size,
refresh: 60_000,
};
state.output_map.borrow_mut().update_mode_by_name(
Mode {
size,
refresh: 60_000,
},
OUTPUT_NAME,
);
let output_mut = state.output_map.borrow();
let output = output_mut.find_by_name(OUTPUT_NAME).unwrap();
state.window_map.borrow_mut().layers.arange_layers(output);
state.backend_data.render = true;
}
X11Event::PresentCompleted | X11Event::Refresh => {
state.backend_data.render = true;
}
X11Event::Input(event) => state.process_input_event(event),
})
.expect("Failed to insert X11 Backend into event loop");
let start_time = std::time::Instant::now();
let mut cursor_visible = true;
#[cfg(feature = "xwayland")]
state.start_xwayland();
info!(log, "Initialization completed, starting the main loop.");
while state.running.load(Ordering::SeqCst) {
let (output_geometry, output_scale) = state
.output_map
.borrow()
.find_by_name(OUTPUT_NAME)
.map(|output| (output.geometry(), output.scale()))
.unwrap();
if state.backend_data.render {
state.backend_data.render = false;
let backend_data = &mut state.backend_data;
match backend_data.surface.present() {
Ok(present) => {
let mut renderer = renderer.borrow_mut();
// We need to borrow everything we want to refer to inside the renderer callback otherwise rustc is unhappy.
let window_map = state.window_map.borrow();
let (x, y) = state.pointer_location.into();
let dnd_icon = &state.dnd_icon;
let cursor_status = &state.cursor_status;
#[cfg(feature = "debug")]
let fps = backend_data.fps.avg().round() as u32;
#[cfg(feature = "debug")]
let fps_texture = &backend_data.fps_texture;
if let Err(err) = renderer.bind(present.buffer()) {
error!(log, "Error while binding buffer: {}", err);
}
// drawing logic
match renderer
// X11 scanout for a Dmabuf is upside down
// TODO: Address this issue in renderer.
.render(
backend_data.mode.size,
Transform::Flipped180,
|renderer, frame| {
render_layers_and_windows(
renderer,
frame,
&*window_map,
output_geometry,
output_scale,
&log,
)?;
// draw the dnd icon if any
{
let guard = dnd_icon.lock().unwrap();
if let Some(ref surface) = *guard {
if surface.as_ref().is_alive() {
draw_dnd_icon(
renderer,
frame,
surface,
(x as i32, y as i32).into(),
output_scale,
&log,
)?;
}
}
}
// draw the cursor as relevant
{
let mut guard = cursor_status.lock().unwrap();
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *guard {
reset = !surface.as_ref().is_alive();
}
if reset {
*guard = CursorImageStatus::Default;
}
// draw as relevant
if let CursorImageStatus::Image(ref surface) = *guard {
cursor_visible = false;
draw_cursor(
renderer,
frame,
surface,
(x as i32, y as i32).into(),
output_scale,
&log,
)?;
} else {
cursor_visible = true;
}
}
#[cfg(feature = "debug")]
{
use crate::drawing::draw_fps;
draw_fps(renderer, frame, fps_texture, output_scale as f64, fps)?;
}
Ok(())
},
)
.map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x)
.map_err(Into::<SwapBuffersError>::into)
{
Ok(()) => {
// Unbind the buffer and now let the scope end to present.
if let Err(err) = renderer.unbind() {
error!(log, "Error while unbinding buffer: {}", err);
}
}
Err(err) => {
if let SwapBuffersError::ContextLost(err) = err {
error!(log, "Critical Rendering Error: {}", err);
state.running.store(false, Ordering::SeqCst);
}
}
}
}
Err(err) => {
error!(log, "Failed to allocate buffers to present to window: {}", err);
state.running.store(false, Ordering::SeqCst);
}
}
#[cfg(feature = "debug")]
state.backend_data.fps.tick();
window.set_cursor_visible(cursor_visible);
// Send frame events so that client start drawing their next frame
state
.window_map
.borrow()
.send_frames(start_time.elapsed().as_millis() as u32);
display.borrow_mut().flush_clients(&mut state);
}
if event_loop
.dispatch(Some(Duration::from_millis(16)), &mut state)
.is_err()
{
state.running.store(false, Ordering::SeqCst);
} else {
display.borrow_mut().flush_clients(&mut state);
state.window_map.borrow_mut().refresh();
state.output_map.borrow_mut().refresh();
}
}
// Cleanup stuff
state.window_map.borrow_mut().clear();
}

View File

@ -4,7 +4,7 @@ use std::{
use smithay::{
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client},
utils::{Logical, Point},
utils::{x11rb::X11Source, Logical, Point},
wayland::compositor::give_role,
};
@ -27,10 +27,6 @@ use crate::{
AnvilState,
};
use x11rb_event_source::X11Source;
mod x11rb_event_source;
impl<BackendData: 'static> AnvilState<BackendData> {
pub fn start_xwayland(&mut self) {
if let Err(e) = self.xwayland.start() {
@ -237,7 +233,7 @@ pub fn commit_hook(surface: &WlSurface) {
}
}
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct X11Surface {
surface: WlSurface,
}

View File

@ -1,6 +1,6 @@
//! This module represents abstraction on top the linux direct rendering manager api (drm).
//!
//! ## DrmDevice
//! ## [`DrmDevice`]
//!
//! A device exposes certain properties, which are directly derived
//! from the *device* as perceived by the direct rendering manager api (drm). These resources consists
@ -39,7 +39,7 @@
//! smithay does not make sure that `connectors` are not already in use by another `Surface`. Overlapping `connector`-Sets may
//! be an error or result in undefined rendering behavior depending on the `Surface` implementation.
//!
//! ## DrmSurface
//! ## [`DrmSurface`]
//!
//! A surface is a part of a `Device` that may output a picture to a number of connectors. It pumps pictures of buffers to outputs.
//!
@ -61,15 +61,24 @@
//!
//! Buffer management and details about the various types can be found in the [`allocator`-Module](crate::backend::allocator) and
//! rendering abstractions, which can target these buffers can be found in the [`renderer`-Module](crate::backend::renderer).
//!
//! ## [`DrmNode`]
//!
//! A drm node refers to a drm device and the capabilities that may be performed using the node.
//! Generally [`DrmNode`] is primarily used by clients (such as the output backends) which need
//! to allocate buffers for use in X11 or Wayland. If you need to do mode setting, you should use
//! [`DrmDevice`] instead.
pub(crate) mod device;
pub(self) mod error;
pub(self) mod node;
#[cfg(feature = "backend_session")]
pub(self) mod session;
pub(self) mod surface;
pub use device::{DevPath, DrmDevice, DrmEvent};
pub use error::Error as DrmError;
pub use node::{ConvertErrorKind, ConvertNodeError, CreateDrmNodeError, DrmNode, NodeType};
#[cfg(feature = "backend_gbm")]
pub use surface::gbm::{Error as GbmBufferedSurfaceError, GbmBufferedSurface};
pub use surface::DrmSurface;

View File

@ -0,0 +1,45 @@
//! OS-Specific DRM constants
// DRM major value.
#[cfg(target_os = "dragonfly")]
pub const DRM_MAJOR: u64 = 145;
#[cfg(target_os = "netbsd")]
pub const DRM_MAJOR: u64 = 34;
#[cfg(all(target_os = "openbsd", target_arch = "i386"))]
pub const DRM_MAJOR: u64 = 88;
#[cfg(all(target_os = "openbsd", not(target_arch = "i386")))]
pub const DRM_MAJOR: u64 = 87;
#[cfg(not(any(target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd")))]
#[allow(dead_code)] // Not used on Linux
pub const DRM_MAJOR: u64 = 226;
// DRM node prefixes
#[cfg(not(target_os = "openbsd"))]
pub const PRIMARY_NAME: &str = "card";
#[cfg(target_os = "freebsd")]
pub const PRIMARY_NAME: &str = "drm";
#[cfg(not(target_os = "openbsd"))]
pub const CONTROL_NAME: &str = "controlD";
#[cfg(target_os = "freebsd")]
pub const CONTROL_NAME: &str = "drmC";
#[cfg(not(target_os = "openbsd"))]
pub const RENDER_NAME: &str = "renderD";
#[cfg(target_os = "freebsd")]
pub const RENDER_NAME: &str = "drmR";
#[cfg(not(target_os = "openbsd"))]
pub const DIR_NAME: &str = "dev";
#[cfg(target_os = "openbsd")]
pub const DIR_NAME: &str = "dev/dri";

380
src/backend/drm/node/mod.rs Normal file
View File

@ -0,0 +1,380 @@
pub(crate) mod constants;
use constants::*;
use libc::dev_t;
use std::{
fmt::{self, Display, Formatter},
fs, io,
os::unix::prelude::{AsRawFd, IntoRawFd, RawFd},
path::PathBuf,
};
use nix::{
fcntl::{self, OFlag},
sys::stat::{fstat, major, minor, Mode},
unistd::close,
};
/// A node which refers to a DRM device.
#[derive(Debug)]
pub struct DrmNode {
// Always `Some`, None variant is used when taking ownership
fd: Option<RawFd>,
dev: dev_t,
ty: NodeType,
}
impl DrmNode {
/// Creates a DRM node from a file descriptor.
///
/// This function takes ownership of the passed in file descriptor, which will be closed when
/// dropped.
pub fn from_fd<A: AsRawFd>(fd: A) -> Result<DrmNode, CreateDrmNodeError> {
let fd = fd.as_raw_fd();
let stat = fstat(fd).map_err(Into::<io::Error>::into)?;
let dev = stat.st_rdev;
let major = major(dev);
let minor = minor(dev);
if !is_device_drm(major, minor) {
return Err(CreateDrmNodeError::NotDrmNode);
}
/*
The type of the DRM node is determined by the minor number ranges.
0-63 -> Primary
64-127 -> Control
128-255 -> Render
*/
let ty = match minor >> 6 {
0 => NodeType::Primary,
1 => NodeType::Control,
2 => NodeType::Render,
_ => return Err(CreateDrmNodeError::NotDrmNode),
};
Ok(DrmNode {
fd: Some(fd),
dev,
ty,
})
}
/// Creates a DRM node of the specified type using the same DRM device as the provided node.
/// The provided node will be consumed if the new node is successfully created.
///
/// This function is useful for obtaining the render node of a DRM device when the provided
/// node is a primary node.
pub fn from_node_with_type(node: DrmNode, ty: NodeType) -> Result<DrmNode, ConvertNodeError> {
from_node_with_type(node, ty)
}
/// Returns the type of the DRM node.
pub fn ty(&self) -> NodeType {
self.ty
}
/// Returns the device_id of the underlying DRM node.
pub fn dev_id(&self) -> dev_t {
self.dev
}
/// Returns the path of the open device if possible.
pub fn dev_path(&self) -> Option<PathBuf> {
fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
}
/// Returns the major device number of the DRM device.
pub fn major(&self) -> u64 {
major(self.dev_id())
}
/// Returns the minor device number of the DRM device.
pub fn minor(&self) -> u64 {
minor(self.dev_id())
}
/// Returns whether the DRM device has render nodes.
pub fn has_render(&self) -> bool {
#[cfg(target_os = "linux")]
{
node_path(self, NodeType::Render).is_ok()
}
// TODO: More robust checks on non-linux.
#[cfg(target_os = "freebsd")]
{
false
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
{
false
}
}
}
impl Display for DrmNode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.ty.minor_name_prefix(), minor(self.dev_id()))
}
}
impl IntoRawFd for DrmNode {
fn into_raw_fd(mut self) -> RawFd {
self.fd.take().unwrap()
}
}
impl AsRawFd for DrmNode {
fn as_raw_fd(&self) -> RawFd {
self.fd.unwrap()
}
}
impl Drop for DrmNode {
fn drop(&mut self) {
if let Some(fd) = self.fd {
let _ = close(fd);
}
}
}
/// A type of node
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum NodeType {
/// A primary node may be used to allocate buffers.
///
/// If no other node is present, this may be used to post a buffer to an output with mode-setting.
Primary,
/// A control node may be used for mode-setting.
///
/// This is almost never used since no DRM API for control nodes is available yet.
Control,
/// A render node may be used by a client to allocate buffers.
///
/// Mode-setting is not possible with a render node.
Render,
}
impl NodeType {
/// Returns a string representing the prefix of a minor device's name.
///
/// For example, on Linux with a primary node, the returned string would be `card`.
pub fn minor_name_prefix(&self) -> &str {
match self {
NodeType::Primary => PRIMARY_NAME,
NodeType::Control => CONTROL_NAME,
NodeType::Render => RENDER_NAME,
}
}
}
impl Display for NodeType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
NodeType::Primary => "Primary",
NodeType::Control => "Control",
NodeType::Render => "Render",
}
)
}
}
/// An error that may occur when creating a DrmNode from a file descriptor.
#[derive(Debug, thiserror::Error)]
pub enum CreateDrmNodeError {
/// Some underlying IO error occured while trying to create a DRM node.
#[error("{0}")]
Io(io::Error),
/// The provided file descriptor does not refer to a DRM node.
#[error("the provided file descriptor does not refer to a DRM node.")]
NotDrmNode,
}
impl From<io::Error> for CreateDrmNodeError {
fn from(err: io::Error) -> Self {
CreateDrmNodeError::Io(err)
}
}
/// A DRM node could not be used to get an alternative type of node.
///
/// If this error is returned, the original DRM node may be retrieved using [`ConvertNodeError::node`].
#[derive(Debug, thiserror::Error)]
#[error("{kind}")]
pub struct ConvertNodeError {
node: DrmNode,
kind: ConvertErrorKind,
}
impl ConvertNodeError {
/// Returns the original DrmNode.
pub fn node(self) -> DrmNode {
self.node
}
/// Returns the kind of error that occurred when obtaining a different type of DRM node.
pub fn kind(&self) -> &ConvertErrorKind {
&self.kind
}
}
/// An error that may occur when obtaining a different type of DRM node.
#[derive(Debug, thiserror::Error)]
pub enum ConvertErrorKind {
/// The requested node type is not available.
#[error("the requested node type, {0}, is not available.")]
NodeTypeNotAvailable(NodeType),
/// An IO error occurred when when obtaining a different type of DRM node.
#[error("{0}")]
Io(io::Error),
}
impl From<io::Error> for ConvertErrorKind {
fn from(err: io::Error) -> Self {
ConvertErrorKind::Io(err)
}
}
#[cfg(target_os = "linux")]
fn is_device_drm(major: u64, minor: u64) -> bool {
use nix::sys::stat::stat;
let path = format!("/sys/dev/char/{}:{}/device/drm", major, minor);
stat(path.as_str()).is_ok()
}
#[cfg(target_os = "freebsd")]
pub fn is_device_drm(major: u64, _minor: u64) -> bool {
use nix::sys::stat::makedev;
use nix::sys::stat::SFlag;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
let dev_name = Vec::<c_char>::with_capacity(255); // Matching value of SPECNAMELEN in FreeBSD 13+
let buf: *mut c_char = unsafe {
libc::devname_r(
makedev(major, minor),
SFlag::S_IFCHR.bits(), // Must be S_IFCHR or S_IFBLK
dev_name.as_mut_ptr(),
dev_name.len() as c_int,
)
};
// Buffer was too small (weird issue with the size of buffer) or the device could not be named.
if buf.is_null() {
return Err(CreateDrmNodeError::NotDrmNode);
}
// SAFETY: The buffer written to by devname_r is guaranteed to be NUL terminated.
let dev_name = unsafe { CStr::from_ptr(buf) };
let dev_name = dev_name.to_str().expect("Returned device name is not valid utf8");
dev_name.starts_with("drm/")
|| dev_name.starts_with("dri/card")
|| dev_name.starts_with("dri/control")
|| dev_name.starts_with("dri/renderD")
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
pub fn is_device_drm(major: u64, _minor: u64) -> bool {
major == DRM_MAJOR
}
#[cfg(target_os = "linux")]
fn from_node_with_type(node: DrmNode, ty: NodeType) -> Result<DrmNode, ConvertNodeError> {
match node_path(&node, ty) {
Ok(path) => {
let fd = match fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty())
.map_err(Into::<io::Error>::into)
.map_err(Into::<ConvertErrorKind>::into)
{
Ok(fd) => fd,
Err(kind) => return Err(ConvertNodeError { node, kind }),
};
let result = DrmNode::from_fd(fd);
match result {
Ok(node) => Ok(node),
// Some error occurred while opening the new node
Err(CreateDrmNodeError::Io(err)) => Err(ConvertNodeError {
node,
kind: err.into(),
}),
_ => unreachable!(),
}
}
Err(err) => Err(ConvertNodeError {
node,
kind: err.into(),
}),
}
}
#[cfg(target_os = "freebsd")]
fn from_node_with_type(node: DrmNode, ty: NodeType) -> Result<DrmNode, ConvertNodeError> {
// TODO: Not implemented yet, so fail conversion
Err(ConvertNodeError {
node,
kind: ConvertErrorKind::NodeTypeNotAvailable(ty),
})
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
fn from_node_with_type(node: DrmNode, ty: NodeType) -> Result<DrmNode, ConvertNodeError> {
// TODO: Not implemented yet, so fail conversion
Err(ConvertNodeError {
node,
kind: ConvertErrorKind::NodeTypeNotAvailable(ty),
})
}
/// Returns the path of a specific type of node from the same DRM device as an existing DrmNode.
#[cfg(target_os = "linux")]
fn node_path(node: &DrmNode, ty: NodeType) -> io::Result<PathBuf> {
use std::io::ErrorKind;
let major = node.major();
let minor = node.minor();
let read = fs::read_dir(format!("/sys/dev/char/{}:{}/device/drm", major, minor))?;
for entry in read.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
// Only 1 primary, control and render node may exist simultaneously, so the
// first occurrence is good enough.
if name.starts_with(ty.minor_name_prefix()) {
let mut path = entry.path();
path.push(DIR_NAME);
if path.exists() {
return Ok(path);
}
}
}
Err(io::Error::new(
ErrorKind::NotFound,
format!(
"Could not find node of type {} from DRM device {}:{}",
ty, major, minor
),
))
}

View File

@ -16,6 +16,9 @@ use winit::{platform::unix::WindowExtUnix, window::Window as WinitWindow};
#[cfg(feature = "backend_gbm")]
use gbm::{AsRaw, Device as GbmDevice};
#[cfg(feature = "backend_x11")]
use crate::backend::x11::X11Surface;
/// Create a `EGLPlatform<'a>` for the provided platform.
///
/// # Arguments
@ -165,6 +168,27 @@ impl EGLNativeDisplay for WinitWindow {
}
}
#[cfg(feature = "backend_x11")]
impl EGLNativeDisplay for X11Surface {
fn supported_platforms(&self) -> Vec<EGLPlatform<'_>> {
vec![
// todo: https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_device.txt
// see: https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_platform_gbm.txt
egl_platform!(
PLATFORM_GBM_KHR,
self.device().as_raw(),
&["EGL_KHR_platform_gbm"]
),
// see: https://www.khronos.org/registry/EGL/extensions/MESA/EGL_MESA_platform_gbm.txt
egl_platform!(
PLATFORM_GBM_MESA,
self.device().as_raw(),
&["EGL_MESA_platform_gbm"]
),
]
}
}
/// Trait for types returning valid surface pointers for initializing egl
///
/// ## Unsafety

View File

@ -77,7 +77,7 @@ impl<B: InputBackend> Event<B> for UnusedEvent {
}
/// State of key on a keyboard. Either pressed or released
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum KeyState {
/// Key is released
Released,
@ -110,7 +110,7 @@ impl<B: InputBackend> KeyboardKeyEvent<B> for UnusedEvent {
}
/// A particular mouse button
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum MouseButton {
/// Left mouse button
Left,
@ -123,7 +123,7 @@ pub enum MouseButton {
}
/// State of a button on a pointer device, like mouse or tablet tool. Either pressed or released
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum ButtonState {
/// Button is released
Released,
@ -150,7 +150,7 @@ impl<B: InputBackend> PointerButtonEvent<B> for UnusedEvent {
}
/// Axis when scrolling
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Axis {
/// Vertical axis
Vertical,

View File

@ -55,13 +55,26 @@
//! moment, even clients using dma-buf still require that the `wl_drm` infrastructure is
//! initialized to have hardware-acceleration.
//!
//! ## X11 backend
//!
//! Alongside this infrastructure, Smithay also provides an alternative backend based on
//! [x11rb](https://crates.io/crates/x11rb), which makes it possible to run your compositor as
//! an X11 client. This is generally quite helpful for development and debugging.
//!
//! The X11 backend does not concern itself with what renderer is in use, allowing presentation to
//! the window assuming you can provide it with a [`Dmabuf`](crate::backend::allocator::dmabuf::Dmabuf).
//! The X11 backend is also an input provider, and is accessible in the [`x11`] module, gated by
//! the `backend_x11` cargo feature.
//!
//! ## Winit backend
//!
//! Alongside this infrastructure, Smithay also provides an alternative backend based on
//! [winit](https://crates.io/crates/winit), which makes it possible to run your compositor as
//! a Wayland or X11 client. This is generally quite helpful for development and debugging.
//! That backend is both a renderer and an input provider, and is accessible in the [`winit`]
//! module, gated by the `backend_winit` cargo feature.
//! a Wayland or X11 client. You are encouraged to use the X11 backend where possible since winit
//! does not integrate into calloop too well. This backend is generally quite helpful for
//! development and debugging. That backend is both a renderer and an input provider, and is
//! accessible in the [`winit`] module, gated by the `backend_winit` cargo feature.
//!
pub mod allocator;
pub mod input;
@ -81,6 +94,9 @@ pub mod udev;
#[cfg(feature = "backend_winit")]
pub mod winit;
#[cfg(feature = "backend_x11")]
pub mod x11;
/// Error that can happen when swapping buffers.
#[derive(Debug, thiserror::Error)]
pub enum SwapBuffersError {

231
src/backend/x11/buffer.rs Normal file
View File

@ -0,0 +1,231 @@
//! Utilities for importing buffers into X11.
//!
//! Buffers imported into X11 are represented as X pixmaps which are then presented to the window.
//!
//! At the moment only [`Dmabuf`] backed pixmaps are supported.
//!
//! ## Dmabuf pixmaps
//!
//! A [`Dmabuf`] backed pixmap is created using the [`DRI3`](x11rb::protocol::dri3) extension of
//! the X server. One of two code paths is used here. For more modern DRI3 (>= 1.2) implementations
//! multi-plane Dmabufs, may be used to create a pixmap. Otherwise the fallback code path
//! (available in >= 1.0) is used to create the pixmap. Although the Dmabuf may only have one plane.
//!
//! If you do need to modify any of the logic pertaining to the Dmabuf presentation, do ensure you
//! read the `dri3proto.txt` file (link in the non-public comments of the x11 mod.rs).
//!
//! ## Presentation to the window
//!
//! Presentation to the window is handled through the [`Present`](x11rb::protocol::present)
//! extension of the X server. Because we use direct rendering to present to the window, using
//! V-Sync from OpenGL or the equivalents in other rendering APIs will not work. This is where
//! the utility of the present extension is useful. When using the `present_pixmap` function,
//! the X server will notify when the frame has been presented to the window. The notification
//! of presentation usually occurs on a V-blank.
//!
//! If you do need to modify any of the logic pertaining to the using the present extension, do
//! ensure you read the `presentproto.txt` file (link in the non-public comments of the
//! x11 mod.rs).
use std::sync::atomic::Ordering;
use super::{Window, X11Error};
use drm_fourcc::DrmFourcc;
use nix::fcntl;
use x11rb::connection::Connection;
use x11rb::protocol::dri3::ConnectionExt as _;
use x11rb::protocol::present::{self, ConnectionExt};
use x11rb::protocol::xproto::PixmapWrapper;
use x11rb::rust_connection::{ConnectionError, ReplyOrIdError};
use x11rb::utils::RawFdContainer;
use crate::backend::allocator::dmabuf::Dmabuf;
use crate::backend::allocator::Buffer;
// Shm can be easily supported in the future using, xcb_shm_create_pixmap.
#[derive(Debug, thiserror::Error)]
pub enum CreatePixmapError {
#[error("An x11 protocol error occured")]
Protocol(X11Error),
#[error("The Dmabuf had too many planes")]
TooManyPlanes,
#[error("Duplicating the file descriptors for the dmabuf handles failed")]
DupFailed(String),
#[error("Buffer had incorrect format, expected: {0}")]
IncorrectFormat(DrmFourcc),
}
impl From<X11Error> for CreatePixmapError {
fn from(e: X11Error) -> Self {
CreatePixmapError::Protocol(e)
}
}
impl From<ReplyOrIdError> for CreatePixmapError {
fn from(e: ReplyOrIdError) -> Self {
X11Error::from(e).into()
}
}
impl From<ConnectionError> for CreatePixmapError {
fn from(e: ConnectionError) -> Self {
X11Error::from(e).into()
}
}
pub trait PixmapWrapperExt<'c, C>
where
C: Connection,
{
/// Creates a new Pixmap using the supplied Dmabuf.
///
/// The returned Pixmap is freed when dropped.
fn with_dmabuf(
connection: &'c C,
window: &Window,
dmabuf: &Dmabuf,
) -> Result<PixmapWrapper<'c, C>, CreatePixmapError>;
/// Presents the pixmap to the window.
///
/// The wrapper is consumed when this function is called. The return value will contain the
/// id of the pixmap.
///
/// The pixmap will be automatically dropped when it bubbles up in the X11 event loop after the
/// X server has finished presentation with the buffer behind the pixmap.
fn present(self, connection: &C, window: &Window) -> Result<u32, X11Error>;
}
impl<'c, C> PixmapWrapperExt<'c, C> for PixmapWrapper<'c, C>
where
C: Connection,
{
fn with_dmabuf(
connection: &'c C,
window: &Window,
dmabuf: &Dmabuf,
) -> Result<PixmapWrapper<'c, C>, CreatePixmapError> {
let window_inner = window.0.upgrade().unwrap();
if dmabuf.format().code != window_inner.format {
return Err(CreatePixmapError::IncorrectFormat(window_inner.format));
}
let mut fds = Vec::new();
// XCB closes the file descriptor after sending, so duplicate the file descriptors.
for handle in dmabuf.handles() {
let fd = fcntl::fcntl(
handle,
fcntl::FcntlArg::F_DUPFD_CLOEXEC(3), // Set to 3 so the fd cannot become stdin, stdout or stderr
)
.map_err(|e| CreatePixmapError::DupFailed(e.to_string()))?;
fds.push(RawFdContainer::new(fd))
}
// We need dri3 >= 1.2 in order to use the enhanced dri3_pixmap_from_buffers function.
let xid = if window_inner.extensions.dri3 >= (1, 2) {
if dmabuf.num_planes() > 4 {
return Err(CreatePixmapError::TooManyPlanes);
}
let xid = connection.generate_id()?;
let mut strides = dmabuf.strides();
let mut offsets = dmabuf.offsets();
connection.dri3_pixmap_from_buffers(
xid,
window.id(),
dmabuf.width() as u16,
dmabuf.height() as u16,
strides.next().unwrap(), // there must be at least one plane and stride.
offsets.next().unwrap(),
// The other planes are optional, so unwrap_or to `NONE` if those planes are not available.
strides.next().unwrap_or(x11rb::NONE),
offsets.next().unwrap_or(x11rb::NONE),
strides.next().unwrap_or(x11rb::NONE),
offsets.next().unwrap_or(x11rb::NONE),
strides.next().unwrap_or(x11rb::NONE),
offsets.next().unwrap_or(x11rb::NONE),
window.depth(),
// In the future this could be made nicer.
match window.format().unwrap() {
DrmFourcc::Argb8888 => 32,
DrmFourcc::Xrgb8888 => 24,
_ => unreachable!(),
},
dmabuf.format().modifier.into(),
fds,
)?;
xid
} else {
// Old codepath can only create a pixmap using one plane from a dmabuf.
if dmabuf.num_planes() != 1 {
return Err(CreatePixmapError::TooManyPlanes);
}
let xid = connection.generate_id()?;
let mut strides = dmabuf.strides();
let stride = strides.next().unwrap();
connection.dri3_pixmap_from_buffer(
xid,
window.id(),
dmabuf.height() * stride,
dmabuf.width() as u16,
dmabuf.height() as u16,
stride as u16,
window.depth(),
// In the future this could be made nicer.
match window.format().unwrap() {
DrmFourcc::Argb8888 => 32,
DrmFourcc::Xrgb8888 => 24,
_ => unreachable!(),
},
fds.remove(0),
)?;
xid
};
Ok(PixmapWrapper::for_pixmap(connection, xid))
}
fn present(self, connection: &C, window: &Window) -> Result<u32, X11Error> {
let window_inner = window.0.upgrade().unwrap(); // We have the connection and window alive.
let next_serial = window_inner.next_serial.fetch_add(1, Ordering::SeqCst);
// We want to present as soon as possible, so wait 1ms so the X server will present when next convenient.
let msc = window_inner.last_msc.load(Ordering::SeqCst) + 1;
// options parameter does not take the enum but a u32.
const OPTIONS: present::Option = present::Option::NONE;
connection.present_pixmap(
window.id(),
self.pixmap(),
next_serial,
x11rb::NONE, // Update the entire window
x11rb::NONE, // Update the entire window
0, // No offsets
0,
x11rb::NONE, // Let the X server pick the most suitable crtc
x11rb::NONE, // Do not wait to present
x11rb::NONE, // We will wait for the X server to tell us when it is done with the pixmap.
OPTIONS.into(), // No special presentation options.
msc,
0,
0,
&[], // We don't need to notify any other windows.
)?;
// Pixmaps are reference counted on the X server. Because of reference counting we may
// drop the wrapper and the X server will free the pixmap when presentation has completed.
Ok(self.pixmap())
}
}

161
src/backend/x11/error.rs Normal file
View File

@ -0,0 +1,161 @@
use std::io;
use nix::errno::Errno;
use x11rb::rust_connection::{ConnectError, ConnectionError, ReplyError, ReplyOrIdError};
use crate::backend::{allocator::gbm::GbmConvertError, drm::CreateDrmNodeError};
/// An error emitted by the X11 backend during setup.
#[derive(Debug, thiserror::Error)]
pub enum X11Error {
/// Connecting to the X server failed.
#[error("Connecting to the X server failed")]
ConnectionFailed(ConnectError),
/// A required X11 extension was not present or has the right version.
#[error("{0}")]
MissingExtension(MissingExtensionError),
/// Some protocol error occurred during setup.
#[error("Some protocol error occurred during setup")]
Protocol(ReplyOrIdError),
/// Creating the window failed.
#[error("Creating the window failed")]
CreateWindow(CreateWindowError),
/// The X server is not capable of direct rendering.
#[error("The X server is not capable of direct rendering")]
CannotDirectRender,
/// Failed to allocate buffers needed to present to the window.
#[error("Failed to allocate buffers needed to present to the window")]
Allocation(AllocateBuffersError),
}
impl From<ConnectError> for X11Error {
fn from(err: ConnectError) -> Self {
Self::ConnectionFailed(err)
}
}
impl From<ReplyError> for X11Error {
fn from(err: ReplyError) -> Self {
Self::Protocol(err.into())
}
}
impl From<ConnectionError> for X11Error {
fn from(err: ConnectionError) -> Self {
Self::Protocol(err.into())
}
}
impl From<ReplyOrIdError> for X11Error {
fn from(err: ReplyOrIdError) -> Self {
Self::Protocol(err)
}
}
/// An error that occurs when a required X11 extension is not present.
#[derive(Debug, thiserror::Error)]
pub enum MissingExtensionError {
/// An extension was not found.
#[error("Extension \"{name}\" version {major}.{minor} was not found.")]
NotFound {
/// The name of the required extension.
name: &'static str,
/// The minimum required major version of extension.
major: u32,
/// The minimum required minor version of extension.
minor: u32,
},
/// An extension was present, but the version is too low.
#[error("Extension \"{name}\" version {required_major}.{required_minor} is required but only version {available_major}.{available_minor} is available.")]
WrongVersion {
/// The name of the extension.
name: &'static str,
/// The minimum required major version of extension.
required_major: u32,
/// The minimum required minor version of extension.
required_minor: u32,
/// The major version of the extension available on the X server.
available_major: u32,
/// The minor version of the extension available on the X server.
available_minor: u32,
},
}
impl From<MissingExtensionError> for X11Error {
fn from(err: MissingExtensionError) -> Self {
Self::MissingExtension(err)
}
}
/// An error which may occur when creating an X11 window.
#[derive(Debug, thiserror::Error)]
pub enum CreateWindowError {
/// No depth fulfilling the pixel format requirements was found.
#[error("No depth fulfilling the requirements was found")]
NoDepth,
/// No visual fulfilling the pixel format requirements was found.
#[error("No visual fulfilling the requirements was found")]
NoVisual,
}
impl From<CreateWindowError> for X11Error {
fn from(err: CreateWindowError) -> Self {
Self::CreateWindow(err)
}
}
/// An error which may occur when allocating buffers for presentation to the window.
#[derive(Debug, thiserror::Error)]
pub enum AllocateBuffersError {
/// Failed to open the DRM device to allocate buffers.
#[error("Failed to open the DRM device to allocate buffers.")]
OpenDevice(io::Error),
/// The device used to allocate buffers is not the correct drm node type.
#[error("The device used to allocate buffers is not the correct drm node type.")]
UnsupportedDrmNode,
/// Exporting a dmabuf failed.
#[error("Exporting a dmabuf failed.")]
ExportDmabuf(GbmConvertError),
}
impl From<Errno> for AllocateBuffersError {
fn from(err: Errno) -> Self {
Self::OpenDevice(err.into())
}
}
impl From<io::Error> for AllocateBuffersError {
fn from(err: io::Error) -> Self {
Self::OpenDevice(err)
}
}
impl From<GbmConvertError> for AllocateBuffersError {
fn from(err: GbmConvertError) -> Self {
Self::ExportDmabuf(err)
}
}
impl From<CreateDrmNodeError> for AllocateBuffersError {
fn from(err: CreateDrmNodeError) -> Self {
match err {
CreateDrmNodeError::Io(err) => AllocateBuffersError::OpenDevice(err),
CreateDrmNodeError::NotDrmNode => AllocateBuffersError::UnsupportedDrmNode,
}
}
}
impl From<AllocateBuffersError> for X11Error {
fn from(err: AllocateBuffersError) -> Self {
Self::Allocation(err)
}
}

View File

@ -0,0 +1,123 @@
use super::{MissingExtensionError, X11Error};
/// The extension macro.
///
/// This macro generates a struct which checks for the presence of some X11 extensions and stores
/// the version supplied by the X server.
///
/// ```rust
/// extensions! {
/// // The extension to check for. This should correspond to the name of the extension inside x11rb's `x11rb::protocol::xproto::<name>` module path.
/// xfixes {
/// // The function used to query the available version of the extension. This will be inside the module path as explained above
/// xfixes_query_version,
/// // The minimum version of the extension that will be accepted.
/// minimum: (4, 0),
/// // The version of the extension to request.
/// request: (4, 0),
/// },
/// }
///
/// // The extensions may be checked then using the generated `Extensions` struct using the `check_extensions` function.
/// ```
macro_rules! extensions {
(
$(
$extension:ident { // Extension name for path lookup
$extension_fn:ident, // Function used to look up the version of the extension
minimum: ($min_major:expr, $min_minor:expr),
request: ($req_major:expr, $req_minor:expr),
},
)*
) => {
#[derive(Debug, Copy, Clone)]
pub struct Extensions {
$(
#[doc = concat!(" The version of the `", stringify!($extension), "` extension.")]
pub $extension: (u32, u32),
)*
}
impl Extensions {
pub fn check_extensions<C: x11rb::connection::Connection>(connection: &C, logger: &slog::Logger) -> Result<Extensions, X11Error> {
$(
let $extension = {
use x11rb::protocol::$extension::{ConnectionExt as _, X11_EXTENSION_NAME};
if connection.extension_information(X11_EXTENSION_NAME)?.is_some() {
let version = connection.$extension_fn($req_major, $req_minor)?.reply()?;
#[allow(unused_comparisons)] // Macro comparisons
if version.major_version >= $req_major
|| (version.major_version == $req_major && version.minor_version >= $req_minor)
{
slog::info!(
logger,
"Loaded extension {} version {}.{}",
X11_EXTENSION_NAME,
version.major_version,
version.minor_version,
);
(version.major_version, version.minor_version)
} else {
slog::error!(
logger,
"{} extension version is too low (have {}.{}, expected {}.{})",
X11_EXTENSION_NAME,
version.major_version,
version.minor_version,
$req_major,
$req_minor,
);
return Err(MissingExtensionError::WrongVersion {
name: X11_EXTENSION_NAME,
required_major: $req_major,
required_minor: $req_minor,
available_major: version.major_version,
available_minor: version.minor_version,
}.into());
}
} else {
slog::error!(logger, "{} extension not found", X11_EXTENSION_NAME);
return Err(MissingExtensionError::NotFound {
name: X11_EXTENSION_NAME,
major: $min_major,
minor: $min_minor,
}
.into());
}
};
)*
Ok(Extensions {
$(
$extension,
)*
})
}
}
};
}
extensions! {
present {
present_query_version,
minimum: (1, 0),
request: (1, 0),
},
xfixes {
xfixes_query_version,
minimum: (4, 0),
request: (4, 0),
},
dri3 {
dri3_query_version,
minimum: (1, 0),
request: (1, 2),
},
}

217
src/backend/x11/input.rs Normal file
View File

@ -0,0 +1,217 @@
//! Input backend implementation for the X11 backend.
use super::X11Error;
use crate::{
backend::input::{
self, Axis, AxisSource, ButtonState, Device, DeviceCapability, InputBackend, InputEvent, KeyState,
KeyboardKeyEvent, MouseButton, PointerAxisEvent, PointerButtonEvent, PointerMotionAbsoluteEvent,
UnusedEvent,
},
utils::{Logical, Size},
};
/// Marker used to define the `InputBackend` types for the X11 backend.
#[derive(Debug)]
pub struct X11Input;
/// Virtual input device used by the backend to associate input events.
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct X11VirtualDevice;
impl Device for X11VirtualDevice {
fn id(&self) -> String {
"x11".to_owned()
}
fn name(&self) -> String {
"x11 virtual input".to_owned()
}
fn has_capability(&self, capability: DeviceCapability) -> bool {
matches!(
capability,
DeviceCapability::Keyboard | DeviceCapability::Pointer | DeviceCapability::Touch
)
}
fn usb_id(&self) -> Option<(u32, u32)> {
None
}
fn syspath(&self) -> Option<std::path::PathBuf> {
None
}
}
/// X11-Backend internal event wrapping `X11`'s types into a [`KeyboardKeyEvent`].
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct X11KeyboardInputEvent {
pub(crate) time: u32,
pub(crate) key: u32,
pub(crate) count: u32,
pub(crate) state: KeyState,
}
impl input::Event<X11Input> for X11KeyboardInputEvent {
fn time(&self) -> u32 {
self.time
}
fn device(&self) -> X11VirtualDevice {
X11VirtualDevice
}
}
impl KeyboardKeyEvent<X11Input> for X11KeyboardInputEvent {
fn key_code(&self) -> u32 {
self.key
}
fn state(&self) -> KeyState {
self.state
}
fn count(&self) -> u32 {
self.count
}
}
/// X11-Backend internal event wrapping `X11`'s types into a [`PointerAxisEvent`]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct X11MouseWheelEvent {
pub(crate) time: u32,
pub(crate) axis: Axis,
pub(crate) amount: f64,
}
impl input::Event<X11Input> for X11MouseWheelEvent {
fn time(&self) -> u32 {
self.time
}
fn device(&self) -> X11VirtualDevice {
X11VirtualDevice
}
}
impl PointerAxisEvent<X11Input> for X11MouseWheelEvent {
fn amount(&self, _axis: Axis) -> Option<f64> {
None
}
fn amount_discrete(&self, axis: Axis) -> Option<f64> {
if self.axis == axis {
Some(self.amount)
} else {
Some(0.0)
}
}
fn source(&self) -> AxisSource {
// X11 seems to act within the scope of individual rachets of a scroll wheel.
AxisSource::Wheel
}
}
/// X11-Backend internal event wrapping `X11`'s types into a [`PointerButtonEvent`]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct X11MouseInputEvent {
pub(crate) time: u32,
pub(crate) button: MouseButton,
pub(crate) state: ButtonState,
}
impl input::Event<X11Input> for X11MouseInputEvent {
fn time(&self) -> u32 {
self.time
}
fn device(&self) -> X11VirtualDevice {
X11VirtualDevice
}
}
impl PointerButtonEvent<X11Input> for X11MouseInputEvent {
fn button(&self) -> MouseButton {
self.button
}
fn state(&self) -> ButtonState {
self.state
}
}
/// X11-Backend internal event wrapping `X11`'s types into a [`PointerMotionAbsoluteEvent`]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct X11MouseMovedEvent {
pub(crate) time: u32,
pub(crate) x: f64,
pub(crate) y: f64,
pub(crate) size: Size<u16, Logical>,
}
impl input::Event<X11Input> for X11MouseMovedEvent {
fn time(&self) -> u32 {
self.time
}
fn device(&self) -> X11VirtualDevice {
X11VirtualDevice
}
}
impl PointerMotionAbsoluteEvent<X11Input> for X11MouseMovedEvent {
fn x(&self) -> f64 {
self.x
}
fn y(&self) -> f64 {
self.y
}
fn x_transformed(&self, width: i32) -> f64 {
f64::max(self.x * width as f64 / self.size.w as f64, 0.0)
}
fn y_transformed(&self, height: i32) -> f64 {
f64::max(self.y * height as f64 / self.size.h as f64, 0.0)
}
}
impl InputBackend for X11Input {
type EventError = X11Error;
type Device = X11VirtualDevice;
type KeyboardKeyEvent = X11KeyboardInputEvent;
type PointerAxisEvent = X11MouseWheelEvent;
type PointerButtonEvent = X11MouseInputEvent;
type PointerMotionEvent = UnusedEvent;
type PointerMotionAbsoluteEvent = X11MouseMovedEvent;
type TouchDownEvent = UnusedEvent;
type TouchUpEvent = UnusedEvent;
type TouchMotionEvent = UnusedEvent;
type TouchCancelEvent = UnusedEvent;
type TouchFrameEvent = UnusedEvent;
type TabletToolAxisEvent = UnusedEvent;
type TabletToolProximityEvent = UnusedEvent;
type TabletToolTipEvent = UnusedEvent;
type TabletToolButtonEvent = UnusedEvent;
type SpecialEvent = UnusedEvent;
fn dispatch_new_events<F>(&mut self, _callback: F) -> Result<(), Self::EventError>
where
F: FnMut(InputEvent<Self>),
{
// The implementation of the trait here is exclusively for type definitions.
// See `X11Event::Input` to handle input events.
unreachable!()
}
}

891
src/backend/x11/mod.rs Normal file
View File

@ -0,0 +1,891 @@
//! Implementation of the backend types using X11.
//!
//! This backend provides the appropriate backend implementations to run a Wayland compositor as an
//! X11 client.
//!
//! The backend is initialized using [`X11Backend::new`](self::X11Backend::new). The function will
//! return two objects:
//!
//! - an [`X11Backend`], which you will insert into an [`EventLoop`](calloop::EventLoop) to process events from the backend.
//! - an [`X11Surface`], which represents a surface that buffers are presented to for display.
//!
//! ## Example usage
//!
//! ```rust,no_run
//! # use std::error::Error;
//! # use smithay::backend::x11::X11Backend;
//! # struct CompositorState;
//! fn init_x11_backend(
//! handle: calloop::LoopHandle<CompositorState>,
//! logger: slog::Logger
//! ) -> Result<(), Box<dyn Error>> {
//! // Create the backend, also yielding a surface that may be used to render to the window.
//! let (backend, surface) = X11Backend::new(logger)?;
//! // You can get a handle to the window the backend has created for later use.
//! let window = backend.window();
//!
//! // Insert the backend into the event loop to receive events.
//! handle.insert_source(backend, |event, _window, state| {
//! // Process events from the X server that apply to the window.
//! })?;
//!
//! Ok(())
//! }
//! ```
//!
//! ## EGL
//!
//! When using [`EGL`](crate::backend::egl), an [`X11Surface`] may be used to create an [`EGLDisplay`](crate::backend::egl::EGLDisplay).
/*
A note for future contributors and maintainers:
Do take a look at some useful reading in order to understand this backend more deeply:
DRI3 protocol documentation: https://gitlab.freedesktop.org/xorg/proto/xorgproto/-/blob/master/dri3proto.txt
Present protocol documentation: https://gitlab.freedesktop.org/xorg/proto/xorgproto/-/blob/master/presentproto.txt
*/
mod buffer;
mod error;
#[macro_use]
mod extension;
mod input;
mod window_inner;
use self::{buffer::PixmapWrapperExt, window_inner::WindowInner};
use crate::{
backend::{
allocator::dmabuf::{AsDmabuf, Dmabuf},
drm::{DrmNode, NodeType},
input::{Axis, ButtonState, InputEvent, KeyState, MouseButton},
},
utils::{x11rb::X11Source, Logical, Size},
};
use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
use drm_fourcc::DrmFourcc;
use gbm::BufferObjectFlags;
use nix::fcntl;
use slog::{error, info, o, Logger};
use std::{
io, mem,
os::unix::prelude::AsRawFd,
sync::{
atomic::{AtomicU32, Ordering},
mpsc::{self, Receiver, Sender},
Arc, Weak,
},
};
use x11rb::{
atom_manager,
connection::Connection,
protocol::{
self as x11,
dri3::ConnectionExt as _,
xproto::{ColormapAlloc, ConnectionExt, Depth, PixmapWrapper, VisualClass},
ErrorKind,
},
rust_connection::{ReplyError, RustConnection},
};
pub use self::error::*;
use self::extension::Extensions;
pub use self::input::*;
/// An event emitted by the X11 backend.
#[derive(Debug)]
pub enum X11Event {
/// The X server has required the compositor to redraw the contents of window.
Refresh,
/// An input event occurred.
Input(InputEvent<X11Input>),
/// The window was resized.
Resized(Size<u16, Logical>),
/// The last buffer presented to the window has been displayed.
///
/// When this event is scheduled, the next frame may be rendered.
PresentCompleted,
/// The window has received a request to be closed.
CloseRequested,
}
/// Represents an active connection to the X to manage events on the Window provided by the backend.
#[derive(Debug)]
pub struct X11Backend {
log: Logger,
connection: Arc<RustConnection>,
source: X11Source,
screen_number: usize,
window: Arc<WindowInner>,
resize: Sender<Size<u16, Logical>>,
key_counter: Arc<AtomicU32>,
depth: Depth,
visual_id: u32,
}
atom_manager! {
pub(crate) Atoms: AtomCollectionCookie {
WM_PROTOCOLS,
WM_DELETE_WINDOW,
_NET_WM_NAME,
UTF8_STRING,
_SMITHAY_X11_BACKEND_CLOSE,
}
}
impl X11Backend {
/// Initializes the X11 backend.
///
/// This connects to the X server and configures the window using the default options.
pub fn new<L>(logger: L) -> Result<(X11Backend, X11Surface), X11Error>
where
L: Into<Option<::slog::Logger>>,
{
Self::with_size_and_title((1280, 800).into(), "Smithay", logger)
}
/// Initializes the X11 backend.
///
/// This connects to the X server and configures the window using the default size and the
/// specified window title.
pub fn with_title<L>(title: &str, logger: L) -> Result<(X11Backend, X11Surface), X11Error>
where
L: Into<Option<::slog::Logger>>,
{
Self::with_size_and_title((1280, 800).into(), title, logger)
}
/// Initializes the X11 backend.
///
/// This connects to the X server and configures the window using the default window title
/// and the specified window size.
pub fn with_size<L>(size: Size<u16, Logical>, logger: L) -> Result<(X11Backend, X11Surface), X11Error>
where
L: Into<Option<::slog::Logger>>,
{
Self::with_size_and_title(size, "Smithay", logger)
}
/// Initializes the X11 backend.
///
/// This connects to the X server and configures the window using the specified window size and title.
pub fn with_size_and_title<L>(
size: Size<u16, Logical>,
title: &str,
logger: L,
) -> Result<(X11Backend, X11Surface), X11Error>
where
L: Into<Option<slog::Logger>>,
{
let logger = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_x11"));
info!(logger, "Connecting to the X server");
let (connection, screen_number) = RustConnection::connect(None)?;
let connection = Arc::new(connection);
info!(logger, "Connected to screen {}", screen_number);
let extensions = Extensions::check_extensions(&*connection, &logger)?;
let screen = &connection.setup().roots[screen_number];
let depth = screen
.allowed_depths
.iter()
.find(|depth| depth.depth == 32) // Prefer 32-bit color
.or_else(|| screen.allowed_depths.iter().find(|depth| depth.depth == 24)) // 24-bit fallback for Xrgb8888
.cloned()
.ok_or(CreateWindowError::NoDepth)?;
// Next find a visual using the supported depth
let visual_id = depth
.visuals
.iter()
// Ensure the visual is little endian to comply with the format needed with X/ARGB8888
.filter(|visual| visual.red_mask == 0xff0000)
.find(|visual| visual.class == VisualClass::TRUE_COLOR)
.ok_or(CreateWindowError::NoVisual)?
.visual_id;
let format = match depth.depth {
24 => DrmFourcc::Xrgb8888,
32 => DrmFourcc::Argb8888,
_ => unreachable!(),
};
// Make a colormap
let colormap = connection.generate_id()?;
connection.create_colormap(ColormapAlloc::NONE, colormap, screen.root, visual_id)?;
let atoms = Atoms::new(&*connection)?.reply()?;
let window = Arc::new(WindowInner::new(
Arc::downgrade(&connection),
screen,
size,
title,
format,
atoms,
depth.clone(),
visual_id,
colormap,
extensions,
)?);
let source = X11Source::new(
connection.clone(),
window.id,
atoms._SMITHAY_X11_BACKEND_CLOSE,
logger.clone(),
);
info!(logger, "Window created");
let (resize_send, resize_recv) = mpsc::channel();
let backend = X11Backend {
log: logger,
source,
connection,
window,
key_counter: Arc::new(AtomicU32::new(0)),
depth,
visual_id,
screen_number,
resize: resize_send,
};
let surface = X11Surface::new(&backend, format, resize_recv)?;
Ok((backend, surface))
}
/// Returns the default screen number of the X server.
pub fn screen(&self) -> usize {
self.screen_number
}
/// Returns the underlying connection to the X server.
pub fn connection(&self) -> &RustConnection {
&*self.connection
}
/// Returns a handle to the X11 window created by the backend.
pub fn window(&self) -> Window {
self.window.clone().into()
}
}
/// An X11 surface which uses GBM to allocate and present buffers.
#[derive(Debug)]
pub struct X11Surface {
connection: Weak<RustConnection>,
window: Window,
resize: Receiver<Size<u16, Logical>>,
device: gbm::Device<DrmNode>,
format: DrmFourcc,
width: u16,
height: u16,
current: Dmabuf,
next: Dmabuf,
}
impl X11Surface {
fn new(
backend: &X11Backend,
format: DrmFourcc,
resize: Receiver<Size<u16, Logical>>,
) -> Result<X11Surface, X11Error> {
let connection = &backend.connection;
let window = backend.window();
// Determine which drm-device the Display is using.
let screen = &connection.setup().roots[backend.screen()];
// provider being NONE tells the X server to use the RandR provider.
let dri3 = match connection.dri3_open(screen.root, x11rb::NONE)?.reply() {
Ok(reply) => reply,
Err(err) => {
return Err(if let ReplyError::X11Error(ref protocol_error) = err {
match protocol_error.error_kind {
// Implementation is risen when the renderer is not capable of X server is not capable
// of rendering at all.
ErrorKind::Implementation => X11Error::CannotDirectRender,
// Match may occur when the node cannot be authenticated for the client.
ErrorKind::Match => X11Error::CannotDirectRender,
_ => err.into(),
}
} else {
err.into()
});
}
};
// Take ownership of the container's inner value so we do not need to duplicate the fd.
// This is fine because the X server will always open a new file descriptor.
let drm_device_fd = dri3.device_fd.into_raw_fd();
let fd_flags =
fcntl::fcntl(drm_device_fd.as_raw_fd(), fcntl::F_GETFD).map_err(AllocateBuffersError::from)?;
// Enable the close-on-exec flag.
fcntl::fcntl(
drm_device_fd,
fcntl::F_SETFD(fcntl::FdFlag::from_bits_truncate(fd_flags) | fcntl::FdFlag::FD_CLOEXEC),
)
.map_err(AllocateBuffersError::from)?;
// Kernel documentation explains why we should prefer the node to be a render node:
// https://kernel.readthedocs.io/en/latest/gpu/drm-uapi.html
//
// > Render nodes solely serve render clients, that is, no modesetting or privileged ioctls
// > can be issued on render nodes. Only non-global rendering commands are allowed. If a
// > driver supports render nodes, it must advertise it via the DRIVER_RENDER DRM driver
// > capability. If not supported, the primary node must be used for render clients together
// > with the legacy drmAuth authentication procedure.
//
// Since giving the X11 backend the ability to do modesetting is a big nono, we try to only
// ever create a gbm device from a render node.
//
// Of course if the DRM device does not support render nodes, no DRIVER_RENDER capability, then
// fall back to the primary node.
let drm_node = DrmNode::from_fd(drm_device_fd).map_err(Into::<AllocateBuffersError>::into)?;
let drm_node = if drm_node.ty() != NodeType::Render {
if drm_node.has_render() {
// Try to get the render node.
match DrmNode::from_node_with_type(drm_node, NodeType::Render) {
Ok(node) => node,
Err(err) => {
slog::warn!(&backend.log, "Could not create render node from existing DRM node, falling back to primary node");
err.node()
}
}
} else {
slog::warn!(
&backend.log,
"DRM Device does not have a render node, falling back to primary node"
);
drm_node
}
} else {
drm_node
};
// Finally create a GBMDevice to manage the buffers.
let device = gbm::Device::new(drm_node).map_err(Into::<AllocateBuffersError>::into)?;
let size = backend.window().size();
let current = device
.create_buffer_object::<()>(size.w as u32, size.h as u32, format, BufferObjectFlags::empty())
.map_err(Into::<AllocateBuffersError>::into)?
.export()
.map_err(Into::<AllocateBuffersError>::into)?;
let next = device
.create_buffer_object::<()>(size.w as u32, size.h as u32, format, BufferObjectFlags::empty())
.map_err(Into::<AllocateBuffersError>::into)?
.export()
.map_err(Into::<AllocateBuffersError>::into)?;
Ok(X11Surface {
connection: Arc::downgrade(connection),
window,
device,
format,
width: size.w,
height: size.h,
current,
next,
resize,
})
}
/// Returns a handle to the GBM device used to allocate buffers.
pub fn device(&self) -> &gbm::Device<DrmNode> {
&self.device
}
/// Returns the format of the buffers the surface accepts.
pub fn format(&self) -> DrmFourcc {
self.format
}
/// Returns an RAII scoped object which provides the next buffer.
///
/// When the object is dropped, the contents of the buffer are swapped and then presented.
pub fn present(&mut self) -> Result<Present<'_>, AllocateBuffersError> {
if let Some(new_size) = self.resize.try_iter().last() {
self.resize(new_size)?;
}
Ok(Present { surface: self })
}
fn resize(&mut self, size: Size<u16, Logical>) -> Result<(), AllocateBuffersError> {
let current = self
.device
.create_buffer_object::<()>(
size.w as u32,
size.h as u32,
self.format,
BufferObjectFlags::empty(),
)?
.export()?;
let next = self
.device
.create_buffer_object::<()>(
size.w as u32,
size.h as u32,
self.format,
BufferObjectFlags::empty(),
)?
.export()?;
self.width = size.w;
self.height = size.h;
self.current = current;
self.next = next;
Ok(())
}
}
/// An RAII scope containing the next buffer that will be presented to the window. Presentation
/// occurs when the `Present` is dropped.
///
/// The provided buffer may be bound to a [Renderer](crate::backend::renderer::Renderer) to draw to
/// the window.
///
/// ```rust,ignore
/// // Instantiate a new present object to start the process of presenting.
/// let present = surface.present()?;
///
/// // Bind the buffer to the renderer in order to render.
/// renderer.bind(present.buffer())?;
///
/// // Rendering here!
///
/// // Make sure to unbind the buffer when done.
/// renderer.unbind()?;
///
/// // When the `present` is dropped, what was rendered will be presented to the window.
/// ```
#[derive(Debug)]
pub struct Present<'a> {
surface: &'a mut X11Surface,
}
impl Present<'_> {
/// Returns the next buffer that will be presented to the Window.
///
/// You may bind this buffer to a renderer to render.
pub fn buffer(&self) -> Dmabuf {
self.surface.next.clone()
}
}
impl Drop for Present<'_> {
fn drop(&mut self) {
let surface = &mut self.surface;
if let Some(connection) = surface.connection.upgrade() {
// Swap the buffers
mem::swap(&mut surface.next, &mut surface.current);
if let Ok(pixmap) = PixmapWrapper::with_dmabuf(&*connection, &surface.window, &surface.current) {
// Now present the current buffer
let _ = pixmap.present(&*connection, &surface.window);
}
// Flush the connection after presenting to the window to ensure we don't run out of buffer space in the X11 connection.
let _ = connection.flush();
}
}
}
/// An X11 window.
#[derive(Debug)]
pub struct Window(Weak<WindowInner>);
impl Window {
/// Sets the title of the window.
pub fn set_title(&self, title: &str) {
if let Some(inner) = self.0.upgrade() {
inner.set_title(title);
}
}
/// Maps the window, making it visible.
pub fn map(&self) {
if let Some(inner) = self.0.upgrade() {
inner.map();
}
}
/// Unmaps the window, making it invisible.
pub fn unmap(&self) {
if let Some(inner) = self.0.upgrade() {
inner.unmap();
}
}
/// Returns the size of this window.
///
/// If the window has been destroyed, the size is `0 x 0`.
pub fn size(&self) -> Size<u16, Logical> {
self.0
.upgrade()
.map(|inner| inner.size())
.unwrap_or_else(|| (0, 0).into())
}
/// Changes the visibility of the cursor within the confines of the window.
///
/// If `false`, this will hide the cursor. If `true`, this will show the cursor.
pub fn set_cursor_visible(&self, visible: bool) {
if let Some(inner) = self.0.upgrade() {
inner.set_cursor_visible(visible);
}
}
/// Returns the XID of the window.
pub fn id(&self) -> u32 {
self.0.upgrade().map(|inner| inner.id).unwrap_or(0)
}
/// Returns the depth id of this window.
pub fn depth(&self) -> u8 {
self.0.upgrade().map(|inner| inner.depth.depth).unwrap_or(0)
}
/// Returns the format expected by the window.
pub fn format(&self) -> Option<DrmFourcc> {
self.0.upgrade().map(|inner| inner.format)
}
}
impl PartialEq for Window {
fn eq(&self, other: &Self) -> bool {
match (self.0.upgrade(), other.0.upgrade()) {
(Some(self_), Some(other)) => self_ == other,
_ => false,
}
}
}
impl EventSource for X11Backend {
type Event = X11Event;
/// The window the incoming events are applicable to.
type Metadata = Window;
type Ret = ();
fn process_events<F>(
&mut self,
readiness: Readiness,
token: Token,
mut callback: F,
) -> io::Result<PostAction>
where
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
{
use self::X11Event::Input;
let connection = self.connection.clone();
let window = self.window.clone();
let key_counter = self.key_counter.clone();
let log = self.log.clone();
let mut event_window = window.clone().into();
let resize = &self.resize;
self.source.process_events(readiness, token, |event, _| {
match event {
x11::Event::ButtonPress(button_press) => {
if button_press.event == window.id {
// X11 decided to associate scroll wheel with a button, 4, 5, 6 and 7 for
// up, down, right and left. For scrolling, a press event is emitted and a
// release is them immediately followed for scrolling. This means we can
// ignore release for scrolling.
// Ideally we would use `ButtonIndex` from XCB, however it does not cover 6 and 7
// for horizontal scroll and does not work nicely in match statements, so we
// use magic constants here:
//
// 1 => MouseButton::Left
// 2 => MouseButton::Middle
// 3 => MouseButton::Right
// 4 => Axis::Vertical +1.0
// 5 => Axis::Vertical -1.0
// 6 => Axis::Horizontal -1.0
// 7 => Axis::Horizontal +1.0
// Others => ??
match button_press.detail {
1..=3 => {
// Clicking a button.
callback(
Input(InputEvent::PointerButton {
event: X11MouseInputEvent {
time: button_press.time,
button: match button_press.detail {
1 => MouseButton::Left,
// Confusion: XCB docs for ButtonIndex and what plasma does don't match?
2 => MouseButton::Middle,
3 => MouseButton::Right,
_ => unreachable!(),
},
state: ButtonState::Pressed,
},
}),
&mut event_window,
)
}
4..=7 => {
// Scrolling
callback(
Input(InputEvent::PointerAxis {
event: X11MouseWheelEvent {
time: button_press.time,
axis: match button_press.detail {
// Up | Down
4 | 5 => Axis::Vertical,
// Right | Left
6 | 7 => Axis::Horizontal,
_ => unreachable!(),
},
amount: match button_press.detail {
// Up | Right
4 | 7 => 1.0,
// Down | Left
5 | 6 => -1.0,
_ => unreachable!(),
},
},
}),
&mut event_window,
)
}
// Unknown mouse button
_ => callback(
Input(InputEvent::PointerButton {
event: X11MouseInputEvent {
time: button_press.time,
button: MouseButton::Other(button_press.detail),
state: ButtonState::Pressed,
},
}),
&mut event_window,
),
}
}
}
x11::Event::ButtonRelease(button_release) => {
if button_release.event == window.id {
match button_release.detail {
1..=3 => {
// Releasing a button.
callback(
Input(InputEvent::PointerButton {
event: X11MouseInputEvent {
time: button_release.time,
button: match button_release.detail {
1 => MouseButton::Left,
2 => MouseButton::Middle,
3 => MouseButton::Right,
_ => unreachable!(),
},
state: ButtonState::Released,
},
}),
&mut event_window,
)
}
// We may ignore the release tick for scrolling, as the X server will
// always emit this immediately after press.
4..=7 => (),
_ => callback(
Input(InputEvent::PointerButton {
event: X11MouseInputEvent {
time: button_release.time,
button: MouseButton::Other(button_release.detail),
state: ButtonState::Released,
},
}),
&mut event_window,
),
}
}
}
x11::Event::KeyPress(key_press) => {
if key_press.event == window.id {
callback(
Input(InputEvent::Keyboard {
event: X11KeyboardInputEvent {
time: key_press.time,
// X11's keycodes are +8 relative to the libinput keycodes
// that are expected, so subtract 8 from each keycode to
// match libinput.
//
// https://github.com/freedesktop/xorg-xf86-input-libinput/blob/master/src/xf86libinput.c#L54
key: key_press.detail as u32 - 8,
count: key_counter.fetch_add(1, Ordering::SeqCst) + 1,
state: KeyState::Pressed,
},
}),
&mut event_window,
)
}
}
x11::Event::KeyRelease(key_release) => {
if key_release.event == window.id {
// atomic u32 has no checked_sub, so load and store to do the same.
let mut key_counter_val = key_counter.load(Ordering::SeqCst);
key_counter_val = key_counter_val.saturating_sub(1);
key_counter.store(key_counter_val, Ordering::SeqCst);
callback(
Input(InputEvent::Keyboard {
event: X11KeyboardInputEvent {
time: key_release.time,
// X11's keycodes are +8 relative to the libinput keycodes
// that are expected, so subtract 8 from each keycode to
// match libinput.
//
// https://github.com/freedesktop/xorg-xf86-input-libinput/blob/master/src/xf86libinput.c#L54
key: key_release.detail as u32 - 8,
count: key_counter_val,
state: KeyState::Released,
},
}),
&mut event_window,
);
}
}
x11::Event::MotionNotify(motion_notify) => {
if motion_notify.event == window.id {
// Use event_x/y since those are relative the the window receiving events.
let x = motion_notify.event_x as f64;
let y = motion_notify.event_y as f64;
callback(
Input(InputEvent::PointerMotionAbsolute {
event: X11MouseMovedEvent {
time: motion_notify.time,
x,
y,
size: window.size(),
},
}),
&mut event_window,
)
}
}
x11::Event::ConfigureNotify(configure_notify) => {
if configure_notify.window == window.id {
let previous_size = { *window.size.lock().unwrap() };
// Did the size of the window change?
let configure_notify_size: Size<u16, Logical> =
(configure_notify.width, configure_notify.height).into();
if configure_notify_size != previous_size {
// Intentionally drop the lock on the size mutex incase a user
// requests a resize or does something which causes a resize
// inside the callback.
{
*window.size.lock().unwrap() = configure_notify_size;
}
(callback)(X11Event::Resized(configure_notify_size), &mut event_window);
let _ = resize.send(configure_notify_size);
}
}
}
x11::Event::EnterNotify(enter_notify) => {
if enter_notify.event == window.id {
window.cursor_enter();
}
}
x11::Event::LeaveNotify(leave_notify) => {
if leave_notify.event == window.id {
window.cursor_leave();
}
}
x11::Event::ClientMessage(client_message) => {
if client_message.data.as_data32()[0] == window.atoms.WM_DELETE_WINDOW // Destroy the window?
&& client_message.window == window.id
// Same window
{
(callback)(X11Event::CloseRequested, &mut event_window);
}
}
x11::Event::Expose(expose) => {
if expose.window == window.id && expose.count == 0 {
(callback)(X11Event::Refresh, &mut event_window);
}
}
x11::Event::PresentCompleteNotify(complete_notify) => {
if complete_notify.window == window.id {
window.last_msc.store(complete_notify.msc, Ordering::SeqCst);
(callback)(X11Event::PresentCompleted, &mut event_window);
}
}
x11::Event::PresentIdleNotify(_) => {
// Pixmap is reference counted in the X server, so we do not need to take and drop.
}
x11::Event::Error(e) => {
error!(log, "X11 protocol error: {:?}", e);
}
_ => (),
}
// Flush the connection so changes to the window state during callbacks can be emitted.
let _ = connection.flush();
})
}
fn register(&mut self, poll: &mut Poll, token_factory: &mut TokenFactory) -> io::Result<()> {
self.source.register(poll, token_factory)
}
fn reregister(&mut self, poll: &mut Poll, token_factory: &mut TokenFactory) -> io::Result<()> {
self.source.reregister(poll, token_factory)
}
fn unregister(&mut self, poll: &mut Poll) -> io::Result<()> {
self.source.unregister(poll)
}
}

View File

@ -0,0 +1,296 @@
/**
A note for future contributors and maintainers:
When editing this file, grab the nearest copy of the ICCCM. Following the ICCCM is paramount to
X11 clients behaving properly and preventing scenarios such as windows not being resized in tiling
window managers.
Pay particular attention to "Section 4: Client to Window Manager Communication"
A link to the ICCCM Section 4: https://tronche.com/gui/x/icccm/sec-4.html
*/
use crate::utils::{Logical, Size};
use super::{extension::Extensions, Atoms, Window, X11Error};
use drm_fourcc::DrmFourcc;
use std::sync::{
atomic::{AtomicU32, AtomicU64},
Arc, Mutex, Weak,
};
use x11rb::{
connection::Connection,
protocol::{
present::{self, ConnectionExt as _},
xfixes::ConnectionExt as _,
xproto::{
self as x11, AtomEnum, ConnectionExt, CreateWindowAux, Depth, EventMask, PropMode, Screen,
UnmapNotifyEvent, WindowClass,
},
},
rust_connection::RustConnection,
wrapper::ConnectionExt as _,
};
impl From<Arc<WindowInner>> for Window {
fn from(inner: Arc<WindowInner>) -> Self {
Window(Arc::downgrade(&inner))
}
}
#[derive(Debug)]
pub struct CursorState {
pub inside_window: bool,
pub visible: bool,
}
impl Default for CursorState {
fn default() -> Self {
CursorState {
inside_window: false,
visible: true,
}
}
}
#[derive(Debug)]
pub(crate) struct WindowInner {
pub connection: Weak<RustConnection>,
pub id: x11::Window,
root: x11::Window,
present_event_id: u32,
pub atoms: Atoms,
pub cursor_state: Arc<Mutex<CursorState>>,
pub size: Mutex<Size<u16, Logical>>,
pub next_serial: AtomicU32,
pub last_msc: Arc<AtomicU64>,
pub format: DrmFourcc,
pub depth: Depth,
pub extensions: Extensions,
}
impl WindowInner {
#[allow(clippy::too_many_arguments)]
pub fn new(
connection: Weak<RustConnection>,
screen: &Screen,
size: Size<u16, Logical>,
title: &str,
format: DrmFourcc,
atoms: Atoms,
depth: Depth,
visual_id: u32,
colormap: u32,
extensions: Extensions,
) -> Result<WindowInner, X11Error> {
let weak = connection;
let connection = weak.upgrade().unwrap();
// Generate the xid for the window
let window = connection.generate_id()?;
// The event mask never include `EventMask::RESIZE_REDIRECT`.
//
// The reason is twofold:
// - We are not a window manager
// - Makes our window impossible to resize.
//
// On the resizing aspect, KWin and some other WMs would allow resizing, but those
// compositors rely on putting this window in another window for drawing decorations,
// so visibly in KWin it would look like using the RESIZE_REDIRECT event mask would work,
// but a tiling window manager would be sad and the tiling window manager devs mad because
// this window would refuse to listen to the tiling WM.
//
// For resizing we use ConfigureNotify events from the STRUCTURE_NOTIFY event mask.
let window_aux = CreateWindowAux::new()
.event_mask(
EventMask::EXPOSURE // Be told when the window is exposed
| EventMask::STRUCTURE_NOTIFY
| EventMask::KEY_PRESS // Key press and release
| EventMask::KEY_RELEASE
| EventMask::BUTTON_PRESS // Mouse button press and release
| EventMask::BUTTON_RELEASE
| EventMask::POINTER_MOTION // Mouse movement
| EventMask::ENTER_WINDOW // Track whether the cursor enters of leaves the window.
| EventMask::LEAVE_WINDOW
| EventMask::EXPOSURE
| EventMask::NO_EVENT,
)
// Border pixel and color map need to be set if our depth may differ from the root depth.
.border_pixel(screen.black_pixel)
.colormap(colormap);
let _ = connection.create_window(
depth.depth,
window,
screen.root,
0,
0,
size.w,
size.h,
0,
WindowClass::INPUT_OUTPUT,
visual_id,
&window_aux,
)?;
// We only ever need one event id since we will only ever have one event context.
let present_event_id = connection.generate_id()?;
connection.present_select_input(
present_event_id,
window,
present::EventMask::COMPLETE_NOTIFY | present::EventMask::IDLE_NOTIFY,
)?;
// Send requests to change window properties while we wait for the window creation request to complete.
let window = WindowInner {
connection: weak,
id: window,
root: screen.root,
present_event_id,
atoms,
cursor_state: Arc::new(Mutex::new(CursorState::default())),
size: Mutex::new(size),
next_serial: AtomicU32::new(0),
last_msc: Arc::new(AtomicU64::new(0)),
format,
depth,
extensions,
};
// Enable WM_DELETE_WINDOW so our client is not disconnected upon our toplevel window being destroyed.
connection.change_property32(
PropMode::REPLACE,
window.id,
atoms.WM_PROTOCOLS,
AtomEnum::ATOM,
&[atoms.WM_DELETE_WINDOW],
)?;
// WM class cannot be safely changed later.
let _ = connection.change_property8(
PropMode::REPLACE,
window.id,
AtomEnum::WM_CLASS,
AtomEnum::STRING,
b"Smithay\0Wayland_Compositor\0",
)?;
window.set_title(title);
window.map();
// Flush requests to server so window is displayed.
connection.flush()?;
Ok(window)
}
pub fn map(&self) {
if let Some(connection) = self.connection.upgrade() {
let _ = connection.map_window(self.id);
}
}
pub fn unmap(&self) {
if let Some(connection) = self.connection.upgrade() {
// ICCCM - Changing Window State
//
// Normal -> Withdrawn - The client should unmap the window and follow it with a synthetic
// UnmapNotify event as described later in this section.
let _ = connection.unmap_window(self.id);
// Send a synthetic UnmapNotify event to make the ICCCM happy
let _ = connection.send_event(
false,
self.id,
EventMask::STRUCTURE_NOTIFY | EventMask::SUBSTRUCTURE_NOTIFY,
UnmapNotifyEvent {
response_type: x11rb::protocol::xproto::UNMAP_NOTIFY_EVENT,
sequence: 0, // Ignored by X server
event: self.root,
window: self.id,
from_configure: false,
},
);
}
}
pub fn size(&self) -> Size<u16, Logical> {
*self.size.lock().unwrap()
}
pub fn set_title(&self, title: &str) {
if let Some(connection) = self.connection.upgrade() {
// _NET_WM_NAME should be preferred by window managers, but set both properties.
let _ = connection.change_property8(
PropMode::REPLACE,
self.id,
AtomEnum::WM_NAME,
AtomEnum::STRING,
title.as_bytes(),
);
let _ = connection.change_property8(
PropMode::REPLACE,
self.id,
self.atoms._NET_WM_NAME,
self.atoms.UTF8_STRING,
title.as_bytes(),
);
}
}
pub fn set_cursor_visible(&self, visible: bool) {
if let Some(connection) = self.connection.upgrade() {
let mut state = self.cursor_state.lock().unwrap();
let changed = state.visible != visible;
if changed && state.inside_window {
state.visible = visible;
self.update_cursor(&*connection, state.visible);
}
}
}
pub fn cursor_enter(&self) {
if let Some(connection) = self.connection.upgrade() {
let mut state = self.cursor_state.lock().unwrap();
state.inside_window = true;
self.update_cursor(&*connection, state.visible);
}
}
pub fn cursor_leave(&self) {
if let Some(connection) = self.connection.upgrade() {
let mut state = self.cursor_state.lock().unwrap();
state.inside_window = false;
self.update_cursor(&*connection, true);
}
}
fn update_cursor<C: ConnectionExt>(&self, connection: &C, visible: bool) {
let _ = match visible {
// This generates a Match error if we did not call Show/HideCursor before. Ignore that error.
true => connection
.xfixes_show_cursor(self.id)
.map(|cookie| cookie.ignore_error()),
false => connection
.xfixes_hide_cursor(self.id)
.map(|cookie| cookie.ignore_error()),
};
}
}
impl PartialEq for WindowInner {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Drop for WindowInner {
fn drop(&mut self) {
if let Some(connection) = self.connection.upgrade() {
let _ = connection.destroy_window(self.id);
}
}
}

View File

@ -21,3 +21,5 @@ pub use wayland_protocols;
pub use wayland_server;
#[cfg(feature = "backend_winit")]
pub use winit;
#[cfg(feature = "x11rb_event_source")]
pub use x11rb;

View File

@ -3,6 +3,9 @@
mod geometry;
pub mod signaling;
#[cfg(feature = "x11rb_event_source")]
pub mod x11rb;
pub use self::geometry::{Buffer, Logical, Physical, Point, Raw, Rectangle, Size};
/// This resource is not managed by Smithay

View File

@ -1,3 +1,8 @@
//! Helper utilities for using x11rb as an event source in calloop.
//!
//! The primary use for this module is XWayland integration but is also widely useful for an X11
//! backend in a compositor.
use std::{
io::Result as IOResult,
sync::Arc,
@ -13,7 +18,7 @@ use x11rb::{
rust_connection::RustConnection,
};
use smithay::reexports::calloop::{
use calloop::{
channel::{sync_channel, Channel, Event as ChannelEvent, SyncSender},
EventSource, Poll, PostAction, Readiness, Token, TokenFactory,
};
@ -28,9 +33,10 @@ use smithay::reexports::calloop::{
/// iteration. Calloop only allows "when an FD becomes readable".
///
/// [1]: https://docs.rs/x11rb/0.8.1/x11rb/event_loop_integration/index.html#threads-and-races
#[derive(Debug)]
pub struct X11Source {
connection: Arc<RustConnection>,
channel: Channel<Event>,
channel: Option<Channel<Event>>,
event_thread: Option<JoinHandle<()>>,
close_window: Window,
close_type: Atom,
@ -56,9 +62,10 @@ impl X11Source {
let event_thread = Some(spawn(move || {
run_event_thread(conn, sender, log2);
}));
Self {
connection,
channel,
channel: Some(channel),
event_thread,
close_window,
close_type,
@ -70,9 +77,7 @@ impl X11Source {
impl Drop for X11Source {
fn drop(&mut self) {
// Signal the worker thread to exit by dropping the read end of the channel.
// There is no easy and nice way to do this, so do it the ugly way: Replace it.
let (_, channel) = sync_channel(1);
self.channel = channel;
self.channel.take();
// Send an event to wake up the worker so that it actually exits
let event = ClientMessageEvent {
@ -83,6 +88,7 @@ impl Drop for X11Source {
type_: self.close_type,
data: [0; 20].into(),
};
let _ = self
.connection
.send_event(false, self.close_window, EventMask::NO_EVENT, event);
@ -108,23 +114,39 @@ impl EventSource for X11Source {
C: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
{
let log = self.log.clone();
self.channel
.process_events(readiness, token, move |event, meta| match event {
if let Some(channel) = &mut self.channel {
channel.process_events(readiness, token, move |event, meta| match event {
ChannelEvent::Closed => slog::warn!(log, "Event thread exited"),
ChannelEvent::Msg(event) => callback(event, meta),
})
} else {
Ok(PostAction::Remove)
}
}
fn register(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> IOResult<()> {
self.channel.register(poll, factory)
if let Some(channel) = &mut self.channel {
channel.register(poll, factory)?;
}
Ok(())
}
fn reregister(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> IOResult<()> {
self.channel.reregister(poll, factory)
if let Some(channel) = &mut self.channel {
channel.reregister(poll, factory)?;
}
Ok(())
}
fn unregister(&mut self, poll: &mut Poll) -> IOResult<()> {
self.channel.unregister(poll)
if let Some(channel) = &mut self.channel {
channel.unregister(poll)?;
}
Ok(())
}
}