From 4a9dfcead046bf8ab5c2fdac7286ed2ecdb9a5fe Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 13 Feb 2023 17:44:24 +0100 Subject: [PATCH] deps: Update smithay Integrate new multigpu allocator code for gpu->gpu copies using the vulkan allocator. Also integrates Xwayland clipboard sync. --- Cargo.lock | 7 +- Cargo.toml | 4 +- src/backend/kms/mod.rs | 177 ++++++++++++++++------ src/backend/render/element.rs | 14 +- src/backend/render/mod.rs | 23 +-- src/backend/x11.rs | 123 ++++++++++++--- src/shell/element/mod.rs | 12 +- src/wayland/handlers/data_device.rs | 51 ++++++- src/wayland/handlers/primary_selection.rs | 46 +++++- src/wayland/handlers/screencopy.rs | 10 +- src/xwayland.rs | 98 +++++++++++- 11 files changed, 450 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64a87ea7..de7b6e77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1753,9 +1753,9 @@ dependencies = [ [[package]] name = "input" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4669c07fe301ce2eb323ff23b9480e6a19d8e94520236862caca51b2b5c45e3" +checksum = "eeb3afdf1f8137428002b354eaf87aa629178995683941d94b04c6d145ec8937" dependencies = [ "bitflags", "input-sys", @@ -3292,9 +3292,10 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/pop-os/smithay?rev=3437fe15ca#3437fe15caf1661bc3e93a9a20425be3189a4dc7" +source = "git+https://github.com/smithay//smithay?rev=09e29418e5#09e29418e5d973dd21fc636433cca0b4dc3a17b3" dependencies = [ "appendlist", + "ash", "bitflags", "calloop", "cc", diff --git a/Cargo.toml b/Cargo.toml index dbbe7e0c..298172d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ version = "0.3" git = "https://github.com/Smithay/smithay.git" rev = "2de946cf9e" default-features = false -features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_glow", "renderer_multi", "wayland_frontend", "slog-stdlog", "xwayland"] +features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_vulkan", "backend_x11", "desktop", "use_system_lib", "renderer_glow", "renderer_multi", "wayland_frontend", "slog-stdlog", "xwayland"] [dependencies.smithay-egui] git = "https://github.com/Smithay/smithay-egui.git" @@ -70,4 +70,4 @@ debug = true lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/pop-os/smithay", rev = "3437fe15ca" } +smithay = { git = "https://github.com/smithay//smithay", rev = "09e29418e5" } diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 1cf5b377..d54db9ee 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -15,7 +15,12 @@ use crate::{ use anyhow::{Context, Result}; use smithay::{ backend::{ - allocator::{dmabuf::Dmabuf, gbm::GbmDevice, Format}, + allocator::{ + dmabuf::{AnyError, Dmabuf, DmabufAllocator}, + gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}, + vulkan::{ImageUsageFlags, VulkanAllocator}, + Allocator, Format, + }, drm::{ DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface, NodeType, }, @@ -26,10 +31,11 @@ use smithay::{ damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, - multigpu::{egl::EglGlesBackend, GpuManager}, + multigpu::{gbm::GbmGlesBackend, GpuManager}, }, session::{libseat::LibSeatSession, Event as SessionEvent, Session}, udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent}, + vulkan::{version::Version, Instance, PhysicalDevice}, }, desktop::utils::OutputPresentationFeedback, input::Seat, @@ -53,6 +59,7 @@ use smithay::{ use std::{ cell::RefCell, collections::{HashMap, HashSet}, + ffi::CStr, os::unix::io::FromRawFd, path::PathBuf, time::Duration, @@ -68,7 +75,7 @@ const MIN_RENDER_TIME: Duration = Duration::from_millis(3); pub struct KmsState { devices: HashMap, - pub api: GpuManager>, + pub api: GpuManager>, pub primary: DrmNode, session: LibSeatSession, _tokens: Vec, @@ -77,8 +84,9 @@ pub struct KmsState { pub struct Device { render_node: DrmNode, surfaces: HashMap, - allocator: GbmDevice, drm: Dispatcher<'static, DrmDevice, Data>, + gbm: GbmDevice, + allocator: Box>, formats: HashSet, supports_atomic: bool, event_token: Option, @@ -86,7 +94,8 @@ pub struct Device { } pub struct Surface { - surface: Option, Option>>, + surface: + Option, Option>>, damage_tracker: DamageTrackedRenderer, connector: connector::Handle, output: Output, @@ -137,7 +146,7 @@ pub fn init_backend( }) .map_err(|err| err.error) .context("Failed to initialize libinput event source")?; - let api = GpuManager::new(EglGlesBackend::::default(), None) + let api = GpuManager::new(GbmGlesBackend::::default(), None) .context("Failed to initialize renderers")?; // TODO get this info from system76-power, if available and setup a watcher @@ -359,12 +368,7 @@ impl State { .try_get_render_node() .ok() .and_then(std::convert::identity) - .with_context(|| { - format!( - "Failed to determine path of egl device for {}", - path.display() - ) - })?; + .unwrap_or(drm_node); let egl_context = EGLContext::new(&egl_display, None).with_context(|| { format!( "Failed to create EGLContext for device {:?}:{}", @@ -483,10 +487,15 @@ impl State { } }; + let allocator = Box::new(DmabufAllocator(GbmAllocator::new( + gbm.clone(), + GbmBufferFlags::RENDERING, + ))); let mut device = Device { render_node, surfaces: HashMap::new(), - allocator: gbm, + gbm, + allocator, drm: dispatcher, formats, supports_atomic, @@ -500,7 +509,7 @@ impl State { { let backend = self.backend.kms(); for (crtc, conn) in outputs { - let mut renderer = match backend.api.renderer(&render_node, &render_node) { + let mut renderer = match backend.api.single_renderer(&render_node) { Ok(renderer) => renderer, Err(err) => { slog_scope::warn!("Failed to initialize output: {}", err); @@ -536,6 +545,44 @@ impl State { &self.common.event_loop_handle, ); + if let Ok(instance) = Instance::new(Version::VERSION_1_2, None, None) { + if let Some(physical_device) = + PhysicalDevice::enumerate(&instance) + .ok() + .and_then(|devices| { + devices + .filter(|phd| { + phd.has_device_extension(unsafe { + CStr::from_bytes_with_nul_unchecked( + b"VK_EXT_physical_device_drm\0", + ) + }) + }) + .find(|phd| { + phd.primary_node().unwrap() == Some(render_node) + || phd.render_node().unwrap() == Some(render_node) + }) + }) + { + match VulkanAllocator::new( + &physical_device, + ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::SAMPLED, + ) { + Ok(allocator) => { + self.backend + .kms() + .devices + .get_mut(&drm_node) + .unwrap() + .allocator = Box::new(DmabufAllocator(allocator)); + } + Err(err) => { + slog_scope::warn!("Failed to create vulkan allocator: {}", err); + } + } + } + } + Ok(()) } @@ -562,10 +609,7 @@ impl State { } } for (crtc, conn) in changes.added { - let mut renderer = match backend - .api - .renderer(&device.render_node, &device.render_node) - { + let mut renderer = match backend.api.single_renderer(&device.render_node) { Ok(renderer) => renderer, Err(err) => { slog_scope::warn!("Failed to initialize output: {}", err); @@ -701,7 +745,7 @@ impl Device { crtc: crtc::Handle, conn: connector::Handle, position: (i32, i32), - renderer: &mut GlMultiRenderer<'_>, + renderer: &mut GlMultiRenderer<'_, '_>, ) -> Result { let drm = &mut *self.drm.as_source_mut(); let crtc_info = drm.get_crtc(crtc)?; @@ -828,8 +872,11 @@ fn render_node_for_output( impl Surface { pub fn render_output( &mut self, - dh: &DisplayHandle, - api: &mut GpuManager>, + api: &mut GpuManager>, + render_node: Option<( + &DrmNode, + &mut dyn Allocator, + )>, target_node: &DrmNode, state: &mut Common, screencopy: Option<&[(ScreencopySession, BufferParams)]>, @@ -838,10 +885,16 @@ impl Surface { return Ok(()); } - let render_node = render_node_for_output(dh, &self.output, *target_node, &state.shell); - let mut renderer: GlMultiRenderer = api.renderer(&render_node, &target_node).unwrap(); - let surface = self.surface.as_mut().unwrap(); + let (render_node, mut renderer) = match render_node { + Some((render_node, allocator)) => ( + render_node, + api.renderer(&render_node, &target_node, allocator, surface.format()) + .unwrap(), + ), + None => (target_node, api.single_renderer(&target_node).unwrap()), + }; + let (buffer, age) = surface .next_buffer() .with_context(|| "Failed to allocate buffer")?; @@ -957,7 +1010,10 @@ impl KmsState { let drm_surface = drm.create_surface(*crtc, *mode, &[conn])?; let target = GbmBufferedSurface::new( drm_surface, - device.allocator.clone(), + GbmAllocator::new( + device.gbm.clone(), + GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, + ), device.formats.clone(), None, ) @@ -1051,7 +1107,7 @@ impl KmsState { { return self .api - .renderer::(&device.render_node, &device.render_node)? + .single_renderer(&device.render_node)? .import_dmabuf(&dmabuf, None) .map(|_| ()) .map_err(Into::into); @@ -1100,28 +1156,59 @@ impl KmsState { }, move |_time, _, data| { let backend = data.state.backend.kms(); - if let Some(device) = backend.devices.get_mut(&device) { - if let Some(surface) = device.surfaces.get_mut(&crtc) { - match surface.render_output( - &data.display.handle(), + let (mut device, mut other) = backend + .devices + .iter_mut() + .partition::, _>(|(key, _val)| *key == &device); + let target_device = &mut device[0].1; + + if let Some(surface) = target_device.surfaces.get_mut(&crtc) { + let target_node = target_device.render_node; + let render_node = render_node_for_output( + &data.display.handle(), + &surface.output, + target_node, + &data.state.common.shell, + ); + let state = &mut data.state.common; + + let result = if render_node != target_node { + let render_device = &mut other + .iter_mut() + .find(|(_, val)| val.render_node == render_node) + .unwrap() + .1; + surface.render_output( &mut backend.api, - &device.render_node, - &mut data.state.common, + Some(( + &render_device.render_node, + render_device.allocator.as_mut(), + )), + &target_node, + state, screencopy_sessions.as_deref(), - ) { - Ok(_) => return TimeoutAction::Drop, - Err(err) => { - if backend.session.is_active() { - slog_scope::error!("Error rendering: {}", err); - return TimeoutAction::ToDuration( - Duration::from_secs_f64( - (1000.0 / surface.refresh_rate as f64) - 0.003, - ), - ); - } + ) + } else { + surface.render_output( + &mut backend.api, + None, + &target_node, + state, + screencopy_sessions.as_deref(), + ) + }; + + match result { + Ok(_) => return TimeoutAction::Drop, + Err(err) => { + if backend.session.is_active() { + slog_scope::error!("Error rendering: {}", err); + return TimeoutAction::ToDuration(Duration::from_secs_f64( + (1000.0 / surface.refresh_rate as f64) - 0.003, + )); } - }; - } + } + }; } if let Some(sessions) = screencopy_sessions.as_mut() { diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index c5a10e65..fa230f39 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -154,15 +154,15 @@ impl RenderElement for CosmicElement { } } -impl<'a> RenderElement> for CosmicElement> { +impl<'a, 'b> RenderElement> for CosmicElement> { fn draw<'frame>( &self, - frame: &mut GlMultiFrame<'a, 'frame>, + frame: &mut GlMultiFrame<'a, 'b, 'frame>, src: Rectangle, dst: Rectangle, damage: &[Rectangle], log: &slog::Logger, - ) -> Result<(), as Renderer>::Error> { + ) -> Result<(), as Renderer>::Error> { match self { CosmicElement::Workspace(elem) => elem.draw(frame, src, dst, damage, log), CosmicElement::Cursor(elem) => elem.draw(frame, src, dst, damage, log), @@ -181,8 +181,8 @@ impl<'a> RenderElement> for CosmicElement, - ) -> Option>> { + renderer: &GlMultiRenderer<'a, 'b>, + ) -> Option>> { match self { CosmicElement::Workspace(elem) => elem.underlying_storage(renderer), CosmicElement::Cursor(elem) => elem.underlying_storage(renderer), @@ -263,7 +263,7 @@ impl AsGlowRenderer for GlowRenderer { } } -impl<'a> AsGlowRenderer for GlMultiRenderer<'a> { +impl<'a, 'b> AsGlowRenderer for GlMultiRenderer<'a, 'b> { fn glow_renderer(&self) -> &GlowRenderer { self.as_ref() } @@ -289,7 +289,7 @@ impl<'frame> AsGlowFrame<'frame> for GlowFrame<'frame> { } } -impl<'renderer, 'frame> AsGlowFrame<'frame> for GlMultiFrame<'renderer, 'frame> { +impl<'renderer, 'alloc, 'frame> AsGlowFrame<'frame> for GlMultiFrame<'renderer, 'alloc, 'frame> { fn glow_frame(&self) -> &GlowFrame<'frame> { self.as_ref() } diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 45483ad8..7006ce5a 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -27,9 +27,9 @@ use smithay::{ DamageTrackedRenderer, DamageTrackedRendererError as RenderError, OutputNoMode, }, element::{RenderElement, RenderElementStates}, - gles2::{Gles2Error, Gles2Renderbuffer}, + gles2::Gles2Error, glow::GlowRenderer, - multigpu::{egl::EglGlesBackend, MultiFrame, MultiRenderer}, + multigpu::{gbm::GbmGlesBackend, MultiFrame, MultiRenderer}, Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, TextureFilter, }, }, @@ -43,21 +43,10 @@ use self::cursor::CursorRenderElement; pub mod element; use self::element::{AsGlowRenderer, CosmicElement}; -pub type GlMultiRenderer<'a> = MultiRenderer< - 'a, - 'a, - EglGlesBackend, - EglGlesBackend, - Gles2Renderbuffer, ->; -pub type GlMultiFrame<'a, 'frame> = MultiFrame< - 'a, - 'a, - 'frame, - EglGlesBackend, - EglGlesBackend, - Gles2Renderbuffer, ->; +pub type GlMultiRenderer<'a, 'b> = + MultiRenderer<'a, 'a, 'b, GbmGlesBackend, GbmGlesBackend>; +pub type GlMultiFrame<'a, 'b, 'frame> = + MultiFrame<'a, 'a, 'b, 'frame, GbmGlesBackend, GbmGlesBackend>; pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 3303ef82..a917ca44 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -11,14 +11,19 @@ use crate::{ use anyhow::{Context, Result}; use smithay::{ backend::{ - allocator::dmabuf::Dmabuf, - drm::DrmDeviceFd, - egl::{EGLContext, EGLDisplay}, + allocator::{ + dmabuf::{Dmabuf, DmabufAllocator}, + gbm::{GbmAllocator, GbmBufferFlags}, + vulkan::{ImageUsageFlags, VulkanAllocator}, + }, + drm::{DrmDeviceFd, DrmNode}, + egl::{EGLContext, EGLDevice, EGLDisplay}, input::{Event, InputEvent}, renderer::{ damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, Bind, ImportDma, ImportEgl, }, + vulkan::{version::Version, Instance, PhysicalDevice}, x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Input, X11Surface}, }, desktop::layer_map_for_output, @@ -31,13 +36,18 @@ use smithay::{ }, utils::{DeviceFd, Transform}, }; -use std::cell::RefCell; +use std::{cell::RefCell, os::unix::io::OwnedFd}; #[cfg(feature = "debug")] use crate::state::Fps; +enum Allocator { + Gbm(GbmAllocator), + Vulkan(PhysicalDevice), +} + pub struct X11State { - allocator: GbmDevice, + allocator: Allocator, _egl: EGLDisplay, pub renderer: GlowRenderer, surfaces: Vec, @@ -51,18 +61,28 @@ impl X11State { .build(&self.handle) .with_context(|| "Failed to create window")?; let fourcc = window.format(); - let surface = self - .handle - .create_surface( - &window, - self.allocator.clone(), - Bind::::supported_formats(&self.renderer) - .unwrap() - .iter() - .filter(|format| format.code == fourcc) - .map(|format| format.modifier), - ) - .with_context(|| "Failed to create surface")?; + let modifiers = Bind::::supported_formats(&self.renderer).unwrap(); + let filtered_modifiers = modifiers + .iter() + .filter(|format| format.code == fourcc) + .map(|format| format.modifier); + let surface = match &self.allocator { + Allocator::Gbm(gbm) => self + .handle + .create_surface(&window, DmabufAllocator(gbm.clone()), filtered_modifiers) + .with_context(|| "Failed to create surface")?, + Allocator::Vulkan(vulkan) => self + .handle + .create_surface( + &window, + DmabufAllocator( + VulkanAllocator::new(vulkan, ImageUsageFlags::COLOR_ATTACHMENT) + .with_context(|| "Failed to create vulkan allocator for window")?, + ), + filtered_modifiers, + ) + .with_context(|| "Failed to create surface")?, + }; let name = format!("X11-{}", self.surfaces.len()); let size = window.size(); @@ -251,6 +271,57 @@ impl Surface { } } +fn try_vulkan_allocator(node: &DrmNode) -> Option { + let instance = match Instance::new(Version::VERSION_1_2, None, None) { + Ok(instance) => instance, + Err(err) => { + slog_scope::warn!( + "Failed to instanciate vulkan, falling back to gbm allocator: {}", + err + ); + return None; + } + }; + + let devices = match PhysicalDevice::enumerate(&instance) { + Ok(devices) => devices, + Err(err) => { + slog_scope::debug!("No vulkan devices, falling back to gbm: {}", err); + return None; + } + }; + + let Some(device) = devices + .filter(|phd| { + phd.has_device_extension(smithay::reexports::ash::extensions::ext::PhysicalDeviceDrm::name()) + }) + .find(|phd| { + phd.primary_node().unwrap() == Some(*node) || phd.render_node().unwrap() == Some(*node) + }) + else { + slog_scope::debug!("No vulkan device for node {:?}, falling back to gbm", node); + return None; + }; + + Some(Allocator::Vulkan(device)) +} + +fn try_gbm_allocator(fd: OwnedFd) -> Option { + // Create the gbm device for buffer allocation. + let device = match GbmDevice::new(DrmDeviceFd::new(DeviceFd::from(fd), None)) { + Ok(gbm) => gbm, + Err(err) => { + slog_scope::error!("Failed to create GBM device: {}", err); + return None; + } + }; + + Some(Allocator::Gbm(GbmAllocator::new( + device, + GbmBufferFlags::RENDERING, + ))) +} + pub fn init_backend( dh: &DisplayHandle, event_loop: &mut EventLoop, @@ -260,16 +331,16 @@ pub fn init_backend( let handle = backend.handle(); // Obtain the DRM node the X server uses for direct rendering. - let (_drm_node, fd) = handle + let (drm_node, fd) = 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(DrmDeviceFd::new(DeviceFd::from(fd), None)) - .with_context(|| "Failed to create GBM device")?; - // Initialize EGL using the GBM device. - let egl = - EGLDisplay::new(device.clone(), None).with_context(|| "Failed to create EGL display")?; + let device = EGLDevice::enumerate() + .with_context(|| "Failed to enumerate EGL devices")? + .find(|device| device.try_get_render_node().ok().flatten() == Some(drm_node)) + .with_context(|| format!("Failed to find EGLDevice for node {}", drm_node))?; + // Initialize EGL + 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 @@ -280,7 +351,9 @@ pub fn init_backend( state.backend = BackendData::X11(X11State { handle, - allocator: device, + allocator: try_vulkan_allocator(&drm_node) + .or_else(|| try_gbm_allocator(fd)) + .expect("Failed to create allocator for x11"), _egl: egl, renderer, surfaces: Vec::new(), diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 8b29f4df..ef139e1d 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -775,15 +775,17 @@ impl RenderElement for CosmicMappedRenderElement { } } -impl<'a> RenderElement> for CosmicMappedRenderElement> { +impl<'a, 'b> RenderElement> + for CosmicMappedRenderElement> +{ fn draw<'frame>( &self, - frame: &mut GlMultiFrame<'a, 'frame>, + frame: &mut GlMultiFrame<'a, 'b, 'frame>, src: Rectangle, dst: Rectangle, damage: &[Rectangle], log: &slog::Logger, - ) -> Result<(), as Renderer>::Error> { + ) -> Result<(), as Renderer>::Error> { match self { CosmicMappedRenderElement::Stack(elem) => elem.draw(frame, src, dst, damage, log), CosmicMappedRenderElement::Window(elem) => elem.draw(frame, src, dst, damage, log), @@ -798,8 +800,8 @@ impl<'a> RenderElement> for CosmicMappedRenderElement, - ) -> Option>> { + renderer: &GlMultiRenderer<'a, 'b>, + ) -> Option>> { match self { CosmicMappedRenderElement::Stack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::Window(elem) => elem.underlying_storage(renderer), diff --git a/src/wayland/handlers/data_device.rs b/src/wayland/handlers/data_device.rs index 19dd9c11..d486091b 100644 --- a/src/wayland/handlers/data_device.rs +++ b/src/wayland/handlers/data_device.rs @@ -7,10 +7,12 @@ use smithay::{ reexports::wayland_server::protocol::{wl_data_source::WlDataSource, wl_surface::WlSurface}, utils::IsAlive, wayland::data_device::{ - ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, + with_source_metadata, ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, + ServerDndGrabHandler, }, + xwayland::xwm::{SelectionType, XwmId}, }; -use std::cell::RefCell; +use std::{cell::RefCell, os::unix::io::OwnedFd}; pub struct DnDIcon { surface: RefCell>, @@ -48,9 +50,54 @@ impl ClientDndGrabHandler for State { } impl ServerDndGrabHandler for State {} impl DataDeviceHandler for State { + type SelectionUserData = XwmId; + fn data_device_state(&self) -> &DataDeviceState { &self.common.data_device_state } + + fn new_selection(&mut self, source: Option) { + for xstate in self.common.xwayland_state.values_mut() { + if let Some(xwm) = xstate.xwm.as_mut() { + if let Some(source) = &source { + if let Ok(Err(err)) = with_source_metadata(source, |metadata| { + xwm.new_selection( + SelectionType::Clipboard, + Some(metadata.mime_types.clone()), + ) + }) { + slog_scope::warn!("Failed to set Xwayland clipboard selection: {}", err); + } + } else if let Err(err) = xwm.new_selection(SelectionType::Clipboard, None) { + slog_scope::warn!("Failed to clear Xwayland clipboard selection: {}", err); + } + } + } + } + + fn send_selection( + &mut self, + mime_type: String, + fd: OwnedFd, + user_data: &Self::SelectionUserData, + ) { + if let Some(xwm) = self + .common + .xwayland_state + .values_mut() + .flat_map(|xstate| xstate.xwm.as_mut()) + .find(|xwm| &xwm.id() == user_data) + { + if let Err(err) = xwm.send_selection( + SelectionType::Clipboard, + mime_type, + fd, + self.common.event_loop_handle.clone(), + ) { + slog_scope::warn!("Failed to send clipboard (X11 -> Wayland): {}", err); + } + } + } } delegate_data_device!(State); diff --git a/src/wayland/handlers/primary_selection.rs b/src/wayland/handlers/primary_selection.rs index d77b2264..e79c087d 100644 --- a/src/wayland/handlers/primary_selection.rs +++ b/src/wayland/handlers/primary_selection.rs @@ -3,13 +3,57 @@ use crate::state::State; use smithay::{ delegate_primary_selection, - wayland::primary_selection::{PrimarySelectionHandler, PrimarySelectionState}, + wayland::primary_selection::{PrimarySelectionHandler, PrimarySelectionState, with_source_metadata}, xwayland::xwm::{XwmId, SelectionType}, reexports::wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, }; +use std::os::unix::io::OwnedFd; + impl PrimarySelectionHandler for State { + type SelectionUserData = XwmId; + fn primary_selection_state(&self) -> &PrimarySelectionState { &self.common.primary_selection_state } + + fn new_selection(&mut self, source: Option) { + for xstate in self.common.xwayland_state.values_mut() { + if let Some(xwm) = xstate.xwm.as_mut() { + if let Some(source) = &source { + if let Ok(Err(err)) = with_source_metadata(source, |metadata| { + xwm.new_selection(SelectionType::Primary, Some(metadata.mime_types.clone())) + }) { + slog_scope::warn!("Failed to set Xwayland primary selection: {}", err); + } + } else if let Err(err) = xwm.new_selection(SelectionType::Primary, None) { + slog_scope::warn!("Failed to clear Xwayland primary selection: {}", err); + } + } + } + } + + fn send_selection( + &mut self, + mime_type: String, + fd: OwnedFd, + user_data: &Self::SelectionUserData, + ) { + if let Some(xwm) = self + .common + .xwayland_state + .values_mut() + .flat_map(|xstate| xstate.xwm.as_mut()) + .find(|xwm| &xwm.id() == user_data) + { + if let Err(err) = xwm.send_selection( + SelectionType::Primary, + mime_type, + fd, + self.common.event_loop_handle.clone(), + ) { + slog_scope::warn!("Failed to send primary selection (X11 -> Wayland): {}", err); + } + } + } } delegate_primary_selection!(State); diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index 0603da08..9d42c018 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -183,7 +183,7 @@ impl ScreencopyHandler for State { None }) .unwrap_or(kms.primary.clone()); - _kms_renderer = Some(kms.api.renderer::(&node, &node).unwrap()); + _kms_renderer = Some(kms.api.single_renderer(&node).unwrap()); _kms_renderer.as_mut().unwrap().as_mut() } BackendData::Winit(ref mut winit) => winit.backend.renderer(), @@ -423,7 +423,7 @@ fn formats_for_output( let renderer = match backend { BackendData::Kms(ref mut kms) => { let node = kms.target_node_for_output(&output).unwrap_or(kms.primary); - _kms_renderer = Some(kms.api.renderer::(&node, &node).unwrap()); + _kms_renderer = Some(kms.api.single_renderer(&node).unwrap()); _kms_renderer.as_mut().unwrap().as_mut() } BackendData::Winit(ref mut winit) => winit.backend.renderer(), @@ -664,7 +664,7 @@ pub fn render_output_to_buffer( BackendData::Kms(kms) => { let mut multirenderer = kms .api - .renderer::(node.as_ref().unwrap(), node.as_ref().unwrap()) + .single_renderer(node.as_ref().unwrap()) .map_err(|err| (FailureReason::Unspec, err.into()))?; render_session::<_, _>( node, @@ -794,7 +794,7 @@ pub fn render_workspace_to_buffer( BackendData::Kms(kms) => { let mut multirenderer = kms .api - .renderer::(node.as_ref().unwrap(), node.as_ref().unwrap()) + .single_renderer(node.as_ref().unwrap()) .map_err(|err| (FailureReason::Unspec, err.into()))?; render_session::<_, _>( node, @@ -959,7 +959,7 @@ pub fn render_window_to_buffer( BackendData::Kms(kms) => { let mut multirenderer = kms .api - .renderer::(node.as_ref().unwrap(), node.as_ref().unwrap()) + .single_renderer(node.as_ref().unwrap()) .map_err(|err| (FailureReason::Unspec, err.into()))?; render_session::<_, _>( node, diff --git a/src/xwayland.rs b/src/xwayland.rs index b58bb615..35f097af 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -1,8 +1,8 @@ -use std::ffi::OsString; +use std::{ffi::OsString, os::unix::io::OwnedFd}; use crate::{ backend::render::cursor::Cursor, - shell::{CosmicSurface, Shell}, + shell::{focus::target::KeyboardFocusTarget, CosmicSurface, Shell}, state::{Data, State}, utils::prelude::*, wayland::{handlers::screencopy::PendingScreencopyBuffers, protocols::screencopy::SessionType}, @@ -12,8 +12,18 @@ use smithay::{ desktop::space::SpaceElement, reexports::x11rb::protocol::xproto::Window as X11Window, utils::{Logical, Point, Rectangle, Size}, + wayland::{ + data_device::{ + clear_data_device_selection, current_data_device_selection_userdata, + request_data_device_client_selection, set_data_device_selection, + }, + primary_selection::{ + clear_primary_selection, current_primary_selection_userdata, + request_primary_client_selection, set_primary_selection, + }, + }, xwayland::{ - xwm::{Reorder, XwmId}, + xwm::{Reorder, SelectionType, XwmId}, X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler, }, }; @@ -449,4 +459,86 @@ impl XwmHandler for Data { } } } + + fn send_selection( + &mut self, + _xwm: XwmId, + selection: SelectionType, + mime_type: String, + fd: OwnedFd, + ) { + let seat = self.state.common.last_active_seat(); + match selection { + SelectionType::Clipboard => { + if let Err(err) = request_data_device_client_selection(seat, mime_type, fd) { + slog_scope::error!( + "Failed to request current wayland clipboard for Xwayland: {}", + err + ); + } + } + SelectionType::Primary => { + if let Err(err) = request_primary_client_selection(seat, mime_type, fd) { + slog_scope::error!( + "Failed to request current wayland primary selection for Xwayland: {}", + err + ); + } + } + } + } + + fn allow_selection_access(&mut self, xwm: XwmId, _selection: SelectionType) -> bool { + self.state.common.is_x_focused(xwm) + } + + fn new_selection(&mut self, xwm: XwmId, selection: SelectionType, mime_types: Vec) { + slog_scope::info!("Got Selection {:?} from X11: {:?}", selection, mime_types); + + if self.state.common.is_x_focused(xwm) { + let seat = self.state.common.last_active_seat(); + match selection { + SelectionType::Clipboard => set_data_device_selection( + &self.state.common.display_handle, + &seat, + mime_types, + xwm, + ), + SelectionType::Primary => { + set_primary_selection(&self.state.common.display_handle, &seat, mime_types, xwm) + } + } + } + } + + fn cleared_selection(&mut self, xwm: XwmId, selection: SelectionType) { + for seat in self.state.common.seats() { + match selection { + SelectionType::Clipboard => { + if current_data_device_selection_userdata(seat).as_deref() == Some(&xwm) { + clear_data_device_selection(&self.state.common.display_handle, seat) + } + } + SelectionType::Primary => { + if current_primary_selection_userdata(seat).as_deref() == Some(&xwm) { + clear_primary_selection(&self.state.common.display_handle, seat) + } + } + } + } + } +} + +impl Common { + fn is_x_focused(&self, xwm: XwmId) -> bool { + if let Some(keyboard) = self.last_active_seat().get_keyboard() { + if let Some(KeyboardFocusTarget::Element(mapped)) = keyboard.current_focus() { + if let CosmicSurface::X11(surface) = mapped.active_window() { + return surface.xwm_id().unwrap() == xwm; + } + } + } + + false + } }