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"
[features]
default = ["dlopen"]
default = ["dlopen", "dnd"]
dnd = []
dlopen = ["wayland-backend/dlopen" ]

View file

@ -3,6 +3,7 @@
// `smithay-client-toolkit` examples.
use std::borrow::Cow;
use std::ffi::c_void;
use std::str::{FromStr, Utf8Error};
use sctk::compositor::{CompositorHandler, CompositorState};
@ -10,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_surface::WlSurface;
use sctk::reexports::client::protocol::{wl_keyboard, wl_output, wl_seat, wl_shm, wl_surface};
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::registry::{ProvidesRegistryState, RegistryState};
@ -25,12 +27,20 @@ use sctk::{
delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers,
};
use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
use smithay_clipboard::Clipboard;
use smithay_clipboard::{Clipboard, SimpleClipboard};
use thiserror::Error;
use url::Url;
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() {
let connection = Connection::connect_to_env().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 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 {
registry_state: RegistryState::new(&globals),
@ -90,7 +106,7 @@ struct SimpleWindow {
seat_state: SeatState,
output_state: OutputState,
shm: Shm,
clipboard: Clipboard,
clipboard: SimpleClipboard,
exit: 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;
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
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
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
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::Connection;
#[cfg(feature = "dnd")]
pub mod dnd;
pub mod mime;
mod state;
mod text;
@ -22,14 +24,17 @@ use mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
use state::SelectionTarget;
use text::Text;
pub type SimpleClipboard = Clipboard<()>;
/// Access to a Wayland clipboard.
pub struct Clipboard {
request_sender: Sender<worker::Command>,
pub struct Clipboard<T> {
request_sender: Sender<worker::Command<T>>,
request_receiver: Receiver<Result<(Vec<u8>, MimeType)>>,
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
/// own event queue to handle clipboard requests.
///
@ -47,16 +52,17 @@ impl Clipboard {
let (clipboard_reply_sender, request_receiver) = mpsc::channel();
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 the requested type from a clipboard on the last observed seat.
pub fn load<T: AllowedMimeTypes + 'static>(&self) -> Result<T> {
self.load_inner(SelectionTarget::Clipboard, T::allowed())
pub fn load<D: AllowedMimeTypes + 'static>(&self) -> Result<D> {
self.load_inner(SelectionTarget::Clipboard, D::allowed())
}
/// Load clipboard data.
@ -70,8 +76,8 @@ impl Clipboard {
///
/// Load the requested type from a primary clipboard on the last observed
/// seat.
pub fn load_primary<T: AllowedMimeTypes + 'static>(&self) -> Result<T> {
self.load_inner(SelectionTarget::Primary, T::allowed())
pub fn load_primary<D: AllowedMimeTypes + 'static>(&self) -> Result<D> {
self.load_inner(SelectionTarget::Primary, D::allowed())
}
/// Load primary clipboard data.
@ -104,14 +110,14 @@ impl Clipboard {
/// Store custom data to a clipboard.
///
/// 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);
}
/// Store to a clipboard.
///
/// 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()));
}
@ -119,27 +125,27 @@ impl Clipboard {
///
/// Stores data of the provided type to a primary clipboard on a last
/// 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);
}
/// Store to a primary clipboard.
///
/// 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()));
}
fn load_inner<T: TryFrom<(Vec<u8>, MimeType)> + 'static>(
fn load_inner<D: TryFrom<(Vec<u8>, MimeType)> + 'static>(
&self,
target: SelectionTarget,
allowed: impl Into<Cow<'static, [MimeType]>>,
) -> Result<T> {
) -> Result<D> {
let _ = self.request_sender.send(worker::Command::Load(allowed.into(), target));
match self.request_receiver.recv() {
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::ErrorKind::Other,
"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 _ = self.request_sender.send(request);
}
}
impl Drop for Clipboard {
impl<T> Drop for Clipboard<T> {
fn drop(&mut self) {
// Shutdown smithay-clipboard.
let _ = self.request_sender.send(worker::Command::Exit);

View file

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

View file

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