From fde0689defb42a33c4bc034779c3e8611d4c7f3c Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 17 Dec 2024 15:13:24 -0800 Subject: [PATCH] winit/wayland: Subsurfaces for drag surfaces Use `Icon::Surface` instead of `Icon::Buffer`, so we can then create subsurfaces of the `wl_surface`. Using `.screenshot()` then copying to an shm buffer is suboptimal, but this does seem overall better than with the older Iced version when a drag surface didn't appear until a Vulkan surface could be created for it. This re-uses `Connection` in the platform-specific code, instead of creating from display handle on each call. --- Cargo.toml | 1 + winit/Cargo.toml | 2 + winit/src/lib.rs | 17 +++- winit/src/platform_specific/mod.rs | 53 ++++++----- winit/src/platform_specific/wayland/mod.rs | 95 +++++++++++++++++++ .../wayland/subsurface_widget.rs | 48 +++++++++- 6 files changed, 190 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 21c5c640..099d57bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -296,6 +296,7 @@ wgpu = { version = "27.0", default-features = false, features = [ "wgsl", ] } wayland-protocols = { version = "0.32.1", features = ["staging"] } +wayland-client = { version = "0.31.5" } # web-time = "1.1" diff --git a/winit/Cargo.toml b/winit/Cargo.toml index d1850590..e72b2fca 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -73,12 +73,14 @@ cctk.workspace = true cctk.optional = true wayland-protocols.workspace = true wayland-protocols.optional = true +wayland-client.workspace = true wayland-backend = { version = "0.3.1", features = [ "client_system", ], optional = true } xkbcommon = { version = "0.7", features = ["wayland"], optional = true } xkbcommon-dl = { version = "0.4.1", optional = true } xkeysym = { version = "0.2.0", optional = true } +rustix = { version = "0.38" } [target.'cfg(target_os = "windows")'.dependencies] winapi.workspace = true diff --git a/winit/src/lib.rs b/winit/src/lib.rs index a2da3372..6060b87d 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -24,6 +24,7 @@ pub use iced_program as program; pub use program::core; pub use program::graphics; pub use program::runtime; +use raw_window_handle::HasWindowHandle; pub use runtime::futures; use window_clipboard::mime::ClipboardStoreData; pub use winit; @@ -657,6 +658,10 @@ async fn run_instance

( let mut clipboard = Clipboard::unconnected(); let mut cur_dnd_surface: Option = None; + let mut dnd_surface: Option< + Arc>, + > = None; + #[cfg(feature = "a11y")] let (mut adapters, mut a11y_enabled) = (Default::default(), false); // let (mut adapters, mut a11y_enabled) = if let Some((main_id, title, raw)) = @@ -1133,7 +1138,8 @@ async fn run_instance

( }, cursor, ); - platform_specific_handler.clear_subsurface_list(); + platform_specific_handler + .update_subsurfaces(id, window.raw.rwh_06_window_handle()); draw_span.finish(); if let user_interface::State::Updated { @@ -1508,7 +1514,14 @@ async fn run_instance

( dnd::DndEvent::Offer(..) => { events.push((cur_dnd_surface, core::Event::Dnd(e))); } - dnd::DndEvent::Source(_) => { + dnd::DndEvent::Source(evt) => { + match evt { + dnd::SourceEvent::Finished + | dnd::SourceEvent::Cancelled => { + dnd_surface = None; + } + _ => {} + } for w in window_manager.ids() { events.push((Some(w), core::Event::Dnd(e.clone()))); } diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index ef65b2c5..cd8053c5 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use cctk::sctk::reexports::client::Connection; use iced_graphics::{Compositor, compositor}; use iced_runtime::{core::window, platform_specific, user_interface}; +use raw_window_handle::HasWindowHandle; #[cfg(all(feature = "wayland", target_os = "linux"))] pub mod wayland; @@ -82,7 +83,7 @@ impl PlatformSpecific { pub(crate) fn update_subsurfaces( &mut self, id: window::Id, - window: &dyn winit::window::Window, + window: &dyn HasWindowHandle, ) { #[cfg(all(feature = "wayland", target_os = "linux"))] { @@ -91,31 +92,12 @@ impl PlatformSpecific { }; use wayland_backend::client::ObjectId; - let Ok(backend) = window.rwh_06_display_handle().display_handle() - else { - log::error!("No display handle"); + let Some(conn) = self.wayland.conn() else { + log::error!("No Wayland conn"); return; }; - let conn = match backend.as_raw() { - raw_window_handle::RawDisplayHandle::Wayland( - wayland_display_handle, - ) => { - let backend = unsafe { - Backend::from_foreign_display( - wayland_display_handle.display.as_ptr().cast(), - ) - }; - cctk::sctk::reexports::client::Connection::from_backend( - backend, - ) - } - _ => { - return; - } - }; - - let Ok(raw) = window.rwh_06_window_handle().window_handle() else { + let Ok(raw) = window.window_handle() else { log::error!("Invalid window handle {id:?}"); return; }; @@ -150,6 +132,31 @@ impl PlatformSpecific { self.wayland.update_subsurfaces(id, &wl_surface); } } + + pub(crate) fn create_surface( + &mut self, + ) -> Option> { + #[cfg(all(feature = "wayland", target_os = "linux"))] + { + return self.wayland.create_surface(); + } + None + } + + pub(crate) fn update_surface_shm( + &mut self, + surface: &dyn HasWindowHandle, + width: u32, + height: u32, + data: &[u8], + ) { + #[cfg(all(feature = "wayland", target_os = "linux"))] + { + return self + .wayland + .update_surface_shm(surface, width, height, data); + } + } } pub(crate) fn handle_event<'a, P>( diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 3fca711a..bf125e12 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -18,10 +18,13 @@ use cursor_icon::CursorIcon; use iced_futures::futures::channel::mpsc; use iced_graphics::{Compositor, compositor}; use iced_runtime::core::window; +use raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle}; +use raw_window_handle::{HasRawDisplayHandle, RawWindowHandle}; use sctk_event::SctkEvent; use std::{collections::HashMap, sync::Arc}; use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; use wayland_backend::client::ObjectId; +use wayland_client::{Connection, Proxy}; use winit::event_loop::OwnedDisplayHandle; pub(crate) enum Action { @@ -60,6 +63,7 @@ pub(crate) struct WaylandSpecific { proxy: Option, sender: Option>, display_handle: Option, + conn: Option, modifiers: Modifiers, surface_ids: HashMap, subsurface_state: Option, @@ -74,6 +78,26 @@ impl PlatformSpecific { display: OwnedDisplayHandle, ) -> Self { self.wayland.winit_event_sender = Some(tx); + self.wayland.conn = match display.raw_display_handle() { + Ok(raw_window_handle::RawDisplayHandle::Wayland( + wayland_display_handle, + )) => { + let backend = unsafe { + wayland_backend::client::Backend::from_foreign_display( + wayland_display_handle.display.as_ptr().cast(), + ) + }; + Some(Connection::from_backend(backend)) + } + Ok(_) => { + log::error!("Non-Wayland display handle"); + None + } + Err(_) => { + log::error!("No display handle"); + None + } + }; self.wayland.display_handle = Some(display); self.wayland.proxy = Some(raw); // TODO remove this @@ -111,6 +135,10 @@ impl PlatformSpecific { } impl WaylandSpecific { + pub(crate) fn conn(&self) -> Option<&Connection> { + self.conn.as_ref() + } + pub(crate) fn handle_event<'a, P>( &mut self, e: SctkEvent, @@ -135,6 +163,7 @@ impl WaylandSpecific { proxy, sender, display_handle, + conn, surface_ids, modifiers, subsurface_state, @@ -198,4 +227,70 @@ impl WaylandSpecific { &subsurfaces, ); } + + pub(crate) fn create_surface( + &mut self, + ) -> Option> { + if let Some(subsurface_state) = self.subsurface_state.as_mut() { + let wl_surface = subsurface_state.create_surface(); + Some(Box::new(Window(wl_surface))) + } else { + None + } + } + + pub(crate) fn update_surface_shm( + &mut self, + window: &dyn HasWindowHandle, + width: u32, + height: u32, + data: &[u8], + ) { + if let Some(subsurface_state) = self.subsurface_state.as_mut() { + if let RawWindowHandle::Wayland(window) = + window.window_handle().unwrap().as_raw() + { + let id = unsafe { + ObjectId::from_ptr( + WlSurface::interface(), + window.surface.as_ptr().cast(), + ) + .unwrap() + }; + let surface = + WlSurface::from_id(self.conn.as_ref().unwrap(), id) + .unwrap(); + subsurface_state + .update_surface_shm(&surface, width, height, data); + } + } + } +} + +struct Window(WlSurface); + +impl HasWindowHandle for Window { + fn window_handle( + &self, + ) -> Result< + raw_window_handle::WindowHandle<'_>, + raw_window_handle::HandleError, + > { + Ok(unsafe { + raw_window_handle::WindowHandle::borrow_raw( + raw_window_handle::RawWindowHandle::Wayland( + raw_window_handle::WaylandWindowHandle::new( + std::ptr::NonNull::new(self.0.id().as_ptr() as *mut _) + .unwrap(), + ), + ), + ) + }) + } +} + +impl Drop for Window { + fn drop(&mut self) { + self.0.destroy(); + } } diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 4d9e1cba..b2e716c9 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -23,7 +23,9 @@ use std::{ use crate::futures::futures::channel::oneshot; use cctk::sctk::{ compositor::SurfaceData, - globals::GlobalData, + error::GlobalError, + globals::{GlobalData, ProvidesBoundGlobal}, + shm::slot::SlotPool, reexports::client::{ Connection, Dispatch, Proxy, QueueHandle, delegate_noop, protocol::{ @@ -250,6 +252,7 @@ impl PartialEq for SubsurfaceBuffer { } } + impl Dispatch for SctkState { fn event( _: &mut SctkState, @@ -287,6 +290,23 @@ impl Dispatch for SctkState { } } +impl Dispatch for SctkState { + fn event( + _: &mut SctkState, + _: &WlBuffer, + event: wl_buffer::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_buffer::Event::Release => { + } + _ => unreachable!(), + } + } +} + impl Dispatch for SctkState { fn event( _: &mut SctkState, @@ -324,6 +344,15 @@ impl Hash for WeakBufferSource { } } +// Implement `ProvidesBoundGlobal` to use `SlotPool` +struct ShmGlobal<'a>(&'a WlShm); + +impl<'a> ProvidesBoundGlobal for ShmGlobal<'a> { + fn bound_global(&self) -> Result { + Ok(self.0.clone()) + } +} + // create wl_buffer from BufferSource (avoid create_immed?) // release #[derive(Debug, Clone)] @@ -341,6 +370,22 @@ pub struct SubsurfaceState { } impl SubsurfaceState { + pub fn create_surface(&self) -> WlSurface { + self + .wl_compositor + .create_surface(&self.qh, SurfaceData::new(None, 1)) + } + + pub fn update_surface_shm(&self, surface: &WlSurface, width: u32, height: u32, data: &[u8]) { + let shm = ShmGlobal(&self.wl_shm); + let mut pool = SlotPool::new(width as usize * height as usize * 4, &shm).unwrap(); + let (buffer, canvas) = pool.create_buffer(width as i32, height as i32, width as i32 * 4, wl_shm::Format::Argb8888).unwrap(); + canvas[0..width as usize * height as usize * 4].copy_from_slice(data); + surface.damage_buffer(0, 0, width as i32, height as i32); + buffer.attach_to(&surface); + surface.commit(); + } + fn create_subsurface(&self, parent: &WlSurface) -> SubsurfaceInstance { let wl_surface = self .wl_compositor @@ -571,6 +616,7 @@ impl Drop for SubsurfaceInstance { } } +#[derive(Debug)] pub(crate) struct SubsurfaceInfo { pub buffer: SubsurfaceBuffer, pub bounds: Rectangle,