wip: dnd offer handling
This commit is contained in:
parent
10e534c1be
commit
1063256706
7 changed files with 591 additions and 92 deletions
|
|
@ -12,12 +12,12 @@ rust-version = "1.65.0"
|
|||
|
||||
[dependencies]
|
||||
libc = "0.2.149"
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"] }
|
||||
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"] }
|
||||
sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = ["calloop"] }
|
||||
wayland-backend = { version = "0.3.3", default_features = false, features = ["client_system"] }
|
||||
|
||||
[dev-dependencies]
|
||||
dirs = "5.0.1"
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop", "xkbcommon"] }
|
||||
sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", default-features = false, features = ["calloop", "xkbcommon"] }
|
||||
thiserror = "1.0.57"
|
||||
url = "2.5.0"
|
||||
|
||||
|
|
|
|||
|
|
@ -11,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_data_device_manager::DndAction;
|
||||
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};
|
||||
|
|
@ -26,7 +27,8 @@ use sctk::{
|
|||
delegate_compositor, delegate_keyboard, delegate_output, delegate_registry, delegate_seat,
|
||||
delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers,
|
||||
};
|
||||
use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
|
||||
use smithay_clipboard::dnd::{DndDestinationRectangle, Rectangle};
|
||||
use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType, ALLOWED_TEXT_MIME_TYPES};
|
||||
use smithay_clipboard::{Clipboard, SimpleClipboard};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
|
@ -66,12 +68,22 @@ fn main() {
|
|||
|
||||
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));
|
||||
clipboard.init_dnd(Box::new(tx)).expect("Failed to set up DnD");
|
||||
|
||||
event_loop.handle().insert_source(rx, |event, _, state| {
|
||||
_ = event_loop.handle().insert_source(rx, |event, _, _state| {
|
||||
dbg!(event);
|
||||
});
|
||||
|
||||
clipboard.register_dnd_destination(window.wl_surface().clone(), vec![
|
||||
DndDestinationRectangle {
|
||||
id: 0,
|
||||
rectangle: Rectangle { x: 0., y: 0., width: 256., height: 256. },
|
||||
mime_types: ALLOWED_TEXT_MIME_TYPES.iter().map(|m| Cow::from(m.to_string())).collect(),
|
||||
actions: DndAction::all(),
|
||||
preferred: DndAction::Copy,
|
||||
},
|
||||
]);
|
||||
|
||||
let mut simple_window = SimpleWindow {
|
||||
registry_state: RegistryState::new(&globals),
|
||||
seat_state: SeatState::new(&globals, &queue_handle),
|
||||
|
|
@ -367,6 +379,7 @@ impl KeyboardHandler for SimpleWindow {
|
|||
_: &wl_keyboard::WlKeyboard,
|
||||
_serial: u32,
|
||||
_modifiers: Modifiers,
|
||||
_: u32,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
134
src/dnd/mod.rs
134
src/dnd/mod.rs
|
|
@ -1,16 +1,22 @@
|
|||
use std::borrow::Cow;
|
||||
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;
|
||||
use crate::Clipboard;
|
||||
|
||||
pub mod state;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DndSurface<T> {
|
||||
pub surface: WlSurface,
|
||||
pub(crate) surface: WlSurface,
|
||||
pub s: T,
|
||||
}
|
||||
|
||||
|
|
@ -46,9 +52,100 @@ pub trait Sender<T> {
|
|||
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<String>),
|
||||
/// 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<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_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: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// 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<Cow<'static, str>>,
|
||||
/// 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 {
|
||||
source: DndSurface<T>,
|
||||
icon: Option<DndSurface<T>>,
|
||||
content: Box<dyn AsMimeTypes + Send>,
|
||||
},
|
||||
/// Set the DnD action chosen by the user.
|
||||
SetAction(DndAction),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DndEvent<T> {
|
||||
Test(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>> {
|
||||
|
|
@ -63,21 +160,44 @@ impl<T> Sender<T> for calloop::channel::SyncSender<DndEvent<T>> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Clipboard<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::InitDnD(tx))
|
||||
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: RawSurface>(&self, s: D) {
|
||||
let s = DndSurface::new(s, &self.connection).unwrap();
|
||||
dbg!(&s.surface);
|
||||
pub fn start_dnd<D: AsMimeTypes + Send + 'static>(
|
||||
&self,
|
||||
source_surface: T,
|
||||
icon_surface: Option<T>,
|
||||
content: D,
|
||||
) {
|
||||
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 {
|
||||
source,
|
||||
icon,
|
||||
content: Box::new(content),
|
||||
}));
|
||||
}
|
||||
|
||||
/// End the current DnD operation, if there is one
|
||||
pub fn end_dnd() {}
|
||||
|
||||
/// 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) {}
|
||||
}
|
||||
|
|
|
|||
315
src/dnd/state.rs
Normal file
315
src/dnd/state.rs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Error, ErrorKind, Read};
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::data_device_manager::data_offer::DragOffer;
|
||||
use sctk::data_device_manager::data_source::DragSource;
|
||||
use sctk::reexports::calloop::PostAction;
|
||||
use sctk::reexports::client::protocol::wl_data_device::WlDataDevice;
|
||||
use sctk::reexports::client::protocol::wl_data_device_manager::DndAction;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Proxy;
|
||||
use wayland_backend::client::ObjectId;
|
||||
|
||||
use crate::mime::{AsMimeTypes, MimeType};
|
||||
use crate::state::{set_non_blocking, State, Target};
|
||||
use crate::text::Text;
|
||||
|
||||
use super::{DndDestinationRectangle, DndEvent, DndRequest, DndSurface, OfferEvent};
|
||||
|
||||
pub(crate) struct DndState<T> {
|
||||
pub(crate) sender: Option<Box<dyn crate::dnd::Sender<T>>>,
|
||||
destinations: HashMap<ObjectId, (DndSurface<T>, Vec<DndDestinationRectangle>)>,
|
||||
dnd_sources: Option<DragSource>,
|
||||
active_surface: Option<(DndSurface<T>, Option<DndDestinationRectangle>)>,
|
||||
source_actions: DndAction,
|
||||
selected_action: DndAction,
|
||||
selected_mime: Option<String>,
|
||||
pub(crate) source_content: Box<dyn AsMimeTypes>,
|
||||
pub(crate) source_mime_types: Rc<Cow<'static, [MimeType]>>,
|
||||
}
|
||||
|
||||
impl<T> Default for DndState<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sender: Default::default(),
|
||||
destinations: Default::default(),
|
||||
dnd_sources: Default::default(),
|
||||
active_surface: None,
|
||||
source_actions: DndAction::empty(),
|
||||
selected_action: DndAction::empty(),
|
||||
selected_mime: None,
|
||||
source_content: Box::new(Text(String::new())),
|
||||
source_mime_types: Rc::new(Cow::Owned(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DndState<T> {
|
||||
pub(crate) fn selected_action(&mut self, a: DndAction) {
|
||||
self.selected_action = a;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> State<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
pub fn update_active_surface(
|
||||
&mut self,
|
||||
surface: &WlSurface,
|
||||
x: f64,
|
||||
y: f64,
|
||||
dnd_state: Option<&DragOffer>,
|
||||
) {
|
||||
let had_dest = self
|
||||
.dnd_state
|
||||
.active_surface
|
||||
.as_ref()
|
||||
.map(|(_, d)| d.as_ref().map(|d| d.id))
|
||||
.unwrap_or_default();
|
||||
self.dnd_state.active_surface =
|
||||
self.dnd_state.destinations.get(&surface.id()).map(|(s, dests)| {
|
||||
let Some((dest, mime, actions)) = dests.iter().find_map(|r| {
|
||||
let actions = dnd_state.as_ref().map(|s| {
|
||||
(
|
||||
s.source_actions.intersection(r.actions),
|
||||
s.source_actions.intersection(r.preferred),
|
||||
)
|
||||
});
|
||||
let mime = dnd_state.as_ref().and_then(|dnd_state| {
|
||||
r.mime_types.iter().find(|m| {
|
||||
dnd_state.with_mime_types(|mimes| mimes.iter().any(|a| &a == m))
|
||||
})
|
||||
});
|
||||
|
||||
dbg!(actions);
|
||||
dbg!(mime);
|
||||
|
||||
(r.rectangle.contains(x, y)
|
||||
&& (r.mime_types.is_empty() || mime.is_some())
|
||||
&& (r.actions.is_all()
|
||||
|| dnd_state
|
||||
.as_ref()
|
||||
.map(|dnd_state| dnd_state.source_actions.intersects(r.actions))
|
||||
.unwrap_or(true)))
|
||||
.then(|| (r.clone(), mime, actions))
|
||||
}) else {
|
||||
if let Some(old_id) = had_dest {
|
||||
if let Some(dnd_state) = dnd_state.as_ref() {
|
||||
if let Some(tx) = self.dnd_state.sender.as_ref() {
|
||||
_ = tx.send(DndEvent::Offer(
|
||||
Some(old_id),
|
||||
super::OfferEvent::LeaveDestination,
|
||||
));
|
||||
}
|
||||
dnd_state.set_actions(DndAction::empty(), DndAction::empty());
|
||||
dnd_state.accept_mime_type(dnd_state.serial, None);
|
||||
self.dnd_state.selected_action = DndAction::empty();
|
||||
self.dnd_state.selected_mime = None;
|
||||
}
|
||||
}
|
||||
return (s.clone(), None);
|
||||
};
|
||||
if let (Some((action, preferred_action)), Some(mime_type), Some(dnd_state)) =
|
||||
(actions, mime, dnd_state.as_ref())
|
||||
{
|
||||
dnd_state.set_actions(action, preferred_action);
|
||||
self.dnd_state.selected_mime = Some(mime_type.to_string());
|
||||
dnd_state.accept_mime_type(dnd_state.serial, Some(mime_type.to_string()))
|
||||
}
|
||||
(s.clone(), Some(dest))
|
||||
});
|
||||
}
|
||||
|
||||
fn cur_id(&self) -> Option<u128> {
|
||||
self.dnd_state.active_surface.as_ref().and_then(|(_, rect)| rect.as_ref().map(|r| r.id))
|
||||
}
|
||||
|
||||
pub(crate) fn offer_drop(&mut self, wl_data_device: &WlDataDevice) {
|
||||
let Some(tx) = self.dnd_state.sender.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let id = self.cur_id();
|
||||
_ = tx.send(DndEvent::Offer(id, super::OfferEvent::Drop));
|
||||
|
||||
let Some(data_device) = self
|
||||
.seats
|
||||
.iter()
|
||||
.find_map(|(_, s)| s.data_device.as_ref().filter(|dev| dev.inner() == wl_data_device))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(dnd_state) = data_device.data().drag_offer() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if self.dnd_state.selected_action == DndAction::Ask {
|
||||
_ = tx.send(DndEvent::Offer(
|
||||
id,
|
||||
super::OfferEvent::SelectedAction(self.dnd_state.selected_action),
|
||||
));
|
||||
return;
|
||||
} else if self.dnd_state.selected_action.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(mime) = self.dnd_state.selected_mime.take() else {
|
||||
dnd_state.accept_mime_type(dnd_state.serial, None);
|
||||
return;
|
||||
};
|
||||
|
||||
dnd_state.set_actions(self.dnd_state.selected_action, self.dnd_state.selected_action);
|
||||
dnd_state.accept_mime_type(dnd_state.serial, Some(mime.clone()));
|
||||
|
||||
_ = self.load_dnd(MimeType::Other(mime.into()));
|
||||
}
|
||||
|
||||
pub(crate) fn offer_enter(
|
||||
&mut self,
|
||||
x: f64,
|
||||
y: f64,
|
||||
surface: &WlSurface,
|
||||
wl_data_device: &WlDataDevice,
|
||||
) {
|
||||
if self.dnd_state.sender.is_none() {
|
||||
return;
|
||||
}
|
||||
dbg!(&self.dnd_state.destinations);
|
||||
let Some(data_device) = self
|
||||
.seats
|
||||
.iter()
|
||||
.find_map(|(_, s)| s.data_device.as_ref().filter(|dev| dev.inner() == wl_data_device))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let dnd_state = data_device.data().drag_offer();
|
||||
self.update_active_surface(surface, x, y, dnd_state.as_ref());
|
||||
let Some((surface, id)) = self
|
||||
.dnd_state
|
||||
.active_surface
|
||||
.as_ref()
|
||||
.map(|(s, d)| (s.clone(), d.as_ref().map(|d| d.id)))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(tx) = self.dnd_state.sender.as_ref() else {
|
||||
return;
|
||||
};
|
||||
// TODO accept mime / action
|
||||
_ = tx.send(DndEvent::Offer(id, super::OfferEvent::Enter {
|
||||
x,
|
||||
y,
|
||||
surface: surface.s,
|
||||
mime_types: Vec::new(),
|
||||
}));
|
||||
}
|
||||
|
||||
pub(crate) fn offer_motion(&mut self, x: f64, y: f64, wl_data_device: &WlDataDevice) {
|
||||
let Some((surface, dest)) = self
|
||||
.dnd_state
|
||||
.active_surface
|
||||
.clone()
|
||||
.map(|(s, dest)| (s, dest.filter(|d| d.rectangle.contains(x, y))))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(data_device) = self
|
||||
.seats
|
||||
.iter()
|
||||
.find_map(|(_, s)| s.data_device.as_ref().filter(|dev| dev.inner() == wl_data_device))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let dnd_state = data_device.data().drag_offer();
|
||||
if dest.is_none() {
|
||||
self.update_active_surface(&surface.surface, x, y, dnd_state.as_ref());
|
||||
}
|
||||
let id = self.cur_id();
|
||||
if let Some(tx) = self.dnd_state.sender.as_ref() {
|
||||
_ = tx.send(DndEvent::Offer(id, super::OfferEvent::Motion { x, y }));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn offer_leave(&mut self) {
|
||||
if let Some(tx) = self.dnd_state.sender.as_ref() {
|
||||
self.dnd_state.active_surface = None;
|
||||
self.dnd_state.selected_action = DndAction::empty();
|
||||
self.dnd_state.selected_mime = None;
|
||||
_ = tx.send(DndEvent::Offer(None, super::OfferEvent::Leave))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_dnd_request(&mut self, r: DndRequest<T>) {
|
||||
match r {
|
||||
DndRequest::InitDnD(sender) => self.dnd_state.sender = Some(sender),
|
||||
DndRequest::Surface(s, dests) => {
|
||||
self.dnd_state.destinations.insert(s.surface.id(), (s, dests));
|
||||
},
|
||||
DndRequest::StartDnd { source, icon, content } => {},
|
||||
DndRequest::SetAction(_) => {
|
||||
todo!()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// Load data for the given target.
|
||||
pub fn load_dnd(&mut self, mut mime_type: MimeType) -> std::io::Result<()> {
|
||||
let cur_id = self.cur_id();
|
||||
let latest = self
|
||||
.latest_seat
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::new(ErrorKind::Other, "no events received on any seat"))?;
|
||||
let seat = self
|
||||
.seats
|
||||
.get_mut(latest)
|
||||
.ok_or_else(|| Error::new(ErrorKind::Other, "active seat lost"))?;
|
||||
|
||||
if !seat.has_focus {
|
||||
return Err(Error::new(ErrorKind::Other, "client doesn't have focus"));
|
||||
}
|
||||
let offer = seat
|
||||
.data_device
|
||||
.as_ref()
|
||||
.and_then(|d| d.data().drag_offer())
|
||||
.ok_or_else(|| Error::new(ErrorKind::Other, "offer does not exist."))?;
|
||||
|
||||
let read_pipe = { offer.receive(mime_type.to_string())? };
|
||||
|
||||
// Mark FD as non-blocking so we won't block ourselves.
|
||||
unsafe {
|
||||
set_non_blocking(read_pipe.as_raw_fd())?;
|
||||
}
|
||||
|
||||
let mut reader_buffer = [0; 4096];
|
||||
let mut content = Vec::new();
|
||||
let _ = self.loop_handle.insert_source(read_pipe, move |_, file, state| {
|
||||
let file = unsafe { file.get_mut() };
|
||||
let Some(tx) = state.dnd_state.sender.as_ref() else {
|
||||
return PostAction::Remove;
|
||||
};
|
||||
loop {
|
||||
match file.read(&mut reader_buffer) {
|
||||
Ok(0) => {
|
||||
offer.finish();
|
||||
let _ = tx.send(DndEvent::Offer(cur_id, OfferEvent::Data {
|
||||
data: mem::take(&mut content),
|
||||
mime_type: mem::take(&mut mime_type).to_string(),
|
||||
}));
|
||||
break PostAction::Remove;
|
||||
},
|
||||
Ok(n) => content.extend_from_slice(&reader_buffer[..n]),
|
||||
Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue,
|
||||
Err(_) => {
|
||||
// let _ = state.dnd_state.sender.unwrap().send(Err(err));
|
||||
break PostAction::Remove;
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
21
src/lib.rs
21
src/lib.rs
|
|
@ -11,6 +11,7 @@ use std::sync::mpsc::{self, Receiver};
|
|||
|
||||
use sctk::reexports::calloop::channel::{self, Sender};
|
||||
use sctk::reexports::client::backend::Backend;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Connection;
|
||||
|
||||
#[cfg(feature = "dnd")]
|
||||
|
|
@ -24,7 +25,7 @@ use mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
|
|||
use state::SelectionTarget;
|
||||
use text::Text;
|
||||
|
||||
pub type SimpleClipboard = Clipboard<()>;
|
||||
pub type SimpleClipboard = Clipboard<WlSurface>;
|
||||
|
||||
/// Access to a Wayland clipboard.
|
||||
pub struct Clipboard<T> {
|
||||
|
|
@ -34,7 +35,7 @@ pub struct Clipboard<T> {
|
|||
connection: Connection,
|
||||
}
|
||||
|
||||
impl<T: 'static + Send> Clipboard<T> {
|
||||
impl<T: 'static + Send + Clone> Clipboard<T> {
|
||||
/// Creates new clipboard which will be running on its own thread with its
|
||||
/// own event queue to handle clipboard requests.
|
||||
///
|
||||
|
|
@ -62,7 +63,7 @@ impl<T: 'static + Send> Clipboard<T> {
|
|||
///
|
||||
/// Load the requested type from a clipboard on the last observed seat.
|
||||
pub fn load<D: AllowedMimeTypes + 'static>(&self) -> Result<D> {
|
||||
self.load_inner(SelectionTarget::Clipboard, D::allowed())
|
||||
self.load_inner(Target::Clipboard, D::allowed())
|
||||
}
|
||||
|
||||
/// Load clipboard data.
|
||||
|
|
@ -77,7 +78,7 @@ impl<T: 'static + Send> Clipboard<T> {
|
|||
/// Load the requested type from a primary clipboard on the last observed
|
||||
/// seat.
|
||||
pub fn load_primary<D: AllowedMimeTypes + 'static>(&self) -> Result<D> {
|
||||
self.load_inner(SelectionTarget::Primary, D::allowed())
|
||||
self.load_inner(Target::Primary, D::allowed())
|
||||
}
|
||||
|
||||
/// Load primary clipboard data.
|
||||
|
|
@ -94,7 +95,7 @@ impl<T: 'static + Send> Clipboard<T> {
|
|||
&self,
|
||||
allowed: impl Into<Cow<'static, [MimeType]>>,
|
||||
) -> Result<(Vec<u8>, MimeType)> {
|
||||
self.load_inner(SelectionTarget::Clipboard, allowed)
|
||||
self.load_inner(Target::Clipboard, allowed)
|
||||
}
|
||||
|
||||
/// Load raw primary clipboard data.
|
||||
|
|
@ -104,14 +105,14 @@ impl<T: 'static + Send> Clipboard<T> {
|
|||
&self,
|
||||
allowed: impl Into<Cow<'static, [MimeType]>>,
|
||||
) -> Result<(Vec<u8>, MimeType)> {
|
||||
self.load_inner(SelectionTarget::Primary, allowed)
|
||||
self.load_inner(Target::Primary, allowed)
|
||||
}
|
||||
|
||||
/// Store custom data to a clipboard.
|
||||
///
|
||||
/// Stores data of the provided type to a clipboard on a last observed seat.
|
||||
pub fn store<D: AsMimeTypes + Send + 'static>(&self, data: D) {
|
||||
self.store_inner(data, SelectionTarget::Clipboard);
|
||||
self.store_inner(data, Target::Clipboard);
|
||||
}
|
||||
|
||||
/// Store to a clipboard.
|
||||
|
|
@ -126,7 +127,7 @@ impl<T: 'static + Send> Clipboard<T> {
|
|||
/// Stores data of the provided type to a primary clipboard on a last
|
||||
/// observed seat.
|
||||
pub fn store_primary<D: AsMimeTypes + Send + 'static>(&self, data: D) {
|
||||
self.store_inner(data, SelectionTarget::Primary);
|
||||
self.store_inner(data, Target::Primary);
|
||||
}
|
||||
|
||||
/// Store to a primary clipboard.
|
||||
|
|
@ -138,7 +139,7 @@ impl<T: 'static + Send> Clipboard<T> {
|
|||
|
||||
fn load_inner<D: TryFrom<(Vec<u8>, MimeType)> + 'static>(
|
||||
&self,
|
||||
target: SelectionTarget,
|
||||
target: Target,
|
||||
allowed: impl Into<Cow<'static, [MimeType]>>,
|
||||
) -> Result<D> {
|
||||
let _ = self.request_sender.send(worker::Command::Load(allowed.into(), target));
|
||||
|
|
@ -158,7 +159,7 @@ impl<T: 'static + Send> Clipboard<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn store_inner<D: AsMimeTypes + Send + 'static>(&self, data: D, target: SelectionTarget) {
|
||||
fn store_inner<D: AsMimeTypes + Send + 'static>(&self, data: D, target: Target) {
|
||||
let request = worker::Command::Store(Box::new(data), target);
|
||||
let _ = self.request_sender.send(request);
|
||||
}
|
||||
|
|
|
|||
159
src/state.rs
159
src/state.rs
|
|
@ -9,11 +9,12 @@ use std::sync::mpsc::Sender;
|
|||
|
||||
use sctk::data_device_manager::data_device::{DataDevice, DataDeviceHandler};
|
||||
use sctk::data_device_manager::data_offer::{DataOfferError, DataOfferHandler, DragOffer};
|
||||
use sctk::data_device_manager::data_source::{CopyPasteSource, DataSourceHandler};
|
||||
use sctk::data_device_manager::data_source::{CopyPasteSource, DataSourceHandler, DragSource};
|
||||
use sctk::data_device_manager::{DataDeviceManagerState, WritePipe};
|
||||
use sctk::primary_selection::device::{PrimarySelectionDevice, PrimarySelectionDeviceHandler};
|
||||
use sctk::primary_selection::selection::{PrimarySelectionSource, PrimarySelectionSourceHandler};
|
||||
use sctk::primary_selection::PrimarySelectionManagerState;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::registry::{ProvidesRegistryState, RegistryState};
|
||||
use sctk::seat::pointer::{PointerData, PointerEvent, PointerEventKind, PointerHandler};
|
||||
use sctk::seat::{Capability, SeatHandler, SeatState};
|
||||
|
|
@ -37,6 +38,8 @@ use sctk::reexports::protocols::wp::primary_selection::zv1::client::{
|
|||
};
|
||||
use wayland_backend::client::ObjectId;
|
||||
|
||||
use crate::dnd::state::DndState;
|
||||
use crate::dnd::{DndDestinationRectangle, DndSurface};
|
||||
use crate::mime::{AsMimeTypes, MimeType};
|
||||
use crate::text::Text;
|
||||
|
||||
|
|
@ -49,11 +52,11 @@ pub struct State<T> {
|
|||
registry_state: RegistryState,
|
||||
seat_state: SeatState,
|
||||
|
||||
seats: HashMap<ObjectId, ClipboardSeatState>,
|
||||
pub(crate) seats: HashMap<ObjectId, ClipboardSeatState>,
|
||||
/// The latest seat which got an event.
|
||||
latest_seat: Option<ObjectId>,
|
||||
pub(crate) latest_seat: Option<ObjectId>,
|
||||
|
||||
loop_handle: LoopHandle<'static, Self>,
|
||||
pub(crate) loop_handle: LoopHandle<'static, Self>,
|
||||
queue_handle: QueueHandle<Self>,
|
||||
|
||||
primary_sources: Vec<PrimarySelectionSource>,
|
||||
|
|
@ -64,11 +67,11 @@ pub struct State<T> {
|
|||
data_selection_content: Box<dyn AsMimeTypes>,
|
||||
data_selection_mime_types: Rc<Cow<'static, [MimeType]>>,
|
||||
#[cfg(feature = "dnd")]
|
||||
pub(crate) sender: Option<Box<dyn crate::dnd::Sender<T>>>,
|
||||
pub(crate) dnd_state: crate::dnd::state::DndState<T>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> State<T> {
|
||||
impl<T: 'static + Clone> State<T> {
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
|
|
@ -110,7 +113,7 @@ impl<T: 'static> State<T> {
|
|||
primary_selection_mime_types: Rc::new(Default::default()),
|
||||
data_selection_mime_types: Rc::new(Default::default()),
|
||||
#[cfg(feature = "dnd")]
|
||||
sender: None,
|
||||
dnd_state: DndState::default(),
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
|
@ -118,11 +121,7 @@ impl<T: 'static> State<T> {
|
|||
/// Store selection for the given target.
|
||||
///
|
||||
/// Selection source is only created when `Some(())` is returned.
|
||||
pub fn store_selection(
|
||||
&mut self,
|
||||
ty: SelectionTarget,
|
||||
contents: Box<dyn AsMimeTypes>,
|
||||
) -> Option<()> {
|
||||
pub fn store_selection(&mut self, ty: Target, contents: Box<dyn AsMimeTypes>) -> Option<()> {
|
||||
let latest = self.latest_seat.as_ref()?;
|
||||
let seat = self.seats.get_mut(latest)?;
|
||||
|
||||
|
|
@ -131,7 +130,7 @@ impl<T: 'static> State<T> {
|
|||
}
|
||||
|
||||
match ty {
|
||||
SelectionTarget::Clipboard => {
|
||||
Target::Clipboard => {
|
||||
let mgr = self.data_device_manager_state.as_ref()?;
|
||||
let mime_types = contents.available();
|
||||
self.data_selection_content = contents;
|
||||
|
|
@ -140,7 +139,7 @@ impl<T: 'static> State<T> {
|
|||
source.set_selection(seat.data_device.as_ref().unwrap(), seat.latest_serial);
|
||||
self.data_sources.push(source);
|
||||
},
|
||||
SelectionTarget::Primary => {
|
||||
Target::Primary => {
|
||||
let mgr = self.primary_selection_manager_state.as_ref()?;
|
||||
let mime_types = contents.available();
|
||||
self.primary_selection_content = contents;
|
||||
|
|
@ -149,17 +148,17 @@ impl<T: 'static> State<T> {
|
|||
source.set_selection(seat.primary_device.as_ref().unwrap(), seat.latest_serial);
|
||||
self.primary_sources.push(source);
|
||||
},
|
||||
#[cfg(feature = "dnd")]
|
||||
Target::DnD => {
|
||||
unreachable!()
|
||||
},
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Load selection for the given target.
|
||||
pub fn load_selection(
|
||||
&mut self,
|
||||
ty: SelectionTarget,
|
||||
allowed_mime_types: &[MimeType],
|
||||
) -> Result<()> {
|
||||
/// Load data for the given target.
|
||||
pub fn load(&mut self, ty: Target, allowed_mime_types: &[MimeType]) -> Result<()> {
|
||||
let latest = self
|
||||
.latest_seat
|
||||
.as_ref()
|
||||
|
|
@ -174,7 +173,7 @@ impl<T: 'static> State<T> {
|
|||
}
|
||||
|
||||
let (read_pipe, mut mime_type) = match ty {
|
||||
SelectionTarget::Clipboard => {
|
||||
Target::Clipboard => {
|
||||
let selection = seat
|
||||
.data_device
|
||||
.as_ref()
|
||||
|
|
@ -197,7 +196,7 @@ impl<T: 'static> State<T> {
|
|||
mime_type,
|
||||
)
|
||||
},
|
||||
SelectionTarget::Primary => {
|
||||
Target::Primary => {
|
||||
let selection = seat
|
||||
.primary_device
|
||||
.as_ref()
|
||||
|
|
@ -212,6 +211,21 @@ impl<T: 'static> State<T> {
|
|||
|
||||
(selection.receive(mime_type.to_string())?, mime_type)
|
||||
},
|
||||
#[cfg(feature = "dnd")]
|
||||
Target::DnD => {
|
||||
let offer = seat
|
||||
.data_device
|
||||
.as_ref()
|
||||
.and_then(|d| d.data().drag_offer())
|
||||
.ok_or_else(|| Error::new(ErrorKind::Other, "offer does not exist."))?;
|
||||
let Some(mime) = allowed_mime_types.get(0) else {
|
||||
return Err(Error::new(
|
||||
ErrorKind::NotFound,
|
||||
"supported mime-type is not found",
|
||||
));
|
||||
};
|
||||
(offer.receive(mime.to_string())?, mime.clone())
|
||||
},
|
||||
};
|
||||
|
||||
// Mark FD as non-blocking so we won't block ourselves.
|
||||
|
|
@ -244,10 +258,12 @@ impl<T: 'static> State<T> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn send_request(&mut self, ty: SelectionTarget, write_pipe: WritePipe, mime: String) {
|
||||
fn send_request(&mut self, ty: Target, write_pipe: WritePipe, mime: String) {
|
||||
let Some(mime_type) = MimeType::find_allowed(&[mime], match ty {
|
||||
SelectionTarget::Clipboard => &self.data_selection_mime_types,
|
||||
SelectionTarget::Primary => &self.primary_selection_mime_types,
|
||||
Target::Clipboard => &self.data_selection_mime_types,
|
||||
Target::Primary => &self.primary_selection_mime_types,
|
||||
#[cfg(feature = "dnd")]
|
||||
Target::DnD => &self.dnd_state.source_mime_types,
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -262,8 +278,10 @@ impl<T: 'static> State<T> {
|
|||
// Don't access the content on the state directly, since it could change during
|
||||
// the send.
|
||||
let contents = match ty {
|
||||
SelectionTarget::Clipboard => self.data_selection_content.as_bytes(&mime_type),
|
||||
SelectionTarget::Primary => self.primary_selection_content.as_bytes(&mime_type),
|
||||
Target::Clipboard => self.data_selection_content.as_bytes(&mime_type),
|
||||
Target::Primary => self.primary_selection_content.as_bytes(&mime_type),
|
||||
#[cfg(feature = "dnd")]
|
||||
Target::DnD => self.dnd_state.source_content.as_bytes(&mime_type),
|
||||
};
|
||||
|
||||
let Some(contents) = contents else {
|
||||
|
|
@ -288,7 +306,7 @@ impl<T: 'static> State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> SeatHandler for State<T> {
|
||||
impl<T: 'static + Clone> SeatHandler for State<T> {
|
||||
fn seat_state(&mut self) -> &mut SeatState {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
|
@ -371,7 +389,7 @@ impl<T: 'static> SeatHandler for State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> PointerHandler for State<T> {
|
||||
impl<T: 'static + Clone> PointerHandler for State<T> {
|
||||
fn pointer_frame(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
|
|
@ -405,20 +423,50 @@ impl<T: 'static> PointerHandler for State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> DataDeviceHandler for State<T> {
|
||||
fn enter(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {}
|
||||
impl<T: 'static + Clone> DataDeviceHandler for State<T>
|
||||
where
|
||||
DndSurface<T>: Clone,
|
||||
{
|
||||
fn enter(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
wl_data_device: &WlDataDevice,
|
||||
x: f64,
|
||||
y: f64,
|
||||
surface: &WlSurface,
|
||||
) {
|
||||
#[cfg(feature = "dnd")]
|
||||
self.offer_enter(x, y, surface, wl_data_device);
|
||||
}
|
||||
|
||||
fn leave(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {}
|
||||
fn leave(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {
|
||||
#[cfg(feature = "dnd")]
|
||||
self.offer_leave();
|
||||
}
|
||||
|
||||
fn motion(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {}
|
||||
fn motion(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
wl_data_device: &WlDataDevice,
|
||||
x: f64,
|
||||
y: f64,
|
||||
) {
|
||||
#[cfg(feature = "dnd")]
|
||||
self.offer_motion(x, y, wl_data_device);
|
||||
}
|
||||
|
||||
fn drop_performed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {}
|
||||
fn drop_performed(&mut self, _: &Connection, _: &QueueHandle<Self>, d: &WlDataDevice) {
|
||||
#[cfg(feature = "dnd")]
|
||||
self.offer_drop(d)
|
||||
}
|
||||
|
||||
// The selection is finished and ready to be used.
|
||||
fn selection(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {}
|
||||
}
|
||||
|
||||
impl<T: 'static> DataSourceHandler for State<T> {
|
||||
impl<T: 'static + Clone> DataSourceHandler for State<T> {
|
||||
fn send_request(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
|
|
@ -427,7 +475,7 @@ impl<T: 'static> DataSourceHandler for State<T> {
|
|||
mime: String,
|
||||
write_pipe: WritePipe,
|
||||
) {
|
||||
self.send_request(SelectionTarget::Clipboard, write_pipe, mime)
|
||||
self.send_request(Target::Clipboard, write_pipe, mime)
|
||||
}
|
||||
|
||||
fn cancelled(&mut self, _: &Connection, _: &QueueHandle<Self>, deleted: &WlDataSource) {
|
||||
|
|
@ -450,7 +498,7 @@ impl<T: 'static> DataSourceHandler for State<T> {
|
|||
fn dnd_finished(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource) {}
|
||||
}
|
||||
|
||||
impl<T: 'static> DataOfferHandler for State<T> {
|
||||
impl<T: 'static + Clone> DataOfferHandler for State<T> {
|
||||
fn source_actions(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
|
|
@ -465,12 +513,14 @@ impl<T: 'static> DataOfferHandler for State<T> {
|
|||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
_: &mut DragOffer,
|
||||
_: DndAction,
|
||||
action: DndAction,
|
||||
) {
|
||||
#[cfg(feature = "dnd")]
|
||||
self.dnd_state.selected_action(action);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ProvidesRegistryState for State<T> {
|
||||
impl<T: 'static + Clone> ProvidesRegistryState for State<T> {
|
||||
registry_handlers![SeatState];
|
||||
|
||||
fn registry(&mut self) -> &mut RegistryState {
|
||||
|
|
@ -478,7 +528,7 @@ impl<T: 'static> ProvidesRegistryState for State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> PrimarySelectionDeviceHandler for State<T> {
|
||||
impl<T: 'static + Clone> PrimarySelectionDeviceHandler for State<T> {
|
||||
fn selection(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
|
|
@ -488,7 +538,7 @@ impl<T: 'static> PrimarySelectionDeviceHandler for State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> PrimarySelectionSourceHandler for State<T> {
|
||||
impl<T: 'static + Clone> PrimarySelectionSourceHandler for State<T> {
|
||||
fn send_request(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
|
|
@ -497,7 +547,7 @@ impl<T: 'static> PrimarySelectionSourceHandler for State<T> {
|
|||
mime: String,
|
||||
write_pipe: WritePipe,
|
||||
) {
|
||||
self.send_request(SelectionTarget::Primary, write_pipe, mime);
|
||||
self.send_request(Target::Primary, write_pipe, mime);
|
||||
}
|
||||
|
||||
fn cancelled(
|
||||
|
|
@ -510,7 +560,7 @@ impl<T: 'static> PrimarySelectionSourceHandler for State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Dispatch<WlKeyboard, ObjectId, State<T>> for State<T> {
|
||||
impl<T: 'static + Clone> Dispatch<WlKeyboard, ObjectId, State<T>> for State<T> {
|
||||
fn event(
|
||||
state: &mut State<T>,
|
||||
_: &WlKeyboard,
|
||||
|
|
@ -543,27 +593,30 @@ impl<T: 'static> Dispatch<WlKeyboard, ObjectId, State<T>> for State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
delegate_seat!(@<T: 'static> State<T>);
|
||||
delegate_pointer!(@<T: 'static> State<T>);
|
||||
delegate_data_device!(@<T: 'static> State<T>);
|
||||
delegate_primary_selection!(@<T: 'static> State<T>);
|
||||
delegate_registry!(@<T: 'static> State<T>);
|
||||
delegate_seat!(@<T: 'static + Clone> State<T>);
|
||||
delegate_pointer!(@<T: 'static + Clone> State<T>);
|
||||
delegate_data_device!(@<T: 'static + Clone> State<T>);
|
||||
delegate_primary_selection!(@<T: 'static + Clone> State<T>);
|
||||
delegate_registry!(@<T: 'static + Clone> State<T>);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SelectionTarget {
|
||||
pub enum Target {
|
||||
/// The target is clipboard selection.
|
||||
Clipboard,
|
||||
/// The target is primary selection.
|
||||
Primary,
|
||||
#[cfg(feature = "dnd")]
|
||||
/// The targe is a DnD offer.
|
||||
DnD,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ClipboardSeatState {
|
||||
pub(crate) struct ClipboardSeatState {
|
||||
keyboard: Option<WlKeyboard>,
|
||||
pointer: Option<WlPointer>,
|
||||
data_device: Option<DataDevice>,
|
||||
pub(crate) data_device: Option<DataDevice>,
|
||||
primary_device: Option<PrimarySelectionDevice>,
|
||||
has_focus: bool,
|
||||
pub(crate) has_focus: bool,
|
||||
|
||||
/// The latest serial used to set the selection content.
|
||||
latest_serial: u32,
|
||||
|
|
@ -585,7 +638,7 @@ impl Drop for ClipboardSeatState {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe fn set_non_blocking(raw_fd: RawFd) -> std::io::Result<()> {
|
||||
pub(crate) unsafe fn set_non_blocking(raw_fd: RawFd) -> std::io::Result<()> {
|
||||
let flags = libc::fcntl(raw_fd, libc::F_GETFL);
|
||||
|
||||
if flags < 0 {
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ use sctk::reexports::calloop_wayland_source::WaylandSource;
|
|||
use sctk::reexports::client::globals::registry_queue_init;
|
||||
use sctk::reexports::client::Connection;
|
||||
|
||||
use crate::dnd::DndRequest;
|
||||
use crate::mime::{AsMimeTypes, MimeType};
|
||||
use crate::state::{SelectionTarget, State};
|
||||
use crate::state::{State, Target};
|
||||
|
||||
/// Spawn a clipboard worker, which dispatches its own `EventQueue` and handles
|
||||
/// clipboard requests.
|
||||
pub fn spawn<T: 'static + Send>(
|
||||
pub fn spawn<T: 'static + Send + Clone>(
|
||||
name: String,
|
||||
display: Connection,
|
||||
rx_chan: Channel<Command<T>>,
|
||||
|
|
@ -31,12 +32,12 @@ pub fn spawn<T: 'static + Send>(
|
|||
/// Clipboard worker thread command.
|
||||
pub enum Command<T> {
|
||||
/// Loads data for the first available mime type in the provided list.
|
||||
Load(Cow<'static, [MimeType]>, SelectionTarget),
|
||||
Load(Cow<'static, [MimeType]>, Target),
|
||||
/// Store Data with the given mime types.
|
||||
Store(Box<dyn AsMimeTypes + Send>, SelectionTarget),
|
||||
Store(Box<dyn AsMimeTypes + Send>, Target),
|
||||
#[cfg(feature = "dnd")]
|
||||
/// Init DnD
|
||||
InitDnD(Box<dyn crate::dnd::Sender<T> + Send>),
|
||||
DndRequest(DndRequest<T>),
|
||||
/// Shutdown the worker.
|
||||
Exit,
|
||||
/// Phantom data
|
||||
|
|
@ -44,7 +45,7 @@ pub enum Command<T> {
|
|||
}
|
||||
|
||||
/// Handle clipboard requests.
|
||||
fn worker_impl<T: 'static>(
|
||||
fn worker_impl<T: 'static + Clone>(
|
||||
connection: Connection,
|
||||
rx_chan: Channel<Command<T>>,
|
||||
reply_tx: Sender<Result<(Vec<u8>, MimeType)>>,
|
||||
|
|
@ -71,21 +72,17 @@ fn worker_impl<T: 'static>(
|
|||
Command::Store(data, target) => {
|
||||
state.store_selection(target, data);
|
||||
},
|
||||
Command::Load(mime_types, SelectionTarget::Clipboard)
|
||||
Command::Load(mime_types, Target::Clipboard)
|
||||
if state.data_device_manager_state.is_some() =>
|
||||
{
|
||||
if let Err(err) =
|
||||
state.load_selection(SelectionTarget::Clipboard, &mime_types)
|
||||
{
|
||||
if let Err(err) = state.load(Target::Clipboard, &mime_types) {
|
||||
let _ = state.reply_tx.send(Err(err));
|
||||
}
|
||||
},
|
||||
Command::Load(mime_types, SelectionTarget::Primary)
|
||||
Command::Load(mime_types, Target::Primary)
|
||||
if state.primary_selection_manager_state.is_some() =>
|
||||
{
|
||||
if let Err(err) =
|
||||
state.load_selection(SelectionTarget::Primary, &mime_types)
|
||||
{
|
||||
if let Err(err) = state.load(Target::Primary, &mime_types) {
|
||||
let _ = state.reply_tx.send(Err(err));
|
||||
}
|
||||
},
|
||||
|
|
@ -96,8 +93,8 @@ fn worker_impl<T: 'static>(
|
|||
)));
|
||||
},
|
||||
#[cfg(feature = "dnd")]
|
||||
Command::InitDnD(sender) => {
|
||||
state.sender = Some(sender);
|
||||
Command::DndRequest(r) => {
|
||||
state.handle_dnd_request(r);
|
||||
},
|
||||
Command::Phantom(_) => unreachable!(),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue