feat: method for peeking dnd offer data

This commit is contained in:
Ashley Wulber 2024-03-25 18:59:23 -04:00
parent 4c65853f51
commit e84ab6d2d8
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
3 changed files with 86 additions and 14 deletions

View file

@ -85,6 +85,18 @@ fn main() {
let s = smithay_clipboard::text::Text::try_from((data, mime_type)).unwrap(); let s = smithay_clipboard::text::Text::try_from((data, mime_type)).unwrap();
println!("Received DnD data for {}: {}", id.unwrap_or_default(), s.0); println!("Received DnD data for {}: {}", id.unwrap_or_default(), s.0);
}, },
smithay_clipboard::dnd::DndEvent::Offer(id, OfferEvent::Motion { x, y }) => {
if id != state.offer_hover_id {
state.offer_hover_id = id;
if let Ok(data) = state.clipboard.peek_offer::<smithay_clipboard::text::Text>(
MimeType::Text(smithay_clipboard::mime::Text::TextPlain),
) {
println!("Peeked the data: {}", data.0);
}
}
println!("Received DnD Motion for {id:?}: at {x}, {y}");
},
smithay_clipboard::dnd::DndEvent::Offer(id, OfferEvent::Leave) => { smithay_clipboard::dnd::DndEvent::Offer(id, OfferEvent::Leave) => {
if state.internal_dnd { if state.internal_dnd {
if state.pointer_focus { if state.pointer_focus {
@ -97,12 +109,25 @@ fn main() {
state.clipboard.end_dnd(); state.clipboard.end_dnd();
} }
} else { } else {
state.offer_hover_id = None;
println!("Dnd offer left {id:?}."); println!("Dnd offer left {id:?}.");
} }
}, },
smithay_clipboard::dnd::DndEvent::Offer(id, OfferEvent::Enter { mime_types, .. }) => {
println!("Received DnD Enter for {id:?}");
state.offer_hover_id = id;
if let Some(mime) = mime_types.get(0) {
if let Ok(data) =
state.clipboard.peek_offer::<smithay_clipboard::text::Text>(mime.clone())
{
println!("Peeked the data: {}", data.0);
}
}
},
smithay_clipboard::dnd::DndEvent::Source(SourceEvent::Finished) => { smithay_clipboard::dnd::DndEvent::Source(SourceEvent::Finished) => {
println!("Finished sending data."); println!("Finished sending data.");
state.internal_dnd = false; state.internal_dnd = false;
state.offer_hover_id = None;
}, },
e => { e => {
dbg!(e); dbg!(e);
@ -172,6 +197,7 @@ fn main() {
internal_dnd: false, internal_dnd: false,
keyboard_focus: false, keyboard_focus: false,
pointer_focus: false, pointer_focus: false,
offer_hover_id: None,
loop_handle: event_loop.handle(), loop_handle: event_loop.handle(),
}; };
@ -204,6 +230,7 @@ struct SimpleWindow {
internal_dnd: bool, internal_dnd: bool,
keyboard_focus: bool, keyboard_focus: bool,
pointer_focus: bool, pointer_focus: bool,
offer_hover_id: Option<u128>,
loop_handle: LoopHandle<'static, SimpleWindow>, loop_handle: LoopHandle<'static, SimpleWindow>,
} }

View file

@ -8,7 +8,7 @@ use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy}; use sctk::reexports::client::{Connection, Proxy};
use wayland_backend::client::{InvalidId, ObjectId}; use wayland_backend::client::{InvalidId, ObjectId};
use crate::mime::{AsMimeTypes, MimeType}; use crate::mime::{AllowedMimeTypes, AsMimeTypes, MimeType};
use crate::Clipboard; use crate::Clipboard;
pub mod state; pub mod state;
@ -139,6 +139,8 @@ pub enum DndRequest<T> {
content: Box<dyn AsMimeTypes + Send>, content: Box<dyn AsMimeTypes + Send>,
actions: DndAction, actions: DndAction,
}, },
/// Peek the data of an active DnD offer
Peek(MimeType),
/// Set the DnD action chosen by the user. /// Set the DnD action chosen by the user.
SetAction(DndAction), SetAction(DndAction),
/// End an active DnD Source /// End an active DnD Source
@ -201,6 +203,8 @@ impl<T: RawSurface> Clipboard<T> {
/// Register a surface for receiving DnD offers /// Register a surface for receiving DnD offers
/// Rectangles should be provided in order of decreasing priority. /// 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: T, rectangles: Vec<DndDestinationRectangle>) { pub fn register_dnd_destination(&self, surface: T, rectangles: Vec<DndDestinationRectangle>) {
let s = DndSurface::new(surface, &self.connection).unwrap(); let s = DndSurface::new(surface, &self.connection).unwrap();
@ -215,4 +219,30 @@ impl<T: RawSurface> Clipboard<T> {
.request_sender .request_sender
.send(crate::worker::Command::DndRequest(DndRequest::SetAction(action))); .send(crate::worker::Command::DndRequest(DndRequest::SetAction(action)));
} }
/// Peek at the contents of a DnD offer
pub fn peek_offer<D: AllowedMimeTypes + 'static>(
&self,
mime_type: MimeType,
) -> std::io::Result<D> {
self.request_sender
.send(crate::worker::Command::DndRequest(DndRequest::Peek(mime_type)))
.map_err(|_| {
std::io::Error::new(std::io::ErrorKind::Other, "Failed to send Peek request.")
})?;
self.request_receiver
.recv()
.map_err(|_| {
std::io::Error::new(std::io::ErrorKind::Other, "Failed to receive data request.")
})
.and_then(|ret| {
D::try_from(ret?).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to convert data to requested type.",
)
})
})
}
} }

View file

@ -162,7 +162,7 @@ where
dnd_state.set_actions(self.dnd_state.selected_action, self.dnd_state.selected_action); dnd_state.set_actions(self.dnd_state.selected_action, self.dnd_state.selected_action);
dnd_state.accept_mime_type(dnd_state.serial, Some(mime.to_string())); dnd_state.accept_mime_type(dnd_state.serial, Some(mime.to_string()));
_ = self.load_dnd(mime); _ = self.load_dnd(mime, false);
} }
pub(crate) fn offer_enter( pub(crate) fn offer_enter(
@ -263,6 +263,11 @@ where
self.dnd_state.source_content = None; self.dnd_state.source_content = None;
self.dnd_state.dnd_source = None; self.dnd_state.dnd_source = None;
}, },
DndRequest::Peek(mime_type) => {
if let Err(err) = self.load_dnd(mime_type, true) {
_ = self.reply_tx.send(Err(err));
}
},
}; };
} }
@ -341,13 +346,13 @@ where
offer.set_actions(a, a); offer.set_actions(a, a);
if let Some(mime_type) = self.dnd_state.selected_mime.clone() { if let Some(mime_type) = self.dnd_state.selected_mime.clone() {
_ = self.load_dnd(mime_type); _ = self.load_dnd(mime_type, false);
} }
Ok(()) Ok(())
} }
/// Load data for the given target. /// Load data for the given target.
pub fn load_dnd(&mut self, mut mime_type: MimeType) -> std::io::Result<()> { pub fn load_dnd(&mut self, mut mime_type: MimeType, peek: bool) -> std::io::Result<()> {
let cur_id = self.cur_id(); let cur_id = self.cur_id();
let latest = self let latest = self
.latest_seat .latest_seat
@ -375,23 +380,33 @@ where
let mut content = Vec::new(); let mut content = Vec::new();
let _ = self.loop_handle.insert_source(read_pipe, move |_, file, state| { let _ = self.loop_handle.insert_source(read_pipe, move |_, file, state| {
let file = unsafe { file.get_mut() }; let file = unsafe { file.get_mut() };
let Some(tx) = state.dnd_state.sender.as_ref() else {
return PostAction::Remove;
};
loop { loop {
match file.read(&mut reader_buffer) { match file.read(&mut reader_buffer) {
Ok(0) => { Ok(0) => {
offer.finish(); // only finish if not peeking
let _ = tx.send(DndEvent::Offer(cur_id, OfferEvent::Data { if !peek {
data: mem::take(&mut content), offer.finish();
mime_type: mem::take(&mut mime_type), }
}));
if peek {
_ = state
.reply_tx
.send(Ok((mem::take(&mut content), mem::take(&mut mime_type))));
} else if let Some(tx) = state.dnd_state.sender.as_ref() {
let _ = tx.send(DndEvent::Offer(cur_id, OfferEvent::Data {
data: mem::take(&mut content),
mime_type: mem::take(&mut mime_type),
}));
}
break PostAction::Remove; break PostAction::Remove;
}, },
Ok(n) => content.extend_from_slice(&reader_buffer[..n]), Ok(n) => content.extend_from_slice(&reader_buffer[..n]),
Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue, Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue,
Err(_) => { Err(err) => {
// let _ = state.dnd_state.sender.unwrap().send(Err(err)); if peek {
let _ = state.reply_tx.send(Err(err));
}
break PostAction::Remove; break PostAction::Remove;
}, },
}; };