wip: Dnd setup

This commit is contained in:
Ashley Wulber 2024-03-20 15:41:09 -04:00
parent 1879b30d49
commit 10e534c1be
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
6 changed files with 161 additions and 51 deletions

View file

@ -22,5 +22,6 @@ thiserror = "1.0.57"
url = "2.5.0" url = "2.5.0"
[features] [features]
default = ["dlopen"] default = ["dlopen", "dnd"]
dnd = []
dlopen = ["wayland-backend/dlopen" ] dlopen = ["wayland-backend/dlopen" ]

View file

@ -3,6 +3,7 @@
// `smithay-client-toolkit` examples. // `smithay-client-toolkit` examples.
use std::borrow::Cow; use std::borrow::Cow;
use std::ffi::c_void;
use std::str::{FromStr, Utf8Error}; use std::str::{FromStr, Utf8Error};
use sctk::compositor::{CompositorHandler, CompositorState}; use sctk::compositor::{CompositorHandler, CompositorState};
@ -10,6 +11,7 @@ use sctk::output::{OutputHandler, OutputState};
use sctk::reexports::calloop::{EventLoop, LoopHandle}; use sctk::reexports::calloop::{EventLoop, LoopHandle};
use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::globals::registry_queue_init; 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::protocol::{wl_keyboard, wl_output, wl_seat, wl_shm, wl_surface};
use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::registry::{ProvidesRegistryState, RegistryState}; use sctk::registry::{ProvidesRegistryState, RegistryState};
@ -25,12 +27,20 @@ use sctk::{
delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers, delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers,
}; };
use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType}; use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
use smithay_clipboard::Clipboard; use smithay_clipboard::{Clipboard, SimpleClipboard};
use thiserror::Error; use thiserror::Error;
use url::Url; use url::Url;
const MIN_DIM_SIZE: usize = 256; 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() { fn main() {
let connection = Connection::connect_to_env().unwrap(); let connection = Connection::connect_to_env().unwrap();
let (globals, event_queue) = registry_queue_init(&connection).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 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 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 { let mut simple_window = SimpleWindow {
registry_state: RegistryState::new(&globals), registry_state: RegistryState::new(&globals),
@ -90,7 +106,7 @@ struct SimpleWindow {
seat_state: SeatState, seat_state: SeatState,
output_state: OutputState, output_state: OutputState,
shm: Shm, shm: Shm,
clipboard: Clipboard, clipboard: SimpleClipboard,
exit: bool, exit: bool,
first_configure: bool, first_configure: bool,

View file

@ -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; use crate::Clipboard;
impl Clipboard { pub struct DndSurface<T> {
pub 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 DndEvent<T> {
Test(T),
}
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> Clipboard<T> {
/// Set up DnD operations for the Clipboard /// Set up DnD operations for the Clipboard
pub fn init_dnd() {} 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))
}
/// Start a DnD operation on the given surface with some data /// Start a DnD operation on the given surface with some data
pub fn start_dnd() {} pub fn start_dnd<D: RawSurface>(&self, s: D) {
let s = DndSurface::new(s, &self.connection).unwrap();
dbg!(&s.surface);
}
/// End the current DnD operation, if there is one /// End the current DnD operation, if there is one
pub fn end_dnd() {} pub fn end_dnd() {}
} }

View file

@ -13,6 +13,8 @@ use sctk::reexports::calloop::channel::{self, Sender};
use sctk::reexports::client::backend::Backend; use sctk::reexports::client::backend::Backend;
use sctk::reexports::client::Connection; use sctk::reexports::client::Connection;
#[cfg(feature = "dnd")]
pub mod dnd;
pub mod mime; pub mod mime;
mod state; mod state;
mod text; mod text;
@ -22,14 +24,17 @@ use mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
use state::SelectionTarget; use state::SelectionTarget;
use text::Text; use text::Text;
pub type SimpleClipboard = Clipboard<()>;
/// Access to a Wayland clipboard. /// Access to a Wayland clipboard.
pub struct Clipboard { pub struct Clipboard<T> {
request_sender: Sender<worker::Command>, request_sender: Sender<worker::Command<T>>,
request_receiver: Receiver<Result<(Vec<u8>, MimeType)>>, request_receiver: Receiver<Result<(Vec<u8>, MimeType)>>,
clipboard_thread: Option<std::thread::JoinHandle<()>>, clipboard_thread: Option<std::thread::JoinHandle<()>>,
connection: Connection,
} }
impl Clipboard { impl<T: 'static + Send> Clipboard<T> {
/// Creates new clipboard which will be running on its own thread with its /// Creates new clipboard which will be running on its own thread with its
/// own event queue to handle clipboard requests. /// own event queue to handle clipboard requests.
/// ///
@ -47,16 +52,17 @@ impl Clipboard {
let (clipboard_reply_sender, request_receiver) = mpsc::channel(); let (clipboard_reply_sender, request_receiver) = mpsc::channel();
let name = String::from("smithay-clipboard"); 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 custom clipboard data.
/// ///
/// Load the requested type from a clipboard on the last observed seat. /// Load the requested type from a clipboard on the last observed seat.
pub fn load<T: AllowedMimeTypes + 'static>(&self) -> Result<T> { pub fn load<D: AllowedMimeTypes + 'static>(&self) -> Result<D> {
self.load_inner(SelectionTarget::Clipboard, T::allowed()) self.load_inner(SelectionTarget::Clipboard, D::allowed())
} }
/// Load clipboard data. /// Load clipboard data.
@ -70,8 +76,8 @@ impl Clipboard {
/// ///
/// Load the requested type from a primary clipboard on the last observed /// Load the requested type from a primary clipboard on the last observed
/// seat. /// seat.
pub fn load_primary<T: AllowedMimeTypes + 'static>(&self) -> Result<T> { pub fn load_primary<D: AllowedMimeTypes + 'static>(&self) -> Result<D> {
self.load_inner(SelectionTarget::Primary, T::allowed()) self.load_inner(SelectionTarget::Primary, D::allowed())
} }
/// Load primary clipboard data. /// Load primary clipboard data.
@ -104,14 +110,14 @@ impl Clipboard {
/// Store custom data to a clipboard. /// Store custom data to a clipboard.
/// ///
/// Stores data of the provided type to a clipboard on a last observed seat. /// Stores data of the provided type to a clipboard on a last observed seat.
pub fn store<T: AsMimeTypes + Send + 'static>(&self, data: T) { pub fn store<D: AsMimeTypes + Send + 'static>(&self, data: D) {
self.store_inner(data, SelectionTarget::Clipboard); self.store_inner(data, SelectionTarget::Clipboard);
} }
/// Store to a clipboard. /// Store to a clipboard.
/// ///
/// Stores to a clipboard on a last observed seat. /// Stores to a clipboard on a last observed seat.
pub fn store_text<T: Into<String>>(&self, text: T) { pub fn store_text<D: Into<String>>(&self, text: D) {
self.store(Text(text.into())); self.store(Text(text.into()));
} }
@ -119,27 +125,27 @@ impl Clipboard {
/// ///
/// Stores data of the provided type to a primary clipboard on a last /// Stores data of the provided type to a primary clipboard on a last
/// observed seat. /// observed seat.
pub fn store_primary<T: AsMimeTypes + Send + 'static>(&self, data: T) { pub fn store_primary<D: AsMimeTypes + Send + 'static>(&self, data: D) {
self.store_inner(data, SelectionTarget::Primary); self.store_inner(data, SelectionTarget::Primary);
} }
/// Store to a primary clipboard. /// Store to a primary clipboard.
/// ///
/// Stores to a primary clipboard on a last observed seat. /// Stores to a primary clipboard on a last observed seat.
pub fn store_primary_text<T: Into<String>>(&self, text: T) { pub fn store_primary_text<D: Into<String>>(&self, text: D) {
self.store_primary(Text(text.into())); self.store_primary(Text(text.into()));
} }
fn load_inner<T: TryFrom<(Vec<u8>, MimeType)> + 'static>( fn load_inner<D: TryFrom<(Vec<u8>, MimeType)> + 'static>(
&self, &self,
target: SelectionTarget, target: SelectionTarget,
allowed: impl Into<Cow<'static, [MimeType]>>, allowed: impl Into<Cow<'static, [MimeType]>>,
) -> Result<T> { ) -> Result<D> {
let _ = self.request_sender.send(worker::Command::Load(allowed.into(), target)); let _ = self.request_sender.send(worker::Command::Load(allowed.into(), target));
match self.request_receiver.recv() { match self.request_receiver.recv() {
Ok(res) => res.and_then(|(data, mime)| { 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::Error::new(
std::io::ErrorKind::Other, std::io::ErrorKind::Other,
"Failed to load data of the requested type.", "Failed to load data of the requested type.",
@ -152,13 +158,13 @@ impl Clipboard {
} }
} }
fn store_inner<T: AsMimeTypes + Send + 'static>(&self, data: T, target: SelectionTarget) { fn store_inner<D: AsMimeTypes + Send + 'static>(&self, data: D, target: SelectionTarget) {
let request = worker::Command::Store(Box::new(data), target); let request = worker::Command::Store(Box::new(data), target);
let _ = self.request_sender.send(request); let _ = self.request_sender.send(request);
} }
} }
impl Drop for Clipboard { impl<T> Drop for Clipboard<T> {
fn drop(&mut self) { fn drop(&mut self) {
// Shutdown smithay-clipboard. // Shutdown smithay-clipboard.
let _ = self.request_sender.send(worker::Command::Exit); let _ = self.request_sender.send(worker::Command::Exit);

View file

@ -1,6 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{Error, ErrorKind, Read, Result, Write}; use std::io::{Error, ErrorKind, Read, Result, Write};
use std::marker::PhantomData;
use std::mem; use std::mem;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc; use std::rc::Rc;
@ -39,7 +40,7 @@ use wayland_backend::client::ObjectId;
use crate::mime::{AsMimeTypes, MimeType}; use crate::mime::{AsMimeTypes, MimeType};
use crate::text::Text; use crate::text::Text;
pub struct State { pub struct State<T> {
pub primary_selection_manager_state: Option<PrimarySelectionManagerState>, pub primary_selection_manager_state: Option<PrimarySelectionManagerState>,
pub data_device_manager_state: Option<DataDeviceManagerState>, pub data_device_manager_state: Option<DataDeviceManagerState>,
pub reply_tx: Sender<Result<(Vec<u8>, MimeType)>>, pub reply_tx: Sender<Result<(Vec<u8>, MimeType)>>,
@ -62,9 +63,12 @@ pub struct State {
data_sources: Vec<CopyPasteSource>, data_sources: Vec<CopyPasteSource>,
data_selection_content: Box<dyn AsMimeTypes>, data_selection_content: Box<dyn AsMimeTypes>,
data_selection_mime_types: Rc<Cow<'static, [MimeType]>>, data_selection_mime_types: Rc<Cow<'static, [MimeType]>>,
#[cfg(feature = "dnd")]
pub(crate) sender: Option<Box<dyn crate::dnd::Sender<T>>>,
_phantom: PhantomData<T>,
} }
impl State { impl<T: 'static> State<T> {
#[must_use] #[must_use]
pub fn new( pub fn new(
globals: &GlobalList, globals: &GlobalList,
@ -105,6 +109,9 @@ impl State {
seats, seats,
primary_selection_mime_types: Rc::new(Default::default()), primary_selection_mime_types: Rc::new(Default::default()),
data_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<T: 'static> SeatHandler for State<T> {
fn seat_state(&mut self) -> &mut SeatState { fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state &mut self.seat_state
} }
@ -364,7 +371,7 @@ impl SeatHandler for State {
} }
} }
impl PointerHandler for State { impl<T: 'static> PointerHandler for State<T> {
fn pointer_frame( fn pointer_frame(
&mut self, &mut self,
_: &Connection, _: &Connection,
@ -398,7 +405,7 @@ impl PointerHandler for State {
} }
} }
impl DataDeviceHandler for State { impl<T: 'static> DataDeviceHandler for State<T> {
fn enter(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {} fn enter(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {}
fn leave(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {} fn leave(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {}
@ -411,7 +418,7 @@ impl DataDeviceHandler for State {
fn selection(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {} fn selection(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {}
} }
impl DataSourceHandler for State { impl<T: 'static> DataSourceHandler for State<T> {
fn send_request( fn send_request(
&mut self, &mut self,
_: &Connection, _: &Connection,
@ -443,7 +450,7 @@ impl DataSourceHandler for State {
fn dnd_finished(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource) {} fn dnd_finished(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource) {}
} }
impl DataOfferHandler for State { impl<T: 'static> DataOfferHandler for State<T> {
fn source_actions( fn source_actions(
&mut self, &mut self,
_: &Connection, _: &Connection,
@ -463,7 +470,7 @@ impl DataOfferHandler for State {
} }
} }
impl ProvidesRegistryState for State { impl<T: 'static> ProvidesRegistryState for State<T> {
registry_handlers![SeatState]; registry_handlers![SeatState];
fn registry(&mut self) -> &mut RegistryState { fn registry(&mut self) -> &mut RegistryState {
@ -471,7 +478,7 @@ impl ProvidesRegistryState for State {
} }
} }
impl PrimarySelectionDeviceHandler for State { impl<T: 'static> PrimarySelectionDeviceHandler for State<T> {
fn selection( fn selection(
&mut self, &mut self,
_: &Connection, _: &Connection,
@ -481,7 +488,7 @@ impl PrimarySelectionDeviceHandler for State {
} }
} }
impl PrimarySelectionSourceHandler for State { impl<T: 'static> PrimarySelectionSourceHandler for State<T> {
fn send_request( fn send_request(
&mut self, &mut self,
_: &Connection, _: &Connection,
@ -503,14 +510,14 @@ impl PrimarySelectionSourceHandler for State {
} }
} }
impl Dispatch<WlKeyboard, ObjectId, State> for State { impl<T: 'static> Dispatch<WlKeyboard, ObjectId, State<T>> for State<T> {
fn event( fn event(
state: &mut State, state: &mut State<T>,
_: &WlKeyboard, _: &WlKeyboard,
event: <WlKeyboard as sctk::reexports::client::Proxy>::Event, event: <WlKeyboard as sctk::reexports::client::Proxy>::Event,
data: &ObjectId, data: &ObjectId,
_: &Connection, _: &Connection,
_: &QueueHandle<State>, _: &QueueHandle<State<T>>,
) { ) {
use sctk::reexports::client::protocol::wl_keyboard::Event as WlKeyboardEvent; use sctk::reexports::client::protocol::wl_keyboard::Event as WlKeyboardEvent;
let seat_state = match state.seats.get_mut(data) { let seat_state = match state.seats.get_mut(data) {
@ -536,11 +543,11 @@ impl Dispatch<WlKeyboard, ObjectId, State> for State {
} }
} }
delegate_seat!(State); delegate_seat!(@<T: 'static> State<T>);
delegate_pointer!(State); delegate_pointer!(@<T: 'static> State<T>);
delegate_data_device!(State); delegate_data_device!(@<T: 'static> State<T>);
delegate_primary_selection!(State); delegate_primary_selection!(@<T: 'static> State<T>);
delegate_registry!(State); delegate_registry!(@<T: 'static> State<T>);
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum SelectionTarget { pub enum SelectionTarget {

View file

@ -1,5 +1,6 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::io::{Error, ErrorKind, Result}; use std::io::{Error, ErrorKind, Result};
use std::marker::PhantomData;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use sctk::reexports::calloop::channel::Channel; 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 /// Spawn a clipboard worker, which dispatches its own `EventQueue` and handles
/// clipboard requests. /// clipboard requests.
pub fn spawn( pub fn spawn<T: 'static + Send>(
name: String, name: String,
display: Connection, display: Connection,
rx_chan: Channel<Command>, rx_chan: Channel<Command<T>>,
worker_replier: Sender<Result<(Vec<u8>, MimeType)>>, worker_replier: Sender<Result<(Vec<u8>, MimeType)>>,
) -> Option<std::thread::JoinHandle<()>> { ) -> Option<std::thread::JoinHandle<()>> {
std::thread::Builder::new() std::thread::Builder::new()
@ -28,19 +29,24 @@ pub fn spawn(
} }
/// Clipboard worker thread command. /// Clipboard worker thread command.
pub enum Command { pub enum Command<T> {
/// Loads data for the first available mime type in the provided list. /// Loads data for the first available mime type in the provided list.
Load(Cow<'static, [MimeType]>, SelectionTarget), Load(Cow<'static, [MimeType]>, SelectionTarget),
/// Store Data with the given mime types. /// Store Data with the given mime types.
Store(Box<dyn AsMimeTypes + Send>, SelectionTarget), Store(Box<dyn AsMimeTypes + Send>, SelectionTarget),
#[cfg(feature = "dnd")]
/// Init DnD
InitDnD(Box<dyn crate::dnd::Sender<T> + Send>),
/// Shutdown the worker. /// Shutdown the worker.
Exit, Exit,
/// Phantom data
Phantom(PhantomData<T>),
} }
/// Handle clipboard requests. /// Handle clipboard requests.
fn worker_impl( fn worker_impl<T: 'static>(
connection: Connection, connection: Connection,
rx_chan: Channel<Command>, rx_chan: Channel<Command<T>>,
reply_tx: Sender<Result<(Vec<u8>, MimeType)>>, reply_tx: Sender<Result<(Vec<u8>, MimeType)>>,
) { ) {
let (globals, event_queue) = match registry_queue_init(&connection) { let (globals, event_queue) = match registry_queue_init(&connection) {
@ -48,7 +54,7 @@ fn worker_impl(
Err(_) => return, Err(_) => return,
}; };
let mut event_loop = EventLoop::<State>::try_new().unwrap(); let mut event_loop = EventLoop::<State<T>>::try_new().unwrap();
let loop_handle = event_loop.handle(); let loop_handle = event_loop.handle();
let mut state = match State::new(&globals, &event_queue.handle(), loop_handle.clone(), reply_tx) 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", "requested selection is not supported",
))); )));
}, },
#[cfg(feature = "dnd")]
Command::InitDnD(sender) => {
state.sender = Some(sender);
},
Command::Phantom(_) => unreachable!(),
} }
} }
}) })