use std::ffi::c_void; use std::fmt::Debug; use std::sync::mpsc::SendError; use sctk::reexports::calloop; use sctk::reexports::client::protocol::wl_data_device_manager::DndAction; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Connection, Proxy}; use wayland_backend::client::{InvalidId, ObjectId}; use crate::mime::{AsMimeTypes, MimeType}; use crate::Clipboard; pub mod state; #[derive(Clone)] pub struct DndSurface { pub(crate) 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 SourceEvent { /// DnD operation ended. Finished, /// DnD Cancelled. Cancelled, /// DnD action chosen by the compositor. Action(DndAction), /// Mime accepted by destination. /// If [`None`], no mime types are accepted. Mime(Option), /// DnD Dropped. The operation is still ongoing until receiving a /// [`Finished`] event. Dropped, } #[derive(Debug)] pub enum OfferEvent { Enter { x: f64, y: f64, mime_types: Vec, surface: T, }, Motion { x: f64, y: f64, }, /// The offer is no longer on a DnD destination. LeaveDestination, /// The offer has left the surface. Leave, /// An offer was dropped Drop, /// If the selected action is ASK, the user must be presented with a choice. /// [`Clipboard::set_actions`] should then be called before data can be /// requested and th DnD operation can be finished. SelectedAction(DndAction), Data { data: Vec, mime_type: MimeType, }, } /// A rectangle with a logical location and size relative to a [`Surface`] #[derive(Debug, Default, Clone)] pub struct Rectangle { pub x: f64, pub y: f64, pub width: f64, pub height: f64, } impl Rectangle { fn contains(&self, x: f64, y: f64) -> bool { self.x <= x && self.x + self.width >= x && self.y <= y && self.y + self.height >= y } } #[derive(Debug, Clone)] pub struct DndDestinationRectangle { /// A unique ID pub id: u128, /// The rectangle representing this destination. pub rectangle: Rectangle, /// Accepted mime types in this rectangle pub mime_types: Vec, /// Accepted actions in this rectangle pub actions: DndAction, /// Prefered action in this rectangle pub preferred: DndAction, } pub enum DndRequest { /// Init DnD InitDnD(Box + Send>), /// Register a surface for receiving Dnd events. Surface(DndSurface, Vec), /// Start a Dnd operation with the given source surface and data. StartDnd { internal: bool, source: DndSurface, icon: Option>, content: Box, actions: DndAction, }, /// Set the DnD action chosen by the user. SetAction(DndAction), /// End an active DnD Source DndEnd, } #[derive(Debug)] pub enum DndEvent { /// Dnd Offer event with the corresponding destination rectangle ID. Offer(Option, OfferEvent), /// Dnd Source event. Source(SourceEvent), } 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( &self, tx: Box + Send>, ) -> Result<(), SendError>> { self.request_sender.send(crate::worker::Command::DndRequest(DndRequest::InitDnD(tx))) } /// Start a DnD operation on the given surface with some data pub fn start_dnd( &self, internal: bool, source_surface: T, icon_surface: Option, content: D, actions: DndAction, ) { let source = DndSurface::new(source_surface, &self.connection).unwrap(); let icon = icon_surface.map(|s| DndSurface::new(s, &self.connection).unwrap()); _ = self.request_sender.send(crate::worker::Command::DndRequest(DndRequest::StartDnd { internal, source, icon, content: Box::new(content), actions, })); } /// End the current DnD operation, if there is one pub fn end_dnd(&self) { _ = self.request_sender.send(crate::worker::Command::DndRequest(DndRequest::DndEnd)); } /// Register a surface for receiving DnD offers /// Rectangles should be provided in order of decreasing priority. pub fn register_dnd_destination(&self, surface: T, rectangles: Vec) { let s = DndSurface::new(surface, &self.connection).unwrap(); _ = self .request_sender .send(crate::worker::Command::DndRequest(DndRequest::Surface(s, rectangles))); } /// Set the final action after presenting the user with a choice pub fn set_action(&self, action: DndAction) { _ = self .request_sender .send(crate::worker::Command::DndRequest(DndRequest::SetAction(action))); } }