1use std::collections::HashMap;
2use std::io::{Error, ErrorKind, Read, Write};
3use std::mem;
4use std::os::unix::io::AsRawFd;
5
6use sctk::data_device_manager::data_offer::DragOffer;
7use sctk::data_device_manager::data_source::DragSource;
8use sctk::data_device_manager::WritePipe;
9use sctk::reexports::calloop::PostAction;
10use sctk::reexports::client::protocol::wl_data_device::WlDataDevice;
11use sctk::reexports::client::protocol::wl_data_device_manager::DndAction;
12use sctk::reexports::client::protocol::wl_shm::Format;
13use sctk::reexports::client::protocol::wl_surface::WlSurface;
14use sctk::reexports::client::Proxy;
15use wayland_backend::client::ObjectId;
16
17use crate::mime::{AsMimeTypes, MimeType};
18use crate::state::{set_non_blocking, State};
19
20use super::{DndDestinationRectangle, DndEvent, DndRequest, DndSurface, Icon, OfferEvent};
21
22pub(crate) struct DndState<T> {
23 pub(crate) sender: Option<Box<dyn crate::dnd::Sender<T>>>,
24 destinations: HashMap<ObjectId, (DndSurface<T>, Vec<DndDestinationRectangle>)>,
25 pub(crate) dnd_source: Option<DragSource>,
26 active_surface: Option<(DndSurface<T>, Option<DndDestinationRectangle>)>,
27 source_actions: DndAction,
28 selected_action: DndAction,
29 selected_mime: Option<MimeType>,
30 pub(crate) icon_surface: Option<WlSurface>,
31 pub(crate) source_content: Option<Box<dyn AsMimeTypes>>,
32 accept_ctr: u32,
33}
34
35impl<T> Default for DndState<T> {
36 fn default() -> Self {
37 Self {
38 sender: Default::default(),
39 destinations: Default::default(),
40 dnd_source: Default::default(),
41 active_surface: None,
42 source_actions: DndAction::empty(),
43 selected_action: DndAction::empty(),
44 selected_mime: None,
45 source_content: None,
46 accept_ctr: 1,
47 icon_surface: None,
48 }
49 }
50}
51
52impl<T> DndState<T> {
53 pub(crate) fn selected_action(&mut self, a: DndAction) {
54 self.selected_action = a;
55 if let Some(tx) = self.sender.as_ref() {
56 _ = tx.send(DndEvent::Offer(
57 self.active_surface.as_ref().and_then(|(_, d)| d.as_ref().map(|d| d.id)),
58 OfferEvent::SelectedAction(a),
59 ));
60 }
61 }
62}
63
64impl<T> State<T>
65where
66 T: Clone + 'static,
67{
68 pub fn update_active_surface(
69 &mut self,
70 surface: &WlSurface,
71 x: f64,
72 y: f64,
73 dnd_state: Option<&DragOffer>,
74 ) {
75 let had_dest = self
76 .dnd_state
77 .active_surface
78 .as_ref()
79 .map(|(_, d)| d.as_ref().map(|d| d.id))
80 .unwrap_or_default();
81 self.dnd_state.active_surface =
82 self.dnd_state.destinations.get(&surface.id()).map(|(s, dests)| {
83 let Some((dest, mime, actions)) = dests.iter().find_map(|r| {
84 let actions = dnd_state.as_ref().map(|s| {
85 (
86 s.source_actions.intersection(r.actions),
87 s.source_actions.intersection(r.preferred),
88 )
89 });
90 let mime = dnd_state.as_ref().and_then(|dnd_state| {
91 r.mime_types.iter().find(|m| {
92 dnd_state.with_mime_types(|mimes| mimes.iter().any(|a| a == m.as_ref()))
93 })
94 });
95
96 (r.rectangle.contains(x, y)
97 && (r.mime_types.is_empty() || mime.is_some())
98 && (r.actions.is_all()
99 || dnd_state
100 .as_ref()
101 .map(|dnd_state| dnd_state.source_actions.intersects(r.actions))
102 .unwrap_or(true)))
103 .then(|| (r.clone(), mime, actions))
104 }) else {
105 if let Some(old_id) = had_dest {
106 if let Some(dnd_state) = dnd_state.as_ref() {
107 if let Some(tx) = self.dnd_state.sender.as_ref() {
108 _ = tx.send(DndEvent::Offer(
109 Some(old_id),
110 super::OfferEvent::LeaveDestination,
111 ));
112 }
113 dnd_state.set_actions(DndAction::empty(), DndAction::empty());
114 dnd_state.accept_mime_type(self.dnd_state.accept_ctr, None);
115 self.dnd_state.accept_ctr = self.dnd_state.accept_ctr.wrapping_add(1);
116 self.dnd_state.selected_action = DndAction::empty();
117 self.dnd_state.selected_mime = None;
118 }
119 }
120 return (s.clone(), None);
121 };
122 if !had_dest.is_some_and(|old_id| old_id == dest.id) {
123 if let (Some((action, preferred_action)), Some(mime_type), Some(dnd_state)) =
124 (actions, mime, dnd_state.as_ref())
125 {
126 if let Some((tx, old_id)) = self.dnd_state.sender.as_ref().zip(had_dest) {
127 _ = tx.send(DndEvent::Offer(
128 Some(old_id),
129 super::OfferEvent::LeaveDestination,
130 ));
131 }
132 if let Some(tx) = self.dnd_state.sender.as_ref() {
133 _ = tx.send(DndEvent::Offer(Some(dest.id), OfferEvent::Enter {
134 x,
135 y,
136 surface: s.s.clone(),
137 mime_types: dest.mime_types.clone(),
138 }));
139
140 _ = tx.send(DndEvent::Offer(
141 Some(dest.id),
142 OfferEvent::SelectedAction(self.dnd_state.selected_action),
143 ));
144 }
145 dnd_state.set_actions(action, preferred_action);
146 self.dnd_state.selected_mime = Some(mime_type.clone());
147 dnd_state.accept_mime_type(
148 self.dnd_state.accept_ctr,
149 Some(mime_type.to_string()),
150 );
151 self.dnd_state.accept_ctr = self.dnd_state.accept_ctr.wrapping_add(1);
152 }
153 }
154 (s.clone(), Some(dest))
155 });
156 }
157
158 fn cur_id(&self) -> Option<u128> {
159 self.dnd_state.active_surface.as_ref().and_then(|(_, rect)| rect.as_ref().map(|r| r.id))
160 }
161
162 pub(crate) fn offer_drop(&mut self, wl_data_device: &WlDataDevice) {
163 let Some(tx) = self.dnd_state.sender.as_ref() else {
164 return;
165 };
166 let id = self.cur_id();
167 _ = tx.send(DndEvent::Offer(id, super::OfferEvent::Drop));
168
169 let Some(data_device) = self
170 .seats
171 .iter()
172 .find_map(|(_, s)| s.data_device.as_ref().filter(|dev| dev.inner() == wl_data_device))
173 else {
174 return;
175 };
176 let Some(dnd_state) = data_device.data().drag_offer() else {
177 return;
178 };
179
180 if self.dnd_state.selected_action == DndAction::Ask {
181 _ = tx.send(DndEvent::Offer(
182 id,
183 super::OfferEvent::SelectedAction(self.dnd_state.selected_action),
184 ));
185 return;
186 } else if self.dnd_state.selected_action.is_empty() {
187 return;
188 }
189 let Some(mime) = self.dnd_state.selected_mime.take() else {
190 dnd_state.accept_mime_type(dnd_state.serial, None);
191 return;
192 };
193
194 dnd_state.set_actions(self.dnd_state.selected_action, self.dnd_state.selected_action);
195 dnd_state.accept_mime_type(dnd_state.serial, Some(mime.to_string()));
196
197 _ = self.load_dnd(mime, false);
198 }
199
200 pub(crate) fn offer_enter(
201 &mut self,
202 x: f64,
203 y: f64,
204 surface: &WlSurface,
205 wl_data_device: &WlDataDevice,
206 ) {
207 if self.dnd_state.sender.is_none() {
208 return;
209 }
210 let Some(data_device) = self
211 .seats
212 .iter()
213 .find_map(|(_, s)| s.data_device.as_ref().filter(|dev| dev.inner() == wl_data_device))
214 else {
215 return;
216 };
217 let drag_offer = data_device.data().drag_offer();
218 if drag_offer.is_none() && self.dnd_state.source_content.is_none() {
219 return;
221 }
222 self.update_active_surface(surface, x, y, drag_offer.as_ref());
223 let Some((surface, id)) = self
224 .dnd_state
225 .active_surface
226 .as_ref()
227 .map(|(s, d)| (s.clone(), d.as_ref().map(|d| d.id)))
228 else {
229 return;
230 };
231 let Some(tx) = self.dnd_state.sender.as_ref() else {
232 return;
233 };
234 _ = tx.send(DndEvent::Offer(id, super::OfferEvent::Enter {
236 x,
237 y,
238 surface: surface.s,
239 mime_types: Vec::new(),
240 }));
241 }
242
243 pub(crate) fn offer_motion(&mut self, x: f64, y: f64, wl_data_device: &WlDataDevice) {
244 let Some(surface) = self.dnd_state.active_surface.clone().map(|(s, _)| s) else {
245 return;
246 };
247 let Some(data_device) = self
248 .seats
249 .iter()
250 .find_map(|(_, s)| s.data_device.as_ref().filter(|dev| dev.inner() == wl_data_device))
251 else {
252 return;
253 };
254 let drag_offer = data_device.data().drag_offer();
255 if drag_offer.is_none() && self.dnd_state.source_content.is_none() {
256 return;
258 }
259 self.update_active_surface(&surface.surface, x, y, drag_offer.as_ref());
260 let id = self.cur_id();
261 if let Some(tx) = self.dnd_state.sender.as_ref() {
262 _ = tx.send(DndEvent::Offer(id, super::OfferEvent::Motion { x, y }));
263 }
264 }
265
266 pub(crate) fn offer_leave(&mut self) {
267 if let Some(tx) = self.dnd_state.sender.as_ref() {
268 self.dnd_state.active_surface = None;
269 self.dnd_state.selected_action = DndAction::empty();
270 self.dnd_state.selected_mime = None;
271 _ = tx.send(DndEvent::Offer(None, super::OfferEvent::Leave))
272 }
273 }
274
275 pub(crate) fn handle_dnd_request(&mut self, r: DndRequest<T>) {
276 match r {
277 DndRequest::InitDnd(sender) => self.dnd_state.sender = Some(sender),
278 DndRequest::Surface(s, dests) => {
279 if dests.is_empty() {
280 self.dnd_state.destinations.remove(&s.surface.id());
281 } else {
282 self.dnd_state.destinations.insert(s.surface.id(), (s, dests));
283 }
284 },
285 DndRequest::StartDnd { internal, source, icon, content, actions } => {
286 _ = self.start_dnd(internal, source, icon, content, actions);
287 },
288 DndRequest::SetAction(a) => {
289 _ = self.user_selected_action(a);
290 },
291 DndRequest::DndEnd => {
292 if let Some(s) = self.dnd_state.icon_surface.take() {
293 _ = s.destroy();
294 }
295 self.dnd_state.source_content = None;
296 self.dnd_state.dnd_source = None;
297 self.pool.remove(&0);
298 },
299 DndRequest::Peek(mime_type) => {
300 if let Err(err) = self.load_dnd(mime_type, true) {
301 _ = self.reply_tx.send(Err(err));
302 }
303 },
304 };
305 }
306
307 fn start_dnd(
308 &mut self,
309 internal: bool,
310 source_surface: DndSurface<T>,
311 mut icon: Option<Icon<DndSurface<T>>>,
312 content: Box<dyn AsMimeTypes + Send>,
313 actions: DndAction,
314 ) -> std::io::Result<()> {
315 let latest = self
316 .latest_seat
317 .as_ref()
318 .ok_or_else(|| Error::new(ErrorKind::Other, "no events received on any seat"))?;
319 let seat = self
320 .seats
321 .get_mut(latest)
322 .ok_or_else(|| Error::new(ErrorKind::Other, "active seat lost"))?;
323 let serial = seat.latest_serial;
324
325 let data_device = seat
326 .data_device
327 .as_ref()
328 .ok_or_else(|| Error::new(ErrorKind::Other, "data device missing"))?;
329
330 let (icon_surface, buffer) = if let Some(i) = icon.take() {
331 match i {
332 Icon::Surface(s) => (Some(s.surface.clone()), None),
333 Icon::Buf { data, width, height, transparent } => {
334 let surface = self.compositor_state.create_surface(&self.queue_handle);
335 self.pool.remove(&0);
336 let (_, wl_buffer, buf) = self
337 .pool
338 .create_buffer(
339 width as i32,
340 width as i32 * 4,
341 height as i32,
342 &0,
343 if transparent { Format::Argb8888 } else { Format::Xrgb8888 },
344 )
345 .map_err(|err| Error::new(ErrorKind::Other, err))?;
346 buf.copy_from_slice(&data);
347
348 (Some(surface), Some((wl_buffer, width, height)))
349 },
350 }
351 } else {
352 (None, None)
353 };
354
355 if internal {
356 DragSource::start_internal_drag(
357 data_device,
358 &source_surface.surface,
359 icon_surface.as_ref(),
360 serial,
361 )
362 } else {
363 let mime_types = content.available();
364 let source = self
365 .data_device_manager_state
366 .as_ref()
367 .map(|s| {
368 s.create_drag_and_drop_source(
369 &self.queue_handle,
370 mime_types.iter().map(|m| m.as_ref()),
371 actions,
372 )
373 })
374 .ok_or_else(|| Error::new(ErrorKind::Other, "data device manager missing"))?;
375 source.start_drag(data_device, &source_surface.surface, icon_surface.as_ref(), serial);
376
377 self.dnd_state.dnd_source = Some(source);
378 self.dnd_state.source_content = Some(content);
379 self.dnd_state.source_actions = actions;
380 }
381
382 if let (Some((wl_buffer, width, height)), Some(surface)) = (buffer, icon_surface) {
383 surface.damage_buffer(0, 0, width as i32, height as i32);
384 surface.attach(Some(wl_buffer), 0, 0);
385 surface.commit();
386
387 self.dnd_state.icon_surface = Some(surface);
388 }
389
390 Ok(())
391 }
392
393 fn user_selected_action(&mut self, a: DndAction) -> std::io::Result<()> {
394 let latest = self
395 .latest_seat
396 .as_ref()
397 .ok_or_else(|| Error::new(ErrorKind::Other, "no events received on any seat"))?;
398 let seat = self
399 .seats
400 .get_mut(latest)
401 .ok_or_else(|| Error::new(ErrorKind::Other, "active seat lost"))?;
402
403 let offer = seat
404 .data_device
405 .as_ref()
406 .and_then(|d| d.data().drag_offer())
407 .ok_or_else(|| Error::new(ErrorKind::Other, "offer does not exist."))?;
408 offer.set_actions(a, a);
409
410 if let Some(mime_type) = self.dnd_state.selected_mime.clone() {
411 _ = self.load_dnd(mime_type, false);
412 }
413 Ok(())
414 }
415
416 pub fn load_dnd(&mut self, mut mime_type: MimeType, peek: bool) -> std::io::Result<()> {
418 let cur_id = self.cur_id();
419 let latest = self
420 .latest_seat
421 .as_ref()
422 .ok_or_else(|| Error::new(ErrorKind::Other, "no events received on any seat"))?;
423 let seat = self
424 .seats
425 .get_mut(latest)
426 .ok_or_else(|| Error::new(ErrorKind::Other, "active seat lost"))?;
427
428 let offer = seat
429 .data_device
430 .as_ref()
431 .and_then(|d| d.data().drag_offer())
432 .ok_or_else(|| Error::new(ErrorKind::Other, "offer does not exist."))?;
433
434 let read_pipe = { offer.receive(mime_type.to_string())? };
435
436 unsafe {
438 set_non_blocking(read_pipe.as_raw_fd())?;
439 }
440
441 let mut reader_buffer = [0; 4096];
442 let mut content = Vec::new();
443 let _ = self.loop_handle.insert_source(read_pipe, move |_, file, state| {
444 let file = unsafe { file.get_mut() };
445
446 loop {
447 match file.read(&mut reader_buffer) {
448 Ok(0) => {
449 if !peek {
451 offer.finish();
452 }
453
454 if peek {
455 _ = state
456 .reply_tx
457 .send(Ok((mem::take(&mut content), mem::take(&mut mime_type))));
458 } else if let Some(tx) = state.dnd_state.sender.as_ref() {
459 let _ = tx.send(DndEvent::Offer(cur_id, OfferEvent::Data {
460 data: mem::take(&mut content),
461 mime_type: mem::take(&mut mime_type),
462 }));
463 }
464 break PostAction::Remove;
465 },
466 Ok(n) => content.extend_from_slice(&reader_buffer[..n]),
467 Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue,
468 Err(err) => {
469 if peek {
470 let _ = state.reply_tx.send(Err(err));
471 }
472 break PostAction::Remove;
473 },
474 };
475 }
476 });
477
478 Ok(())
479 }
480
481 pub(crate) fn send_dnd_request(&self, write_pipe: WritePipe, mime: String) {
482 let Some(content) = self.dnd_state.source_content.as_ref() else {
483 return;
484 };
485 let Some(mime_type) = MimeType::find_allowed(&[mime], &content.available()) else {
486 return;
487 };
488
489 unsafe {
491 if set_non_blocking(write_pipe.as_raw_fd()).is_err() {
492 return;
493 }
494 }
495
496 let contents = content.as_bytes(&mime_type);
499 let Some(contents) = contents else {
500 return;
501 };
502
503 let mut written = 0;
504 let _ = self.loop_handle.insert_source(write_pipe, move |_, file, _| {
505 let file = unsafe { file.get_mut() };
506 loop {
507 match file.write(&contents[written..]) {
508 Ok(n) if written + n == contents.len() => {
509 written += n;
510 break PostAction::Remove;
511 },
512 Ok(n) => written += n,
513 Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue,
514 Err(_) => break PostAction::Remove,
515 }
516 }
517 });
518 }
519}