smithay-clipboard/src/dnd/mod.rs
2024-03-25 16:18:46 -04:00

216 lines
6.2 KiB
Rust

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<T> {
pub(crate) surface: WlSurface,
pub s: T,
}
impl<T> Debug for DndSurface<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Surface").field("surface", &self.surface).finish()
}
}
impl<T: RawSurface> DndSurface<T> {
fn new(mut s: T, conn: &Connection) -> Result<Self, InvalidId> {
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<T> {
/// Send an event in the channel
fn send(&self, t: DndEvent<T>) -> Result<(), SendError<DndEvent<T>>>;
}
#[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<MimeType>),
/// DnD Dropped. The operation is still ongoing until receiving a
/// [`Finished`] event.
Dropped,
}
#[derive(Debug)]
pub enum OfferEvent<T> {
Enter {
x: f64,
y: f64,
mime_types: Vec<MimeType>,
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<u8>,
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<MimeType>,
/// Accepted actions in this rectangle
pub actions: DndAction,
/// Prefered action in this rectangle
pub preferred: DndAction,
}
pub enum DndRequest<T> {
/// Init DnD
InitDnD(Box<dyn crate::dnd::Sender<T> + Send>),
/// Register a surface for receiving Dnd events.
Surface(DndSurface<T>, Vec<DndDestinationRectangle>),
/// Start a Dnd operation with the given source surface and data.
StartDnd {
internal: bool,
source: DndSurface<T>,
icon: Option<DndSurface<T>>,
content: Box<dyn AsMimeTypes + Send>,
actions: DndAction,
},
/// Set the DnD action chosen by the user.
SetAction(DndAction),
/// End an active DnD Source
DndEnd,
}
#[derive(Debug)]
pub enum DndEvent<T> {
/// Dnd Offer event with the corresponding destination rectangle ID.
Offer(Option<u128>, OfferEvent<T>),
/// Dnd Source event.
Source(SourceEvent),
}
impl<T> Sender<T> for calloop::channel::Sender<DndEvent<T>> {
fn send(&self, t: DndEvent<T>) -> Result<(), SendError<DndEvent<T>>> {
self.send(t)
}
}
impl<T> Sender<T> for calloop::channel::SyncSender<DndEvent<T>> {
fn send(&self, t: DndEvent<T>) -> Result<(), SendError<DndEvent<T>>> {
self.send(t)
}
}
impl<T: RawSurface> Clipboard<T> {
/// Set up DnD operations for the Clipboard
pub fn init_dnd(
&self,
tx: Box<dyn Sender<T> + Send>,
) -> Result<(), SendError<crate::worker::Command<T>>> {
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<D: AsMimeTypes + Send + 'static>(
&self,
internal: bool,
source_surface: T,
icon_surface: Option<T>,
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<DndDestinationRectangle>) {
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)));
}
}