refactor: add buffer variant to icon argument when starting DnD

This commit is contained in:
Ashley Wulber 2024-03-26 23:22:47 -04:00
parent efe7a75e7b
commit ace619fd01
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
5 changed files with 184 additions and 22 deletions

View file

@ -1,7 +1,10 @@
[package]
name = "smithay-clipboard"
version = "0.8.0"
authors = ["Kirill Chibisov <contact@kchibisov.com>", "Victor Berger <victor.berger@m4x.org>"]
authors = [
"Kirill Chibisov <contact@kchibisov.com>",
"Victor Berger <victor.berger@m4x.org>",
]
edition = "2021"
description = "Provides access to the wayland clipboard for client applications."
repository = "https://github.com/smithay/smithay-clipboard"
@ -12,16 +15,23 @@ rust-version = "1.65.0"
[dependencies]
libc = "0.2.149"
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"] }
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", git = "https://github.com/Smithay/client-toolkit", 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"
[features]
default = ["dlopen", "dnd"]
dnd = []
dlopen = ["wayland-backend/dlopen" ]
dlopen = ["wayland-backend/dlopen"]

View file

@ -30,7 +30,7 @@ use sctk::{
delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer, delegate_registry,
delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers,
};
use smithay_clipboard::dnd::{DndDestinationRectangle, OfferEvent, Rectangle, SourceEvent};
use smithay_clipboard::dnd::{DndDestinationRectangle, Icon, OfferEvent, Rectangle, SourceEvent};
use smithay_clipboard::mime::{AllowedMimeTypes, AsMimeTypes, MimeType, ALLOWED_TEXT_MIME_TYPES};
use smithay_clipboard::{Clipboard, SimpleClipboard};
use thiserror::Error;
@ -391,21 +391,33 @@ impl PointerHandler for SimpleWindow {
match &e.kind {
PointerEventKind::Press { button, .. } if *button == BTN_LEFT => {
println!("Starting a drag!");
self.clipboard.start_dnd(
false,
self.window.wl_surface().clone(),
None,
Some(Icon::Buf {
width: 256,
height: 256,
data: vec![0x99; 256 * 256 * 4],
transparent: true,
}),
smithay_clipboard::text::Text("Clipboard Drag and Drop!".to_string()),
DndAction::all(),
);
},
PointerEventKind::Press { button, .. } if *button == BTN_RIGHT => {
println!("Starting an internal drag!");
self.internal_dnd = true;
self.clipboard.start_dnd(
true,
self.window.wl_surface().clone(),
None,
Some(Icon::Buf {
width: 256,
height: 256,
data: vec![0xFF; 256 * 256 * 4],
transparent: true,
}),
smithay_clipboard::text::Text(
"Internal clipboard Drag and Drop!".to_string(),
),

View file

@ -135,7 +135,7 @@ pub enum DndRequest<T> {
StartDnd {
internal: bool,
source: DndSurface<T>,
icon: Option<DndSurface<T>>,
icon: Option<Icon<DndSurface<T>>>,
content: Box<dyn AsMimeTypes + Send>,
actions: DndAction,
},
@ -167,6 +167,17 @@ impl<T> Sender<T> for calloop::channel::SyncSender<DndEvent<T>> {
}
}
pub enum Icon<S> {
Surface(S),
/// Argb8888 or Xrgb8888 encoded image data pre-multiplied by alpha.
Buf {
width: u32,
height: u32,
data: Vec<u8>,
transparent: bool,
},
}
impl<T: RawSurface> Clipboard<T> {
/// Set up DnD operations for the Clipboard
pub fn init_dnd(
@ -181,12 +192,17 @@ impl<T: RawSurface> Clipboard<T> {
&self,
internal: bool,
source_surface: T,
icon_surface: Option<T>,
icon_surface: Option<Icon<T>>,
content: D,
actions: DndAction,
) {
let source = DndSurface::new(source_surface, &self.connection).unwrap();
let icon = icon_surface.map(|s| DndSurface::new(s, &self.connection).unwrap());
let icon = icon_surface.map(|i| match i {
Icon::Surface(s) => Icon::Surface(DndSurface::new(s, &self.connection).unwrap()),
Icon::Buf { width, height, data, transparent } => {
Icon::Buf { width, height, data, transparent }
},
});
_ = self.request_sender.send(crate::worker::Command::DndRequest(DndRequest::StartDnd {
internal,
source,

View file

@ -9,6 +9,7 @@ use sctk::data_device_manager::WritePipe;
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_shm::Format;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
use wayland_backend::client::ObjectId;
@ -16,7 +17,7 @@ use wayland_backend::client::ObjectId;
use crate::mime::{AsMimeTypes, MimeType};
use crate::state::{set_non_blocking, State};
use super::{DndDestinationRectangle, DndEvent, DndRequest, DndSurface, OfferEvent};
use super::{DndDestinationRectangle, DndEvent, DndRequest, DndSurface, Icon, OfferEvent};
pub(crate) struct DndState<T> {
pub(crate) sender: Option<Box<dyn crate::dnd::Sender<T>>>,
@ -26,6 +27,7 @@ pub(crate) struct DndState<T> {
source_actions: DndAction,
selected_action: DndAction,
selected_mime: Option<MimeType>,
pub(crate) icon_surface: Option<WlSurface>,
pub(crate) source_content: Option<Box<dyn AsMimeTypes>>,
accept_ctr: u32,
}
@ -42,6 +44,7 @@ impl<T> Default for DndState<T> {
selected_mime: None,
source_content: None,
accept_ctr: 1,
icon_surface: None,
}
}
}
@ -262,6 +265,8 @@ where
DndRequest::DndEnd => {
self.dnd_state.source_content = None;
self.dnd_state.dnd_source = None;
self.pool.remove(&0);
self.dnd_state.icon_surface = None;
},
DndRequest::Peek(mime_type) => {
if let Err(err) = self.load_dnd(mime_type, true) {
@ -275,7 +280,7 @@ where
&mut self,
internal: bool,
source_surface: DndSurface<T>,
icon: Option<DndSurface<T>>,
mut icon: Option<Icon<DndSurface<T>>>,
content: Box<dyn AsMimeTypes + Send>,
actions: DndAction,
) -> std::io::Result<()> {
@ -294,11 +299,36 @@ where
.as_ref()
.ok_or_else(|| Error::new(ErrorKind::Other, "data device missing"))?;
let (icon_surface, buffer) = if let Some(i) = icon.take() {
match i {
Icon::Surface(s) => (Some(s.surface.clone()), None),
Icon::Buf { data, width, height, transparent } => {
let surface = self.compositor_state.create_surface(&self.queue_handle);
self.pool.remove(&0);
let (_, wl_buffer, buf) = self
.pool
.create_buffer(
width as i32,
width as i32 * 4,
height as i32,
&0,
if transparent { Format::Argb8888 } else { Format::Xrgb8888 },
)
.map_err(|err| Error::new(ErrorKind::Other, err))?;
buf.copy_from_slice(&data);
(Some(surface), Some((wl_buffer, width, height)))
},
}
} else {
(None, None)
};
if internal {
DragSource::start_internal_drag(
data_device,
&source_surface.surface,
icon.as_ref().map(|s| &s.surface),
icon_surface.as_ref(),
serial,
)
} else {
@ -314,17 +344,22 @@ where
)
})
.ok_or_else(|| Error::new(ErrorKind::Other, "data device manager missing"))?;
source.start_drag(
data_device,
&source_surface.surface,
icon.as_ref().map(|s| &s.surface),
serial,
);
source.start_drag(data_device, &source_surface.surface, icon_surface.as_ref(), serial);
self.dnd_state.dnd_source = Some(source);
self.dnd_state.source_content = Some(content);
self.dnd_state.source_actions = actions;
}
if let (Some((wl_buffer, width, height)), Some(surface)) = (buffer, icon_surface) {
surface.damage_buffer(0, 0, width as i32, height as i32);
surface.attach(Some(&wl_buffer), 0, 0);
surface.commit();
dbg!("attached buffer, damaged surface.");
self.dnd_state.icon_surface = Some(surface);
}
Ok(())
}

View file

@ -7,10 +7,12 @@ use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc;
use std::sync::mpsc::Sender;
use sctk::compositor::{CompositorHandler, CompositorState};
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::{DataDeviceManagerState, WritePipe};
use sctk::output::{OutputHandler, OutputState};
use sctk::primary_selection::device::{PrimarySelectionDevice, PrimarySelectionDeviceHandler};
use sctk::primary_selection::selection::{PrimarySelectionSource, PrimarySelectionSourceHandler};
use sctk::primary_selection::PrimarySelectionManagerState;
@ -18,9 +20,11 @@ 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};
use sctk::shm::multi::MultiPool;
use sctk::shm::{Shm, ShmHandler};
use sctk::{
delegate_data_device, delegate_pointer, delegate_primary_selection, delegate_registry,
delegate_seat, registry_handlers,
delegate_compositor, delegate_data_device, delegate_output, delegate_pointer,
delegate_primary_selection, delegate_registry, delegate_seat, delegate_shm, registry_handlers,
};
use sctk::reexports::calloop::{LoopHandle, PostAction};
@ -68,6 +72,10 @@ pub struct State<T> {
data_selection_mime_types: Rc<Cow<'static, [MimeType]>>,
#[cfg(feature = "dnd")]
pub(crate) dnd_state: crate::dnd::state::DndState<T>,
pub(crate) compositor_state: CompositorState,
output_state: OutputState,
pub(crate) shm: Shm,
pub(crate) pool: MultiPool<u8>,
_phantom: PhantomData<T>,
}
@ -90,6 +98,11 @@ impl<T: 'static + Clone> State<T> {
return None;
}
let compositor_state =
CompositorState::bind(&globals, &queue_handle).expect("wl_compositor not available");
let output_state = OutputState::new(&globals, &queue_handle);
let shm = Shm::bind(&globals, &queue_handle).expect("wl_shm not available");
let seat_state = SeatState::new(globals, queue_handle);
for seat in seat_state.seats() {
seats.insert(seat.id(), Default::default());
@ -115,6 +128,10 @@ impl<T: 'static + Clone> State<T> {
#[cfg(feature = "dnd")]
dnd_state: DndState::default(),
_phantom: PhantomData,
compositor_state,
output_state,
pool: MultiPool::new(&shm).expect("Failed to create memory pool."),
shm,
})
}
@ -475,6 +492,8 @@ impl<T: 'static + Clone> DataSourceHandler for State<T> {
if let Some(s) = self.dnd_state.sender.as_ref() {
_ = s.send(DndEvent::Source(crate::dnd::SourceEvent::Cancelled));
}
_ = self.pool.remove(&0);
self.dnd_state.icon_surface = None;
}
}
@ -501,6 +520,8 @@ impl<T: 'static + Clone> DataSourceHandler for State<T> {
if let Some(s) = self.dnd_state.sender.as_ref() {
_ = s.send(DndEvent::Source(crate::dnd::SourceEvent::Dropped))
}
_ = self.pool.remove(&0);
self.dnd_state.icon_surface = None;
}
}
@ -620,6 +641,74 @@ impl<T: 'static + Clone> Dispatch<WlKeyboard, ObjectId, State<T>> for State<T> {
}
}
impl<T: 'static + Clone> CompositorHandler for State<T> {
fn scale_factor_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &sctk::reexports::client::protocol::wl_surface::WlSurface,
_new_factor: i32,
) {
}
fn transform_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &sctk::reexports::client::protocol::wl_surface::WlSurface,
_new_transform: sctk::reexports::client::protocol::wl_output::Transform,
) {
}
fn frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &sctk::reexports::client::protocol::wl_surface::WlSurface,
_time: u32,
) {
}
}
impl<T: 'static + Clone> OutputHandler for State<T> {
fn output_state(&mut self) -> &mut sctk::output::OutputState {
&mut self.output_state
}
fn new_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: sctk::reexports::client::protocol::wl_output::WlOutput,
) {
}
fn update_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: sctk::reexports::client::protocol::wl_output::WlOutput,
) {
}
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: sctk::reexports::client::protocol::wl_output::WlOutput,
) {
}
}
impl<T: 'static + Clone> ShmHandler for State<T> {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
delegate_compositor!(@<T: 'static + Clone> State<T>);
delegate_output!(@<T: 'static + Clone> State<T>);
delegate_shm!(@<T: 'static + Clone> State<T>);
delegate_seat!(@<T: 'static + Clone> State<T>);
delegate_pointer!(@<T: 'static + Clone> State<T>);
delegate_data_device!(@<T: 'static + Clone> State<T>);