From 74a4ed1058b9690d6c53440e9bf47d16ba044b2a Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 15 Dec 2021 23:23:49 +0100 Subject: [PATCH] x11: add nested x11 backend --- Cargo.lock | 71 ++++++++++++ Cargo.toml | 2 +- src/backend/mod.rs | 18 +++ src/backend/x11.rs | 284 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 73 ++++++++---- src/state.rs | 31 ++++- src/utils.rs | 20 ++++ 7 files changed, 470 insertions(+), 29 deletions(-) create mode 100644 src/backend/mod.rs create mode 100644 src/backend/x11.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 51c2dbc7..31a64f41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,6 +151,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + [[package]] name = "downcast-rs" version = "1.2.0" @@ -272,6 +281,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "khronos_api" version = "3.1.0" @@ -300,6 +318,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.14" @@ -356,6 +383,31 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "pkg-config" version = "0.3.24" @@ -466,6 +518,18 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "slog" version = "2.7.0" @@ -554,6 +618,7 @@ dependencies = [ "wayland-commons", "wayland-protocols", "wayland-server", + "wayland-sys", "x11rb", "xkbcommon", ] @@ -694,8 +759,11 @@ checksum = "a0307b0fbbae986e86934b8fbcc56299bbaf8de0b5ad0f00405fb583503c085a" dependencies = [ "bitflags", "downcast-rs", + "lazy_static", "libc", "nix", + "parking_lot", + "scoped-tls", "wayland-commons", "wayland-scanner", "wayland-sys", @@ -707,6 +775,9 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba9e06acb775b3007f8d3094438306979e572d1d3b844d7a71557a84b055d959" dependencies = [ + "dlib", + "libc", + "memoffset", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 93924e5f..6c060b06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,4 @@ version = "0.3" git = "https://github.com/Smithay/smithay.git" rev = "87ab37a1" default-features = false -features = ["backend_x11", "backend_egl", "desktop", "renderer_gl", "wayland_frontend"] \ No newline at end of file +features = ["backend_x11", "backend_egl", "desktop", "use_system_lib", "renderer_gl", "wayland_frontend"] \ No newline at end of file diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 00000000..eea336ef --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use anyhow::Result; +use smithay::reexports::calloop::EventLoop; + +pub mod x11; +// TODO +// pub mod wayland; // tbd in smithay +// pub mod udev; + +pub fn init_backend_auto(event_loop: &mut EventLoop, state: &mut State) -> Result<()> { + if std::env::var_os("DISPLAY").is_some() { + x11::init_backend(event_loop, state) + } else { + unimplemented!("Currently this runs only nested") + } +} diff --git a/src/backend/x11.rs b/src/backend/x11.rs new file mode 100644 index 00000000..f62ae707 --- /dev/null +++ b/src/backend/x11.rs @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + state::{BackendData, State}, + utils::GlobalDrop, +}; +use anyhow::{Context, Result}; +use smithay::{ + backend::{ + allocator::dmabuf::Dmabuf, + drm::DrmNode, + egl::{EGLContext, EGLDisplay}, + renderer::{gles2::Gles2Renderer, Bind, ImportDma, ImportEgl, Unbind}, + x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Surface}, + }, + desktop::Space, + reexports::{ + calloop::{ping, EventLoop, LoopHandle}, + gbm::Device as GbmDevice, + wayland_server::{ + protocol::wl_output::{Subpixel, WlOutput}, + Display, + }, + }, + wayland::{ + dmabuf::init_dmabuf_global, + output::{Mode, Output, PhysicalProperties}, + }, +}; +use std::{ + cell::RefCell, + rc::Rc, + sync::{Arc, Mutex}, +}; + +pub struct X11State { + handle: X11Handle, + allocator: Arc>>, + _egl: EGLDisplay, + renderer: Rc>, + surfaces: Vec, +} + +impl X11State { + pub fn add_window( + &mut self, + display: &mut Display, + handle: LoopHandle<'_, State>, + ) -> Result { + let window = WindowBuilder::new() + .title("COSMIC") + .build(&self.handle) + .with_context(|| "Failed to create window")?; + let fourcc = window.format().unwrap(); + let surface = self + .handle + .create_surface( + &window, + self.allocator.clone(), + Bind::::supported_formats(&*self.renderer.borrow()) + .unwrap() + .iter() + .filter(|format| format.code == fourcc) + .map(|format| format.modifier), + ) + .with_context(|| "Failed to create surface")?; + + let name = format!("X11-{}", self.surfaces.len()); + let size = window.size(); + let props = PhysicalProperties { + size: (0, 0).into(), + subpixel: Subpixel::Unknown, + make: "COSMIC".to_string(), + model: name.clone(), + }; + let mode = Mode { + size: (size.w as i32, size.h as i32).into(), + refresh: 60_000, + }; + let (output, global) = Output::new(display, name, props, None); + let _global = global.into(); + output.change_current_state(Some(mode), None, None, Some((0, 0).into())); + output.set_preferred(mode); + + let output_ref = output.clone(); + let (ping, source) = + ping::make_ping().with_context(|| "Failed to create output event loop source")?; + let _token = handle + .insert_source(source, move |_, _, state| { + let x11_state = state.backend.x11(); + if let Some(surface) = x11_state + .surfaces + .iter_mut() + .find(|s| s.output == output_ref) + { + if let Err(err) = surface + .render_from_space(&mut *x11_state.renderer.borrow_mut(), &mut state.spaces) + { + slog_scope::error!("Error rendering: {}", err); + } + } + }) + .with_context(|| "Failed to add output to event loop")?; + + self.surfaces.push(Surface { + window, + surface, + output: output.clone(), + render: ping.clone(), + _global, + }); + + // schedule first render + ping.ping(); + Ok(output) + } +} + +pub struct Surface { + window: Window, + surface: X11Surface, + output: Output, + render: ping::Ping, + _global: GlobalDrop, +} + +impl Surface { + pub fn render_from_space( + &mut self, + renderer: &mut Gles2Renderer, + space: &mut Space, + ) -> Result<()> { + let (buffer, age) = self + .surface + .buffer() + .with_context(|| "Failed to allocate buffer")?; + renderer + .bind(buffer) + .with_context(|| "Failed to bind buffer")?; + match space.render_output( + renderer, + &self.output, + age as usize, + [0.153, 0.161, 0.165, 1.0], + ) { + Ok(true) => { + slog_scope::trace!("Finished rendering"); + self.surface + .submit() + .with_context(|| "Failed to submit buffer for display")?; + } + Ok(false) => { + let _ = renderer.unbind(); + } + Err(err) => { + self.surface.reset_buffers(); + anyhow::bail!("Rendering failed: {}", err); + } + }; + + Ok(()) + } +} + +pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Result<()> { + let backend = X11Backend::new(None).with_context(|| "Failed to initilize X11 backend")?; + let handle = backend.handle(); + + // Obtain the DRM node the X server uses for direct rendering. + let drm_node = handle + .drm_node() + .with_context(|| "Could not get DRM node used by X server")?; + + // Create the gbm device for buffer allocation. + let device = GbmDevice::new(drm_node).with_context(|| "Failed to create GBM device")?; + // Initialize EGL using the GBM device. + let egl = EGLDisplay::new(&device, None).with_context(|| "Failed to create EGL display")?; + // Create the OpenGL context + let context = EGLContext::new(&egl, None).with_context(|| "Failed to create EGL context")?; + // Create a renderer + let renderer = Rc::new(RefCell::new( + unsafe { Gles2Renderer::new(context, None) } + .with_context(|| "Failed to initialize renderer")?, + )); + + init_egl_client_side(&mut *state.display.borrow_mut(), renderer.clone())?; + + state.backend = BackendData::X11(X11State { + handle, + allocator: Arc::new(Mutex::new(device)), + _egl: egl, + renderer, + surfaces: Vec::new(), + }); + + let output = state + .backend + .x11() + .add_window(&mut *state.display.borrow_mut(), event_loop.handle()) + .with_context(|| "Failed to create wl_output")?; + state + .spaces + .map_output(&output, 1.0, (0, 0).into() /* TODO */); + + event_loop + .handle() + .insert_source(backend, |event, window, state| match event { + X11Event::CloseRequested => { + window.unmap(); + // TODO: drain_filter + for output in state + .backend + .x11() + .surfaces + .iter() + .filter(|s| &s.window == window) + .map(|s| &s.output) + { + state.spaces.unmap_output(output); + } + state.backend.x11().surfaces.retain(|s| &s.window != window); + } + X11Event::Resized(size) => { + let size = { (size.w as i32, size.h as i32).into() }; + let mode = Mode { + size, + refresh: 60_000, + }; + let surface = state + .backend + .x11() + .surfaces + .iter_mut() + .find(|s| &s.window == window) + .expect("Unmanaged X11 surface?"); + surface.render.ping(); + + let output = &surface.output; + output.delete_mode(output.current_mode().unwrap()); + output.change_current_state(Some(mode), None, None, None); + output.set_preferred(mode); + } + + X11Event::PresentCompleted | X11Event::Refresh => { + state + .backend + .x11() + .surfaces + .iter_mut() + .find(|s| &s.window == window) + .expect("Unmanaged X11 surface?") + .render + .ping(); + } + + X11Event::Input(event) => { /*TODO*/ } + }) + .expect("Failed to insert X11 Backend into event loop"); + + Ok(()) +} + +fn init_egl_client_side(display: &mut Display, renderer: Rc>) -> Result<()> { + let bind_result = renderer.borrow_mut().bind_wl_display(display); + match bind_result { + Ok(_) => { + slog_scope::info!("EGL hardware-acceleration enabled"); + let dmabuf_formats = renderer + .borrow() + .dmabuf_formats() + .cloned() + .collect::>(); + init_dmabuf_global( + display, + dmabuf_formats, + move |buffer, _| renderer.borrow_mut().import_dmabuf(buffer).is_ok(), + None, + ); + } + Err(err) => slog_scope::warn!("Unable to initialize bind display to EGL: {}", err), + }; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index b6efba84..0c48e56b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,50 @@ use smithay::reexports::{ use anyhow::{Context, Result}; use slog::Drain; +pub mod backend; pub mod state; +pub mod utils; fn main() -> Result<()> { // setup logger + let _guard = init_logger(); + slog_scope::info!("Cosmic starting up!"); + + // init event loop + let mut event_loop = EventLoop::try_new().with_context(|| "Failed to initialize event loop")?; + // init wayland + let display = init_wayland_display(&mut event_loop)?; + // init state + let mut state = state::State::new(display); + // init backend + backend::init_backend_auto(&mut event_loop, &mut state)?; + + // run the event loop + let signal = event_loop.get_signal(); + event_loop.run(None, &mut state, |state| { + // shall we shut down? + if state.spaces.outputs().next().is_none() || state.should_stop { + slog_scope::info!("Shutting down"); + signal.stop(); + signal.wakeup(); + return; + } + + // trigger routines + state + .spaces + .send_frames(false, state.start_time.elapsed().as_millis() as u32); + state.spaces.refresh(); + + // send out events + let display = state.display.clone(); + display.borrow_mut().flush_clients(state); + })?; + + Ok(()) +} + +fn init_logger() -> Result { let decorator = slog_term::TermDecorator::new().stderr().build(); // usually we would not want to use a Mutex here, but this is usefull for a prototype, // to make sure we do not miss any in-flight messages, when we crash. @@ -24,7 +64,7 @@ fn main() -> Result<()> { .fuse(), slog::o!(), ); - let _guard = slog_scope::set_global_logger(logger); + let guard = slog_scope::set_global_logger(logger); slog_stdlog::init().unwrap(); slog_scope::info!("Version: {}", std::env!("CARGO_PKG_VERSION")); @@ -34,14 +74,15 @@ fn main() -> Result<()> { std::option_env!("GIT_HASH").unwrap_or("Unknown") ); } - slog_scope::info!("Cosmic starting up!"); - // init event loop - let mut event_loop = EventLoop::try_new().with_context(|| "Failed to initialize event loop")?; + Ok(guard) +} - // add wayland socket +fn init_wayland_display(event_loop: &mut EventLoop) -> Result { let mut display = Display::new(); let socket_name = display.add_socket_auto()?; + + slog_scope::info!("Listening on {:?}", socket_name); event_loop .handle() .insert_source( @@ -59,25 +100,7 @@ fn main() -> Result<()> { } }, ) - .expect("Failed to init the wayland event source."); - slog_scope::info!("Listening on {:?}", socket_name); + .with_context(|| "Failed to init the wayland event source.")?; - // init state - let mut state = state::State::new(display); - - // run the event loop - let signal = event_loop.get_signal(); - event_loop.run(None, &mut state, |state| { - // shall we shut down? - if state.should_stop { - signal.stop(); - return; - } - - // send out events - let display = state.display.clone(); - display.borrow_mut().flush_clients(state); - })?; - - Ok(()) + Ok(display) } diff --git a/src/state.rs b/src/state.rs index 6292cbdd..27e0d327 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,19 +1,44 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{cell::RefCell, rc::Rc}; - -use smithay::reexports::wayland_server::Display; +use crate::backend::x11::X11State; +use smithay::{desktop::Space, reexports::wayland_server::Display}; +use std::{cell::RefCell, rc::Rc, time::Instant}; pub struct State { pub display: Rc>, + pub spaces: Space, //TODO: Multiple workspaces + + pub start_time: Instant, pub should_stop: bool, + pub backend: BackendData, +} + +pub enum BackendData { + X11(X11State), + // TODO + // Wayland(WaylandState), + // Udev(UdevState), + Unset, +} + +impl BackendData { + pub fn x11(&mut self) -> &mut X11State { + match self { + BackendData::X11(ref mut x11_state) => x11_state, + _ => unreachable!("Called x11 in non x11 backend"), + } + } } impl State { pub fn new(display: Display) -> State { State { display: Rc::new(RefCell::new(display)), + spaces: Space::new(slog_scope::logger() /*TODO*/), + + start_time: Instant::now(), should_stop: false, + backend: BackendData::Unset, } } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..95541a1b --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::reexports::wayland_server::{Global, Interface, Resource}; +use std::convert::{AsRef, From}; + +pub struct GlobalDrop> + From>>(Option>); + +impl> + From>> From> for GlobalDrop { + fn from(g: Global) -> GlobalDrop { + GlobalDrop(Some(g)) + } +} + +impl> + From>> Drop for GlobalDrop { + fn drop(&mut self) { + if let Some(global) = self.0.take() { + global.destroy(); + } + } +}