feat: dnd integration
This commit is contained in:
parent
c3e9e794b9
commit
228288dfdf
11 changed files with 589 additions and 14 deletions
|
|
@ -15,6 +15,7 @@ categories = ["gui"]
|
||||||
raw-window-handle = { version = "0.6", features = ["std"] }
|
raw-window-handle = { version = "0.6", features = ["std"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
mime = { path = "./mime" }
|
mime = { path = "./mime" }
|
||||||
|
dnd = { path = "./dnd" }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
clipboard-win = { version = "5.0", features = ["std"] }
|
clipboard-win = { version = "5.0", features = ["std"] }
|
||||||
|
|
@ -31,9 +32,4 @@ rand = "0.8"
|
||||||
winit = "0.29"
|
winit = "0.29"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = ["dnd", "macos", "mime", "dnd", "wayland", "x11"]
|
||||||
"macos",
|
|
||||||
"mime",
|
|
||||||
"wayland",
|
|
||||||
"x11",
|
|
||||||
]
|
|
||||||
|
|
|
||||||
16
dnd/Cargo.toml
Normal file
16
dnd/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "dnd"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mime = { path = "../mime" }
|
||||||
|
bitflags = "2.5.0"
|
||||||
|
|
||||||
|
[target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten", target_os="ios", target_os="redox"))))'.dependencies]
|
||||||
|
smithay-clipboard = { git = "https://github.com/pop-os/smithay-clipboard", branch = "dnd", features = [
|
||||||
|
"dnd",
|
||||||
|
] }
|
||||||
|
sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = [
|
||||||
|
"calloop",
|
||||||
|
] }
|
||||||
126
dnd/src/lib.rs
Normal file
126
dnd/src/lib.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
ffi::c_void,
|
||||||
|
sync::{mpsc::SendError, Arc},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
unix,
|
||||||
|
not(any(
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "emscripten",
|
||||||
|
target_os = "redox"
|
||||||
|
))
|
||||||
|
))]
|
||||||
|
#[path = "platform/linux.rs"]
|
||||||
|
pub mod platform;
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
// Attributes can be applied to flags types
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct DndAction: u32 {
|
||||||
|
const Copy = 0b00000001;
|
||||||
|
const Move = 0b00000010;
|
||||||
|
const Ask = 0b00000100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<String>),
|
||||||
|
/// DnD Dropped. The operation is still ongoing until receiving a
|
||||||
|
/// [`SourceEvent::Finished`] event.
|
||||||
|
Dropped,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OfferEvent<T> {
|
||||||
|
Enter {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
mime_types: Vec<String>,
|
||||||
|
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_action`] should then be called before data
|
||||||
|
/// can be requested and th DnD operation can be finished.
|
||||||
|
SelectedAction(DndAction),
|
||||||
|
Data {
|
||||||
|
data: Vec<u8>,
|
||||||
|
mime_type: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A rectangle with a logical location and size relative to a [`DndSurface`]
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct Rectangle {
|
||||||
|
pub x: f64,
|
||||||
|
pub y: f64,
|
||||||
|
pub width: f64,
|
||||||
|
pub height: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Sender<T> {
|
||||||
|
/// Send an event in the channel
|
||||||
|
fn send(&self, t: DndEvent<T>) -> Result<(), SendError<DndEvent<T>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RawSurface {
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// returned pointer must be a valid pointer to the underlying surface, and it must
|
||||||
|
/// remain valid for as long as `RawSurface` object is alive.
|
||||||
|
unsafe fn get_ptr(&mut self) -> *mut c_void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A rectangle with a logical location and size relative to a [`DndSurface`]
|
||||||
|
#[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<Cow<'static, str>>,
|
||||||
|
/// Accepted actions in this rectangle
|
||||||
|
pub actions: DndAction,
|
||||||
|
/// Prefered action in this rectangle
|
||||||
|
pub preferred: DndAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DndSurface(pub Arc<Box<dyn RawSurface + 'static + Send + Sync>>);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DataWrapper<T>(pub T);
|
||||||
88
dnd/src/platform/linux.rs
Normal file
88
dnd/src/platform/linux.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
use std::{borrow::Cow, ffi::c_void, sync::Arc};
|
||||||
|
|
||||||
|
use crate::{DataWrapper, DndAction, DndSurface};
|
||||||
|
use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
|
||||||
|
|
||||||
|
impl<
|
||||||
|
T: mime::AllowedMimeTypes
|
||||||
|
+ std::convert::TryFrom<(std::vec::Vec<u8>, String)>,
|
||||||
|
> AllowedMimeTypes for DataWrapper<T>
|
||||||
|
{
|
||||||
|
fn allowed() -> Cow<'static, [MimeType]> {
|
||||||
|
T::allowed()
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| MimeType::from(Cow::Owned(s.to_string())))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TryFrom<(Vec<u8>, String)>> TryFrom<(Vec<u8>, MimeType)>
|
||||||
|
for DataWrapper<T>
|
||||||
|
{
|
||||||
|
type Error = T::Error;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
(data, mime): (Vec<u8>, MimeType),
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
T::try_from((data, mime.to_string())).map(|d| DataWrapper(d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: mime::AsMimeTypes> AsMimeTypes for DataWrapper<T> {
|
||||||
|
fn available(&self) -> Cow<'static, [MimeType]> {
|
||||||
|
self.0
|
||||||
|
.available()
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| MimeType::from(Cow::Owned(m.to_string())))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_bytes(&self, mime_type: &MimeType) -> Option<Cow<'static, [u8]>> {
|
||||||
|
self.0.as_bytes(mime_type.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl smithay_clipboard::dnd::RawSurface for DndSurface {
|
||||||
|
unsafe fn get_ptr(&mut self) -> *mut c_void {
|
||||||
|
// XXX won't panic because this is only called once before it could be cloned
|
||||||
|
Arc::get_mut(&mut self.0).unwrap().get_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sctk::reexports::client::protocol::wl_data_device_manager::DndAction>
|
||||||
|
for DndAction
|
||||||
|
{
|
||||||
|
fn from(
|
||||||
|
action: sctk::reexports::client::protocol::wl_data_device_manager::DndAction,
|
||||||
|
) -> Self {
|
||||||
|
let mut a = DndAction::empty();
|
||||||
|
if action.contains(sctk::reexports::client::protocol::wl_data_device_manager::DndAction::Copy) {
|
||||||
|
a |= DndAction::Copy;
|
||||||
|
}
|
||||||
|
if action.contains(sctk::reexports::client::protocol::wl_data_device_manager::DndAction::Move) {
|
||||||
|
a |= DndAction::Move;
|
||||||
|
}
|
||||||
|
if action.contains(sctk::reexports::client::protocol::wl_data_device_manager::DndAction::Ask) {
|
||||||
|
a |= DndAction::Ask;
|
||||||
|
}
|
||||||
|
a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DndAction>
|
||||||
|
for sctk::reexports::client::protocol::wl_data_device_manager::DndAction
|
||||||
|
{
|
||||||
|
fn from(action: DndAction) -> Self {
|
||||||
|
let mut a = sctk::reexports::client::protocol::wl_data_device_manager::DndAction::empty();
|
||||||
|
if action.contains(DndAction::Copy) {
|
||||||
|
a |= sctk::reexports::client::protocol::wl_data_device_manager::DndAction::Copy;
|
||||||
|
}
|
||||||
|
if action.contains(DndAction::Move) {
|
||||||
|
a |= sctk::reexports::client::protocol::wl_data_device_manager::DndAction::Move;
|
||||||
|
}
|
||||||
|
if action.contains(DndAction::Ask) {
|
||||||
|
a |= sctk::reexports::client::protocol::wl_data_device_manager::DndAction::Ask;
|
||||||
|
}
|
||||||
|
a
|
||||||
|
}
|
||||||
|
}
|
||||||
0
dnd/src/platform/mod.rs
Normal file
0
dnd/src/platform/mod.rs
Normal file
|
|
@ -6,6 +6,4 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten", target_os="ios", target_os="redox"))))'.dependencies]
|
[target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten", target_os="ios", target_os="redox"))))'.dependencies]
|
||||||
smithay-clipboard = { git = "https://github.com/pop-os/smithay-clipboard", tag = "pop-mime-types" }
|
smithay-clipboard = { git = "https://github.com/pop-os/smithay-clipboard", branch = "dnd" }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
102
src/dnd/mod.rs
Normal file
102
src/dnd/mod.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use ::dnd::{DndAction, DndDestinationRectangle, Sender};
|
||||||
|
use dnd::DndSurface;
|
||||||
|
use mime::{AllowedMimeTypes, AsMimeTypes};
|
||||||
|
|
||||||
|
pub trait DndProvider {
|
||||||
|
/// Set up DnD operations for the Clipboard
|
||||||
|
fn init_dnd(
|
||||||
|
&self,
|
||||||
|
_tx: Box<dyn dnd::Sender<DndSurface> + Send + Sync + 'static>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a DnD operation on the given surface with some data
|
||||||
|
fn start_dnd<D: AsMimeTypes + Send + 'static>(
|
||||||
|
&self,
|
||||||
|
_internal: bool,
|
||||||
|
_source_surface: DndSurface,
|
||||||
|
_icon_surface: Option<DndSurface>,
|
||||||
|
_content: D,
|
||||||
|
_actions: DndAction,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End the current DnD operation, if there is one
|
||||||
|
fn end_dnd(&self) {}
|
||||||
|
|
||||||
|
/// Register a surface for receiving DnD offers
|
||||||
|
/// Rectangles should be provided in order of decreasing priority.
|
||||||
|
/// This method can be called multiple time for a single surface if the
|
||||||
|
/// rectangles change.
|
||||||
|
fn register_dnd_destination(
|
||||||
|
&self,
|
||||||
|
_surface: DndSurface,
|
||||||
|
_rectangles: Vec<DndDestinationRectangle>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the final action after presenting the user with a choice
|
||||||
|
fn set_action(&self, _action: DndAction) {}
|
||||||
|
|
||||||
|
/// Peek at the contents of a DnD offer
|
||||||
|
fn peek_offer<D: AllowedMimeTypes + 'static>(
|
||||||
|
&self,
|
||||||
|
_mime_type: Cow<'static, str>,
|
||||||
|
) -> std::io::Result<D> {
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"DnD not supported",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: DndProvider> DndProvider for crate::PlatformClipboard<C> {
|
||||||
|
fn init_dnd(
|
||||||
|
&self,
|
||||||
|
tx: Box<dyn Sender<DndSurface> + Send + Sync + 'static>,
|
||||||
|
) {
|
||||||
|
self.raw.init_dnd(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_dnd<D: AsMimeTypes + Send + 'static>(
|
||||||
|
&self,
|
||||||
|
internal: bool,
|
||||||
|
source_surface: DndSurface,
|
||||||
|
icon_surface: Option<DndSurface>,
|
||||||
|
content: D,
|
||||||
|
actions: DndAction,
|
||||||
|
) {
|
||||||
|
self.raw.start_dnd(
|
||||||
|
internal,
|
||||||
|
source_surface,
|
||||||
|
icon_surface,
|
||||||
|
content,
|
||||||
|
actions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_dnd(&self) {
|
||||||
|
self.raw.end_dnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_dnd_destination(
|
||||||
|
&self,
|
||||||
|
surface: DndSurface,
|
||||||
|
rectangles: Vec<DndDestinationRectangle>,
|
||||||
|
) {
|
||||||
|
self.raw.register_dnd_destination(surface, rectangles);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_action(&self, action: DndAction) {
|
||||||
|
self.raw.set_action(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_offer<D: AllowedMimeTypes + 'static>(
|
||||||
|
&self,
|
||||||
|
mime_type: Cow<'static, str>,
|
||||||
|
) -> std::io::Result<D> {
|
||||||
|
self.raw.peek_offer::<D>(mime_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,8 @@ mod platform;
|
||||||
#[path = "platform/dummy.rs"]
|
#[path = "platform/dummy.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
||||||
|
mod dnd;
|
||||||
|
|
||||||
use mime::ClipboardStoreData;
|
use mime::ClipboardStoreData;
|
||||||
use raw_window_handle::HasDisplayHandle;
|
use raw_window_handle::HasDisplayHandle;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
dnd::DndProvider,
|
||||||
mime::{ClipboardLoadData, ClipboardStoreData},
|
mime::{ClipboardLoadData, ClipboardStoreData},
|
||||||
ClipboardProvider,
|
ClipboardProvider,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use dnd::{DndAction, DndDestinationRectangle, DndSurface};
|
||||||
|
use mime::{AllowedMimeTypes, AsMimeTypes};
|
||||||
use raw_window_handle::{HasDisplayHandle, RawDisplayHandle};
|
use raw_window_handle::{HasDisplayHandle, RawDisplayHandle};
|
||||||
use std::error::Error;
|
use std::{borrow::Cow, error::Error, sync::Arc};
|
||||||
|
use wayland::DndSender;
|
||||||
|
|
||||||
pub use clipboard_wayland as wayland;
|
pub use clipboard_wayland as wayland;
|
||||||
pub use clipboard_x11 as x11;
|
pub use clipboard_x11 as x11;
|
||||||
|
|
@ -125,6 +129,78 @@ impl ClipboardProvider for Clipboard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DndProvider for Clipboard {
|
||||||
|
fn init_dnd(
|
||||||
|
&self,
|
||||||
|
tx: Box<dyn dnd::Sender<DndSurface> + Send + Sync + 'static>,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
Clipboard::Wayland(c) => c.init_dnd(DndSender(Arc::new(tx))),
|
||||||
|
Clipboard::X11(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_dnd<D: AsMimeTypes + Send + 'static>(
|
||||||
|
&self,
|
||||||
|
internal: bool,
|
||||||
|
source_surface: DndSurface,
|
||||||
|
icon_surface: Option<DndSurface>,
|
||||||
|
content: D,
|
||||||
|
actions: DndAction,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
Clipboard::Wayland(c) => c.start_dnd(
|
||||||
|
internal,
|
||||||
|
source_surface,
|
||||||
|
icon_surface,
|
||||||
|
content,
|
||||||
|
actions,
|
||||||
|
),
|
||||||
|
Clipboard::X11(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_dnd(&self) {
|
||||||
|
match self {
|
||||||
|
Clipboard::Wayland(c) => c.end_dnd(),
|
||||||
|
Clipboard::X11(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_dnd_destination(
|
||||||
|
&self,
|
||||||
|
surface: DndSurface,
|
||||||
|
rectangles: Vec<DndDestinationRectangle>,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
Clipboard::Wayland(c) => {
|
||||||
|
c.register_dnd_destination(surface, rectangles)
|
||||||
|
}
|
||||||
|
Clipboard::X11(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_action(&self, action: DndAction) {
|
||||||
|
match self {
|
||||||
|
Clipboard::Wayland(c) => c.set_action(action),
|
||||||
|
Clipboard::X11(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_offer<D: AllowedMimeTypes + 'static>(
|
||||||
|
&self,
|
||||||
|
mime_type: Cow<'static, str>,
|
||||||
|
) -> std::io::Result<D> {
|
||||||
|
match self {
|
||||||
|
Clipboard::Wayland(c) => c.peek_offer::<D>(mime_type),
|
||||||
|
Clipboard::X11(_) => Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"DnD not supported",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub unsafe fn connect<W: HasDisplayHandle>(
|
pub unsafe fn connect<W: HasDisplayHandle>(
|
||||||
window: &W,
|
window: &W,
|
||||||
) -> Result<Clipboard, Box<dyn Error>> {
|
) -> Result<Clipboard, Box<dyn Error>> {
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,9 @@ documentation = "https://docs.rs/clipboard_wayland"
|
||||||
keywords = ["clipboard", "wayland"]
|
keywords = ["clipboard", "wayland"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
smithay-clipboard = { git = "https://github.com/pop-os/smithay-clipboard", tag = "pop-mime-types" }
|
|
||||||
|
smithay-clipboard = { git = "https://github.com/pop-os/smithay-clipboard", branch = "dnd", features = [
|
||||||
|
"dnd",
|
||||||
|
] }
|
||||||
mime = { path = "../mime" }
|
mime = { path = "../mime" }
|
||||||
|
dnd = { path = "../dnd" }
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,96 @@ use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
error::Error,
|
error::Error,
|
||||||
ffi::c_void,
|
ffi::c_void,
|
||||||
sync::{Arc, Mutex},
|
sync::{mpsc::SendError, Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use dnd::{
|
||||||
|
DataWrapper, DndAction, DndDestinationRectangle, DndSurface, Sender,
|
||||||
|
};
|
||||||
|
use smithay_clipboard::dnd::Rectangle;
|
||||||
pub use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
|
pub use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DndSender(
|
||||||
|
pub Arc<Box<dyn Sender<DndSurface> + 'static + Send + Sync>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl smithay_clipboard::dnd::Sender<DndSurface> for DndSender {
|
||||||
|
fn send(
|
||||||
|
&self,
|
||||||
|
event: smithay_clipboard::dnd::DndEvent<DndSurface>,
|
||||||
|
) -> Result<(), SendError<smithay_clipboard::dnd::DndEvent<DndSurface>>>
|
||||||
|
{
|
||||||
|
_ = self.0.send(match event {
|
||||||
|
smithay_clipboard::dnd::DndEvent::Offer(id, e) => dnd::DndEvent::Offer(
|
||||||
|
id,
|
||||||
|
match e {
|
||||||
|
smithay_clipboard::dnd::OfferEvent::Enter {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
mime_types,
|
||||||
|
surface,
|
||||||
|
} => dnd::OfferEvent::Enter {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
mime_types: mime_types
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| m.to_string())
|
||||||
|
.collect(),
|
||||||
|
surface,
|
||||||
|
},
|
||||||
|
smithay_clipboard::dnd::OfferEvent::Motion { x, y } => {
|
||||||
|
dnd::OfferEvent::Motion { x, y }
|
||||||
|
}
|
||||||
|
smithay_clipboard::dnd::OfferEvent::LeaveDestination => {
|
||||||
|
dnd::OfferEvent::LeaveDestination
|
||||||
|
}
|
||||||
|
smithay_clipboard::dnd::OfferEvent::Leave => {
|
||||||
|
dnd::OfferEvent::Leave
|
||||||
|
}
|
||||||
|
smithay_clipboard::dnd::OfferEvent::Drop => {
|
||||||
|
dnd::OfferEvent::Drop
|
||||||
|
}
|
||||||
|
smithay_clipboard::dnd::OfferEvent::SelectedAction(
|
||||||
|
action,
|
||||||
|
) => dnd::OfferEvent::SelectedAction(action.into()),
|
||||||
|
smithay_clipboard::dnd::OfferEvent::Data {
|
||||||
|
data,
|
||||||
|
mime_type,
|
||||||
|
} => dnd::OfferEvent::Data {
|
||||||
|
data,
|
||||||
|
mime_type: mime_type.to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
smithay_clipboard::dnd::DndEvent::Source(e) => match e {
|
||||||
|
smithay_clipboard::dnd::SourceEvent::Finished => {
|
||||||
|
dnd::DndEvent::Source(dnd::SourceEvent::Finished)
|
||||||
|
}
|
||||||
|
smithay_clipboard::dnd::SourceEvent::Cancelled => {
|
||||||
|
dnd::DndEvent::Source(dnd::SourceEvent::Cancelled)
|
||||||
|
}
|
||||||
|
smithay_clipboard::dnd::SourceEvent::Action(action) => {
|
||||||
|
dnd::DndEvent::Source(dnd::SourceEvent::Action(
|
||||||
|
action.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
smithay_clipboard::dnd::SourceEvent::Mime(mime) => {
|
||||||
|
dnd::DndEvent::Source(dnd::SourceEvent::Mime(
|
||||||
|
mime.map(|m| m.to_string()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
smithay_clipboard::dnd::SourceEvent::Dropped => {
|
||||||
|
dnd::DndEvent::Source(dnd::SourceEvent::Dropped)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Clipboard {
|
pub struct Clipboard {
|
||||||
context: Arc<Mutex<smithay_clipboard::Clipboard>>,
|
context: Arc<Mutex<smithay_clipboard::Clipboard<DndSurface>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clipboard {
|
impl Clipboard {
|
||||||
|
|
@ -120,4 +203,88 @@ impl Clipboard {
|
||||||
)
|
)
|
||||||
.map(|(d, m)| (d, m.to_string()))?)
|
.map(|(d, m)| (d, m.to_string()))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init_dnd(&self, tx: DndSender) {
|
||||||
|
_ = self.context.lock().unwrap().init_dnd(Box::new(tx));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a DnD operation on the given surface with some data
|
||||||
|
pub fn start_dnd<D: mime::AsMimeTypes + Send + 'static>(
|
||||||
|
&self,
|
||||||
|
internal: bool,
|
||||||
|
source_surface: DndSurface,
|
||||||
|
icon_surface: Option<DndSurface>,
|
||||||
|
content: D,
|
||||||
|
actions: DndAction,
|
||||||
|
) {
|
||||||
|
_ = self.context.lock().unwrap().start_dnd(
|
||||||
|
internal,
|
||||||
|
source_surface,
|
||||||
|
icon_surface,
|
||||||
|
DataWrapper(content),
|
||||||
|
actions.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End the current DnD operation, if there is one
|
||||||
|
pub fn end_dnd(&self) {
|
||||||
|
_ = self.context.lock().unwrap().end_dnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a surface for receiving DnD offers
|
||||||
|
/// Rectangles should be provided in order of decreasing priority.
|
||||||
|
/// This method can be called multiple time for a single surface if the
|
||||||
|
/// rectangles change.
|
||||||
|
pub fn register_dnd_destination(
|
||||||
|
&self,
|
||||||
|
surface: DndSurface,
|
||||||
|
rectangles: Vec<DndDestinationRectangle>,
|
||||||
|
) {
|
||||||
|
_ = self.context.lock().unwrap().register_dnd_destination(
|
||||||
|
surface,
|
||||||
|
rectangles
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| RectangleWrapper(r).into())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the final action after presenting the user with a choice
|
||||||
|
pub fn set_action(&self, action: DndAction) {
|
||||||
|
self.context.lock().unwrap().set_action(action.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peek at the contents of a DnD offer
|
||||||
|
pub fn peek_offer<D: mime::AllowedMimeTypes + 'static>(
|
||||||
|
&self,
|
||||||
|
mime_type: Cow<'static, str>,
|
||||||
|
) -> std::io::Result<D> {
|
||||||
|
let d = self
|
||||||
|
.context
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.peek_offer::<DataWrapper<D>>(mime_type.into());
|
||||||
|
d.map(|d| d.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RectangleWrapper(pub DndDestinationRectangle);
|
||||||
|
|
||||||
|
impl From<RectangleWrapper>
|
||||||
|
for smithay_clipboard::dnd::DndDestinationRectangle
|
||||||
|
{
|
||||||
|
fn from(RectangleWrapper(d): RectangleWrapper) -> Self {
|
||||||
|
smithay_clipboard::dnd::DndDestinationRectangle {
|
||||||
|
id: d.id,
|
||||||
|
rectangle: Rectangle {
|
||||||
|
x: d.rectangle.x,
|
||||||
|
y: d.rectangle.y,
|
||||||
|
width: d.rectangle.width,
|
||||||
|
height: d.rectangle.height,
|
||||||
|
},
|
||||||
|
mime_types: d.mime_types.into_iter().map(MimeType::from).collect(),
|
||||||
|
actions: d.actions.into(),
|
||||||
|
preferred: d.preferred.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue