From 10e534c1be2643f6722546fde78ed85f2b86af0e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 20 Mar 2024 15:41:09 -0400 Subject: [PATCH] wip: Dnd setup --- Cargo.toml | 3 +- examples/clipboard.rs | 20 +++++++++-- src/dnd/mod.rs | 81 +++++++++++++++++++++++++++++++++++++++---- src/lib.rs | 42 ++++++++++++---------- src/state.rs | 43 +++++++++++++---------- src/worker.rs | 23 ++++++++---- 6 files changed, 161 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 00c441c..cb7992b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,6 @@ thiserror = "1.0.57" url = "2.5.0" [features] -default = ["dlopen"] +default = ["dlopen", "dnd"] +dnd = [] dlopen = ["wayland-backend/dlopen" ] diff --git a/examples/clipboard.rs b/examples/clipboard.rs index bc1c9dd..557e531 100644 --- a/examples/clipboard.rs +++ b/examples/clipboard.rs @@ -3,6 +3,7 @@ // `smithay-client-toolkit` examples. use std::borrow::Cow; +use std::ffi::c_void; use std::str::{FromStr, Utf8Error}; use sctk::compositor::{CompositorHandler, CompositorState}; @@ -10,6 +11,7 @@ use sctk::output::{OutputHandler, OutputState}; use sctk::reexports::calloop::{EventLoop, LoopHandle}; use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::client::globals::registry_queue_init; +use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::{wl_keyboard, wl_output, wl_seat, wl_shm, wl_surface}; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::registry::{ProvidesRegistryState, RegistryState}; @@ -25,12 +27,20 @@ use sctk::{ delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers, }; use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType}; -use smithay_clipboard::Clipboard; +use smithay_clipboard::{Clipboard, SimpleClipboard}; use thiserror::Error; use url::Url; const MIN_DIM_SIZE: usize = 256; +struct MySurface(WlSurface); + +impl smithay_clipboard::dnd::RawSurface for MySurface { + unsafe fn get_ptr(&mut self) -> *mut c_void { + self.0.id().as_ptr().cast() + } +} + fn main() { let connection = Connection::connect_to_env().unwrap(); let (globals, event_queue) = registry_queue_init(&connection).unwrap(); @@ -55,6 +65,12 @@ fn main() { let clipboard = unsafe { Clipboard::new(connection.display().id().as_ptr() as *mut _) }; let pool = SlotPool::new(MIN_DIM_SIZE * MIN_DIM_SIZE * 4, &shm).expect("Failed to create pool"); + let (tx, rx) = sctk::reexports::calloop::channel::sync_channel(10); + clipboard.init_dnd(Box::new(tx)); + + event_loop.handle().insert_source(rx, |event, _, state| { + dbg!(event); + }); let mut simple_window = SimpleWindow { registry_state: RegistryState::new(&globals), @@ -90,7 +106,7 @@ struct SimpleWindow { seat_state: SeatState, output_state: OutputState, shm: Shm, - clipboard: Clipboard, + clipboard: SimpleClipboard, exit: bool, first_configure: bool, diff --git a/src/dnd/mod.rs b/src/dnd/mod.rs index 3df6524..9a67276 100644 --- a/src/dnd/mod.rs +++ b/src/dnd/mod.rs @@ -1,14 +1,83 @@ +use std::ffi::c_void; +use std::fmt::Debug; +use std::sync::mpsc::SendError; + +use sctk::reexports::calloop; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::{Connection, Proxy}; +use wayland_backend::client::{InvalidId, ObjectId}; + use crate::Clipboard; -impl Clipboard { +pub struct DndSurface { + pub surface: WlSurface, + pub s: T, +} + +impl Debug for DndSurface { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Surface").field("surface", &self.surface).finish() + } +} + +impl DndSurface { + fn new(mut s: T, conn: &Connection) -> Result { + let ptr = unsafe { s.get_ptr() }; + let id = unsafe { ObjectId::from_ptr(WlSurface::interface(), ptr.cast())? }; + let surface = WlSurface::from_id(conn, id)?; + Ok(Self { s, surface }) + } +} + +impl RawSurface for WlSurface { + unsafe fn get_ptr(&mut self) -> *mut c_void { + self.id().as_ptr().cast() + } +} + +pub trait RawSurface { + /// must return a valid `*mut wl_surface` pointer, and it must remain + /// valid for as long as this object is alive. + unsafe fn get_ptr(&mut self) -> *mut c_void; +} + +pub trait Sender { + /// Send an event in the channel + fn send(&self, t: DndEvent) -> Result<(), SendError>>; +} + +#[derive(Debug)] +pub enum DndEvent { + Test(T), +} + +impl Sender for calloop::channel::Sender> { + fn send(&self, t: DndEvent) -> Result<(), SendError>> { + self.send(t) + } +} + +impl Sender for calloop::channel::SyncSender> { + fn send(&self, t: DndEvent) -> Result<(), SendError>> { + self.send(t) + } +} + +impl Clipboard { /// Set up DnD operations for the Clipboard - pub fn init_dnd() {} + pub fn init_dnd( + &self, + tx: Box + Send>, + ) -> Result<(), SendError>> { + self.request_sender.send(crate::worker::Command::InitDnD(tx)) + } /// Start a DnD operation on the given surface with some data - pub fn start_dnd() {} + pub fn start_dnd(&self, s: D) { + let s = DndSurface::new(s, &self.connection).unwrap(); + dbg!(&s.surface); + } /// End the current DnD operation, if there is one pub fn end_dnd() {} - - -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 7aa3f83..0e0d0e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ use sctk::reexports::calloop::channel::{self, Sender}; use sctk::reexports::client::backend::Backend; use sctk::reexports::client::Connection; +#[cfg(feature = "dnd")] +pub mod dnd; pub mod mime; mod state; mod text; @@ -22,14 +24,17 @@ use mime::{AllowedMimeTypes, AsMimeTypes, MimeType}; use state::SelectionTarget; use text::Text; +pub type SimpleClipboard = Clipboard<()>; + /// Access to a Wayland clipboard. -pub struct Clipboard { - request_sender: Sender, +pub struct Clipboard { + request_sender: Sender>, request_receiver: Receiver, MimeType)>>, clipboard_thread: Option>, + connection: Connection, } -impl Clipboard { +impl Clipboard { /// Creates new clipboard which will be running on its own thread with its /// own event queue to handle clipboard requests. /// @@ -47,16 +52,17 @@ impl Clipboard { let (clipboard_reply_sender, request_receiver) = mpsc::channel(); let name = String::from("smithay-clipboard"); - let clipboard_thread = worker::spawn(name, connection, rx_chan, clipboard_reply_sender); + let clipboard_thread = + worker::spawn(name, connection.clone(), rx_chan, clipboard_reply_sender); - Self { request_receiver, request_sender, clipboard_thread } + Self { request_receiver, request_sender, clipboard_thread, connection } } /// Load custom clipboard data. /// /// Load the requested type from a clipboard on the last observed seat. - pub fn load(&self) -> Result { - self.load_inner(SelectionTarget::Clipboard, T::allowed()) + pub fn load(&self) -> Result { + self.load_inner(SelectionTarget::Clipboard, D::allowed()) } /// Load clipboard data. @@ -70,8 +76,8 @@ impl Clipboard { /// /// Load the requested type from a primary clipboard on the last observed /// seat. - pub fn load_primary(&self) -> Result { - self.load_inner(SelectionTarget::Primary, T::allowed()) + pub fn load_primary(&self) -> Result { + self.load_inner(SelectionTarget::Primary, D::allowed()) } /// Load primary clipboard data. @@ -104,14 +110,14 @@ impl Clipboard { /// Store custom data to a clipboard. /// /// Stores data of the provided type to a clipboard on a last observed seat. - pub fn store(&self, data: T) { + pub fn store(&self, data: D) { self.store_inner(data, SelectionTarget::Clipboard); } /// Store to a clipboard. /// /// Stores to a clipboard on a last observed seat. - pub fn store_text>(&self, text: T) { + pub fn store_text>(&self, text: D) { self.store(Text(text.into())); } @@ -119,27 +125,27 @@ impl Clipboard { /// /// Stores data of the provided type to a primary clipboard on a last /// observed seat. - pub fn store_primary(&self, data: T) { + pub fn store_primary(&self, data: D) { self.store_inner(data, SelectionTarget::Primary); } /// Store to a primary clipboard. /// /// Stores to a primary clipboard on a last observed seat. - pub fn store_primary_text>(&self, text: T) { + pub fn store_primary_text>(&self, text: D) { self.store_primary(Text(text.into())); } - fn load_inner, MimeType)> + 'static>( + fn load_inner, MimeType)> + 'static>( &self, target: SelectionTarget, allowed: impl Into>, - ) -> Result { + ) -> Result { let _ = self.request_sender.send(worker::Command::Load(allowed.into(), target)); match self.request_receiver.recv() { Ok(res) => res.and_then(|(data, mime)| { - T::try_from((data, mime)).map_err(|_| { + D::try_from((data, mime)).map_err(|_| { std::io::Error::new( std::io::ErrorKind::Other, "Failed to load data of the requested type.", @@ -152,13 +158,13 @@ impl Clipboard { } } - fn store_inner(&self, data: T, target: SelectionTarget) { + fn store_inner(&self, data: D, target: SelectionTarget) { let request = worker::Command::Store(Box::new(data), target); let _ = self.request_sender.send(request); } } -impl Drop for Clipboard { +impl Drop for Clipboard { fn drop(&mut self) { // Shutdown smithay-clipboard. let _ = self.request_sender.send(worker::Command::Exit); diff --git a/src/state.rs b/src/state.rs index 1683649..debe84f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::io::{Error, ErrorKind, Read, Result, Write}; +use std::marker::PhantomData; use std::mem; use std::os::unix::io::{AsRawFd, RawFd}; use std::rc::Rc; @@ -39,7 +40,7 @@ use wayland_backend::client::ObjectId; use crate::mime::{AsMimeTypes, MimeType}; use crate::text::Text; -pub struct State { +pub struct State { pub primary_selection_manager_state: Option, pub data_device_manager_state: Option, pub reply_tx: Sender, MimeType)>>, @@ -62,9 +63,12 @@ pub struct State { data_sources: Vec, data_selection_content: Box, data_selection_mime_types: Rc>, + #[cfg(feature = "dnd")] + pub(crate) sender: Option>>, + _phantom: PhantomData, } -impl State { +impl State { #[must_use] pub fn new( globals: &GlobalList, @@ -105,6 +109,9 @@ impl State { seats, primary_selection_mime_types: Rc::new(Default::default()), data_selection_mime_types: Rc::new(Default::default()), + #[cfg(feature = "dnd")] + sender: None, + _phantom: PhantomData, }) } @@ -281,7 +288,7 @@ impl State { } } -impl SeatHandler for State { +impl SeatHandler for State { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } @@ -364,7 +371,7 @@ impl SeatHandler for State { } } -impl PointerHandler for State { +impl PointerHandler for State { fn pointer_frame( &mut self, _: &Connection, @@ -398,7 +405,7 @@ impl PointerHandler for State { } } -impl DataDeviceHandler for State { +impl DataDeviceHandler for State { fn enter(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} fn leave(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} @@ -411,7 +418,7 @@ impl DataDeviceHandler for State { fn selection(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} } -impl DataSourceHandler for State { +impl DataSourceHandler for State { fn send_request( &mut self, _: &Connection, @@ -443,7 +450,7 @@ impl DataSourceHandler for State { fn dnd_finished(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataSource) {} } -impl DataOfferHandler for State { +impl DataOfferHandler for State { fn source_actions( &mut self, _: &Connection, @@ -463,7 +470,7 @@ impl DataOfferHandler for State { } } -impl ProvidesRegistryState for State { +impl ProvidesRegistryState for State { registry_handlers![SeatState]; fn registry(&mut self) -> &mut RegistryState { @@ -471,7 +478,7 @@ impl ProvidesRegistryState for State { } } -impl PrimarySelectionDeviceHandler for State { +impl PrimarySelectionDeviceHandler for State { fn selection( &mut self, _: &Connection, @@ -481,7 +488,7 @@ impl PrimarySelectionDeviceHandler for State { } } -impl PrimarySelectionSourceHandler for State { +impl PrimarySelectionSourceHandler for State { fn send_request( &mut self, _: &Connection, @@ -503,14 +510,14 @@ impl PrimarySelectionSourceHandler for State { } } -impl Dispatch for State { +impl Dispatch> for State { fn event( - state: &mut State, + state: &mut State, _: &WlKeyboard, event: ::Event, data: &ObjectId, _: &Connection, - _: &QueueHandle, + _: &QueueHandle>, ) { use sctk::reexports::client::protocol::wl_keyboard::Event as WlKeyboardEvent; let seat_state = match state.seats.get_mut(data) { @@ -536,11 +543,11 @@ impl Dispatch for State { } } -delegate_seat!(State); -delegate_pointer!(State); -delegate_data_device!(State); -delegate_primary_selection!(State); -delegate_registry!(State); +delegate_seat!(@ State); +delegate_pointer!(@ State); +delegate_data_device!(@ State); +delegate_primary_selection!(@ State); +delegate_registry!(@ State); #[derive(Debug, Clone, Copy)] pub enum SelectionTarget { diff --git a/src/worker.rs b/src/worker.rs index 1ddc90f..edc08a7 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::io::{Error, ErrorKind, Result}; +use std::marker::PhantomData; use std::sync::mpsc::Sender; use sctk::reexports::calloop::channel::Channel; @@ -13,10 +14,10 @@ use crate::state::{SelectionTarget, State}; /// Spawn a clipboard worker, which dispatches its own `EventQueue` and handles /// clipboard requests. -pub fn spawn( +pub fn spawn( name: String, display: Connection, - rx_chan: Channel, + rx_chan: Channel>, worker_replier: Sender, MimeType)>>, ) -> Option> { std::thread::Builder::new() @@ -28,19 +29,24 @@ pub fn spawn( } /// Clipboard worker thread command. -pub enum Command { +pub enum Command { /// Loads data for the first available mime type in the provided list. Load(Cow<'static, [MimeType]>, SelectionTarget), /// Store Data with the given mime types. Store(Box, SelectionTarget), + #[cfg(feature = "dnd")] + /// Init DnD + InitDnD(Box + Send>), /// Shutdown the worker. Exit, + /// Phantom data + Phantom(PhantomData), } /// Handle clipboard requests. -fn worker_impl( +fn worker_impl( connection: Connection, - rx_chan: Channel, + rx_chan: Channel>, reply_tx: Sender, MimeType)>>, ) { let (globals, event_queue) = match registry_queue_init(&connection) { @@ -48,7 +54,7 @@ fn worker_impl( Err(_) => return, }; - let mut event_loop = EventLoop::::try_new().unwrap(); + let mut event_loop = EventLoop::>::try_new().unwrap(); let loop_handle = event_loop.handle(); let mut state = match State::new(&globals, &event_queue.handle(), loop_handle.clone(), reply_tx) @@ -89,6 +95,11 @@ fn worker_impl( "requested selection is not supported", ))); }, + #[cfg(feature = "dnd")] + Command::InitDnD(sender) => { + state.sender = Some(sender); + }, + Command::Phantom(_) => unreachable!(), } } })