1 | use std::borrow::Cow; |
2 | use std::collections::HashMap; |
3 | use std::io::{Error, ErrorKind, Read, Result, Write}; |
4 | use std::mem; |
5 | use std::os::unix::io::{AsRawFd, RawFd}; |
6 | use std::rc::Rc; |
7 | use std::sync::mpsc::Sender; |
8 | |
9 | use sctk::data_device_manager::data_device::{DataDevice, DataDeviceHandler}; |
10 | use sctk::data_device_manager::data_offer::{DataOfferError, DataOfferHandler, DragOffer}; |
11 | use sctk::data_device_manager::data_source::{CopyPasteSource, DataSourceHandler}; |
12 | use sctk::data_device_manager::{DataDeviceManagerState, WritePipe}; |
13 | use sctk::primary_selection::device::{PrimarySelectionDevice, PrimarySelectionDeviceHandler}; |
14 | use sctk::primary_selection::selection::{PrimarySelectionSource, PrimarySelectionSourceHandler}; |
15 | use sctk::primary_selection::PrimarySelectionManagerState; |
16 | use sctk::registry::{ProvidesRegistryState, RegistryState}; |
17 | use sctk::seat::pointer::{PointerData, PointerEvent, PointerEventKind, PointerHandler}; |
18 | use sctk::seat::{Capability, SeatHandler, SeatState}; |
19 | use sctk::{ |
20 | delegate_data_device, delegate_pointer, delegate_primary_selection, delegate_registry, |
21 | delegate_seat, registry_handlers, |
22 | }; |
23 | |
24 | use sctk::reexports::calloop::{LoopHandle, PostAction}; |
25 | use sctk::reexports::client::globals::GlobalList; |
26 | use sctk::reexports::client::protocol::wl_data_device::WlDataDevice; |
27 | use sctk::reexports::client::protocol::wl_data_device_manager::DndAction; |
28 | use sctk::reexports::client::protocol::wl_data_source::WlDataSource; |
29 | use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; |
30 | use sctk::reexports::client::protocol::wl_pointer::WlPointer; |
31 | use sctk::reexports::client::protocol::wl_seat::WlSeat; |
32 | use sctk::reexports::client::protocol::wl_surface::WlSurface; |
33 | use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle}; |
34 | use sctk::reexports::protocols::wp::primary_selection::zv1::client::{ |
35 | zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, |
36 | zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, |
37 | }; |
38 | use wayland_backend::client::ObjectId; |
39 | |
40 | use crate::mime::{normalize_to_lf, MimeType, ALLOWED_MIME_TYPES}; |
41 | |
42 | pub struct State { |
43 | pub primary_selection_manager_state: Option<PrimarySelectionManagerState>, |
44 | pub data_device_manager_state: Option<DataDeviceManagerState>, |
45 | pub reply_tx: Sender<Result<String>>, |
46 | pub exit: bool, |
47 | |
48 | registry_state: RegistryState, |
49 | seat_state: SeatState, |
50 | |
51 | seats: HashMap<ObjectId, ClipboardSeatState>, |
52 | /// The latest seat which got an event. |
53 | latest_seat: Option<ObjectId>, |
54 | |
55 | loop_handle: LoopHandle<'static, Self>, |
56 | queue_handle: QueueHandle<Self>, |
57 | |
58 | primary_sources: Vec<PrimarySelectionSource>, |
59 | primary_selection_content: Rc<[u8]>, |
60 | |
61 | data_sources: Vec<CopyPasteSource>, |
62 | data_selection_content: Rc<[u8]>, |
63 | } |
64 | |
65 | impl State { |
66 | #[must_use ] |
67 | pub fn new( |
68 | globals: &GlobalList, |
69 | queue_handle: &QueueHandle<Self>, |
70 | loop_handle: LoopHandle<'static, Self>, |
71 | reply_tx: Sender<Result<String>>, |
72 | ) -> Option<Self> { |
73 | let mut seats = HashMap::new(); |
74 | |
75 | let data_device_manager_state = DataDeviceManagerState::bind(globals, queue_handle).ok(); |
76 | let primary_selection_manager_state = |
77 | PrimarySelectionManagerState::bind(globals, queue_handle).ok(); |
78 | |
79 | // When both globals are not available nothing could be done. |
80 | if data_device_manager_state.is_none() && primary_selection_manager_state.is_none() { |
81 | return None; |
82 | } |
83 | |
84 | let seat_state = SeatState::new(globals, queue_handle); |
85 | for seat in seat_state.seats() { |
86 | seats.insert(seat.id(), Default::default()); |
87 | } |
88 | |
89 | Some(Self { |
90 | registry_state: RegistryState::new(globals), |
91 | primary_selection_content: Rc::from([]), |
92 | data_selection_content: Rc::from([]), |
93 | queue_handle: queue_handle.clone(), |
94 | primary_selection_manager_state, |
95 | primary_sources: Vec::new(), |
96 | data_device_manager_state, |
97 | data_sources: Vec::new(), |
98 | latest_seat: None, |
99 | loop_handle, |
100 | exit: false, |
101 | seat_state, |
102 | reply_tx, |
103 | seats, |
104 | }) |
105 | } |
106 | |
107 | /// Store selection for the given target. |
108 | /// |
109 | /// Selection source is only created when `Some(())` is returned. |
110 | pub fn store_selection(&mut self, ty: SelectionTarget, contents: String) -> Option<()> { |
111 | let latest = self.latest_seat.as_ref()?; |
112 | let seat = self.seats.get_mut(latest)?; |
113 | |
114 | if !seat.has_focus { |
115 | return None; |
116 | } |
117 | |
118 | let contents = Rc::from(contents.into_bytes()); |
119 | |
120 | match ty { |
121 | SelectionTarget::Clipboard => { |
122 | let mgr = self.data_device_manager_state.as_ref()?; |
123 | self.data_selection_content = contents; |
124 | let source = |
125 | mgr.create_copy_paste_source(&self.queue_handle, ALLOWED_MIME_TYPES.iter()); |
126 | source.set_selection(seat.data_device.as_ref().unwrap(), seat.latest_serial); |
127 | self.data_sources.push(source); |
128 | }, |
129 | SelectionTarget::Primary => { |
130 | let mgr = self.primary_selection_manager_state.as_ref()?; |
131 | self.primary_selection_content = contents; |
132 | let source = |
133 | mgr.create_selection_source(&self.queue_handle, ALLOWED_MIME_TYPES.iter()); |
134 | source.set_selection(seat.primary_device.as_ref().unwrap(), seat.latest_serial); |
135 | self.primary_sources.push(source); |
136 | }, |
137 | } |
138 | |
139 | Some(()) |
140 | } |
141 | |
142 | /// Load selection for the given target. |
143 | pub fn load_selection(&mut self, ty: SelectionTarget) -> Result<()> { |
144 | let latest = self |
145 | .latest_seat |
146 | .as_ref() |
147 | .ok_or_else(|| Error::new(ErrorKind::Other, "no events received on any seat" ))?; |
148 | let seat = self |
149 | .seats |
150 | .get_mut(latest) |
151 | .ok_or_else(|| Error::new(ErrorKind::Other, "active seat lost" ))?; |
152 | |
153 | if !seat.has_focus { |
154 | return Err(Error::new(ErrorKind::Other, "client doesn't have focus" )); |
155 | } |
156 | |
157 | let (read_pipe, mime_type) = match ty { |
158 | SelectionTarget::Clipboard => { |
159 | let selection = seat |
160 | .data_device |
161 | .as_ref() |
162 | .and_then(|data| data.data().selection_offer()) |
163 | .ok_or_else(|| Error::new(ErrorKind::Other, "selection is empty" ))?; |
164 | |
165 | let mime_type = |
166 | selection.with_mime_types(MimeType::find_allowed).ok_or_else(|| { |
167 | Error::new(ErrorKind::NotFound, "supported mime-type is not found" ) |
168 | })?; |
169 | |
170 | ( |
171 | selection.receive(mime_type.to_string()).map_err(|err| match err { |
172 | DataOfferError::InvalidReceive => { |
173 | Error::new(ErrorKind::Other, "offer is not ready yet" ) |
174 | }, |
175 | DataOfferError::Io(err) => err, |
176 | })?, |
177 | mime_type, |
178 | ) |
179 | }, |
180 | SelectionTarget::Primary => { |
181 | let selection = seat |
182 | .primary_device |
183 | .as_ref() |
184 | .and_then(|data| data.data().selection_offer()) |
185 | .ok_or_else(|| Error::new(ErrorKind::Other, "selection is empty" ))?; |
186 | |
187 | let mime_type = |
188 | selection.with_mime_types(MimeType::find_allowed).ok_or_else(|| { |
189 | Error::new(ErrorKind::NotFound, "supported mime-type is not found" ) |
190 | })?; |
191 | |
192 | (selection.receive(mime_type.to_string())?, mime_type) |
193 | }, |
194 | }; |
195 | |
196 | // Mark FD as non-blocking so we won't block ourselves. |
197 | unsafe { |
198 | set_non_blocking(read_pipe.as_raw_fd())?; |
199 | } |
200 | |
201 | let mut reader_buffer = [0; 4096]; |
202 | let mut content = Vec::new(); |
203 | let _ = self.loop_handle.insert_source(read_pipe, move |_, file, state| { |
204 | let file = unsafe { file.get_mut() }; |
205 | loop { |
206 | match file.read(&mut reader_buffer) { |
207 | Ok(0) => { |
208 | let utf8 = String::from_utf8_lossy(&content); |
209 | let content = match utf8 { |
210 | Cow::Borrowed(_) => { |
211 | // Don't clone the read data. |
212 | let mut to_send = Vec::new(); |
213 | mem::swap(&mut content, &mut to_send); |
214 | String::from_utf8(to_send).unwrap() |
215 | }, |
216 | Cow::Owned(content) => content, |
217 | }; |
218 | |
219 | // Post-process the content according to mime type. |
220 | let content = match mime_type { |
221 | MimeType::TextPlainUtf8 | MimeType::TextPlain => { |
222 | normalize_to_lf(content) |
223 | }, |
224 | MimeType::Utf8String => content, |
225 | }; |
226 | |
227 | let _ = state.reply_tx.send(Ok(content)); |
228 | break PostAction::Remove; |
229 | }, |
230 | Ok(n) => content.extend_from_slice(&reader_buffer[..n]), |
231 | Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue, |
232 | Err(err) => { |
233 | let _ = state.reply_tx.send(Err(err)); |
234 | break PostAction::Remove; |
235 | }, |
236 | }; |
237 | } |
238 | }); |
239 | |
240 | Ok(()) |
241 | } |
242 | |
243 | fn send_request(&mut self, ty: SelectionTarget, write_pipe: WritePipe, mime: String) { |
244 | // We can only send strings, so don't do anything with the mime-type. |
245 | if MimeType::find_allowed(&[mime]).is_none() { |
246 | return; |
247 | } |
248 | |
249 | // Mark FD as non-blocking so we won't block ourselves. |
250 | unsafe { |
251 | if set_non_blocking(write_pipe.as_raw_fd()).is_err() { |
252 | return; |
253 | } |
254 | } |
255 | |
256 | // Don't access the content on the state directly, since it could change during |
257 | // the send. |
258 | let contents = match ty { |
259 | SelectionTarget::Clipboard => self.data_selection_content.clone(), |
260 | SelectionTarget::Primary => self.primary_selection_content.clone(), |
261 | }; |
262 | |
263 | let mut written = 0; |
264 | let _ = self.loop_handle.insert_source(write_pipe, move |_, file, _| { |
265 | let file = unsafe { file.get_mut() }; |
266 | loop { |
267 | match file.write(&contents[written..]) { |
268 | Ok(n) if written + n == contents.len() => { |
269 | written += n; |
270 | break PostAction::Remove; |
271 | }, |
272 | Ok(n) => written += n, |
273 | Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue, |
274 | Err(_) => break PostAction::Remove, |
275 | } |
276 | } |
277 | }); |
278 | } |
279 | } |
280 | |
281 | impl SeatHandler for State { |
282 | fn seat_state(&mut self) -> &mut SeatState { |
283 | &mut self.seat_state |
284 | } |
285 | |
286 | fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: WlSeat) { |
287 | self.seats.insert(seat.id(), Default::default()); |
288 | } |
289 | |
290 | fn new_capability( |
291 | &mut self, |
292 | _: &Connection, |
293 | qh: &QueueHandle<Self>, |
294 | seat: WlSeat, |
295 | capability: Capability, |
296 | ) { |
297 | let seat_state = self.seats.get_mut(&seat.id()).unwrap(); |
298 | |
299 | match capability { |
300 | Capability::Keyboard => { |
301 | seat_state.keyboard = Some(seat.get_keyboard(qh, seat.id())); |
302 | |
303 | // Selection sources are tied to the keyboard, so add/remove decives |
304 | // when we gain/loss capability. |
305 | |
306 | if seat_state.data_device.is_none() && self.data_device_manager_state.is_some() { |
307 | seat_state.data_device = self |
308 | .data_device_manager_state |
309 | .as_ref() |
310 | .map(|mgr| mgr.get_data_device(qh, &seat)); |
311 | } |
312 | |
313 | if seat_state.primary_device.is_none() |
314 | && self.primary_selection_manager_state.is_some() |
315 | { |
316 | seat_state.primary_device = self |
317 | .primary_selection_manager_state |
318 | .as_ref() |
319 | .map(|mgr| mgr.get_selection_device(qh, &seat)); |
320 | } |
321 | }, |
322 | Capability::Pointer => { |
323 | seat_state.pointer = self.seat_state.get_pointer(qh, &seat).ok(); |
324 | }, |
325 | _ => (), |
326 | } |
327 | } |
328 | |
329 | fn remove_capability( |
330 | &mut self, |
331 | _: &Connection, |
332 | _: &QueueHandle<Self>, |
333 | seat: WlSeat, |
334 | capability: Capability, |
335 | ) { |
336 | let seat_state = self.seats.get_mut(&seat.id()).unwrap(); |
337 | match capability { |
338 | Capability::Keyboard => { |
339 | seat_state.data_device = None; |
340 | seat_state.primary_device = None; |
341 | |
342 | if let Some(keyboard) = seat_state.keyboard.take() { |
343 | if keyboard.version() >= 3 { |
344 | keyboard.release() |
345 | } |
346 | } |
347 | }, |
348 | Capability::Pointer => { |
349 | if let Some(pointer) = seat_state.pointer.take() { |
350 | if pointer.version() >= 3 { |
351 | pointer.release() |
352 | } |
353 | } |
354 | }, |
355 | _ => (), |
356 | } |
357 | } |
358 | |
359 | fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: WlSeat) { |
360 | self.seats.remove(&seat.id()); |
361 | } |
362 | } |
363 | |
364 | impl PointerHandler for State { |
365 | fn pointer_frame( |
366 | &mut self, |
367 | _: &Connection, |
368 | _: &QueueHandle<Self>, |
369 | pointer: &WlPointer, |
370 | events: &[PointerEvent], |
371 | ) { |
372 | let seat = pointer.data::<PointerData>().unwrap().seat(); |
373 | let seat_id = seat.id(); |
374 | let seat_state = match self.seats.get_mut(&seat_id) { |
375 | Some(seat_state) => seat_state, |
376 | None => return, |
377 | }; |
378 | |
379 | let mut updated_serial = false; |
380 | for event in events { |
381 | match event.kind { |
382 | PointerEventKind::Press { serial, .. } |
383 | | PointerEventKind::Release { serial, .. } => { |
384 | updated_serial = true; |
385 | seat_state.latest_serial = serial; |
386 | }, |
387 | _ => (), |
388 | } |
389 | } |
390 | |
391 | // Only update the seat we're using when the serial got updated. |
392 | if updated_serial { |
393 | self.latest_seat = Some(seat_id); |
394 | } |
395 | } |
396 | } |
397 | |
398 | impl DataDeviceHandler for State { |
399 | fn enter( |
400 | &mut self, |
401 | _: &Connection, |
402 | _: &QueueHandle<Self>, |
403 | _: &WlDataDevice, |
404 | _: f64, |
405 | _: f64, |
406 | _: &WlSurface, |
407 | ) { |
408 | } |
409 | |
410 | fn leave(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {} |
411 | |
412 | fn motion(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice, _: f64, _: f64) {} |
413 | |
414 | fn drop_performed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {} |
415 | |
416 | // The selection is finished and ready to be used. |
417 | fn selection(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataDevice) {} |
418 | } |
419 | |
420 | impl DataSourceHandler for State { |
421 | fn send_request( |
422 | &mut self, |
423 | _: &Connection, |
424 | _: &QueueHandle<Self>, |
425 | _: &WlDataSource, |
426 | mime: String, |
427 | write_pipe: WritePipe, |
428 | ) { |
429 | self.send_request(SelectionTarget::Clipboard, write_pipe, mime) |
430 | } |
431 | |
432 | fn cancelled(&mut self, _: &Connection, _: &QueueHandle<Self>, deleted: &WlDataSource) { |
433 | self.data_sources.retain(|source| source.inner() != deleted) |
434 | } |
435 | |
436 | fn accept_mime( |
437 | &mut self, |
438 | _: &Connection, |
439 | _: &QueueHandle<Self>, |
440 | _: &WlDataSource, |
441 | _: Option<String>, |
442 | ) { |
443 | } |
444 | |
445 | fn dnd_dropped(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource) {} |
446 | |
447 | fn action(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource, _: DndAction) {} |
448 | |
449 | fn dnd_finished(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlDataSource) {} |
450 | } |
451 | |
452 | impl DataOfferHandler for State { |
453 | fn source_actions( |
454 | &mut self, |
455 | _: &Connection, |
456 | _: &QueueHandle<Self>, |
457 | _: &mut DragOffer, |
458 | _: DndAction, |
459 | ) { |
460 | } |
461 | |
462 | fn selected_action( |
463 | &mut self, |
464 | _: &Connection, |
465 | _: &QueueHandle<Self>, |
466 | _: &mut DragOffer, |
467 | _: DndAction, |
468 | ) { |
469 | } |
470 | } |
471 | |
472 | impl ProvidesRegistryState for State { |
473 | registry_handlers![SeatState]; |
474 | |
475 | fn registry(&mut self) -> &mut RegistryState { |
476 | &mut self.registry_state |
477 | } |
478 | } |
479 | |
480 | impl PrimarySelectionDeviceHandler for State { |
481 | fn selection( |
482 | &mut self, |
483 | _: &Connection, |
484 | _: &QueueHandle<Self>, |
485 | _: &ZwpPrimarySelectionDeviceV1, |
486 | ) { |
487 | } |
488 | } |
489 | |
490 | impl PrimarySelectionSourceHandler for State { |
491 | fn send_request( |
492 | &mut self, |
493 | _: &Connection, |
494 | _: &QueueHandle<Self>, |
495 | _: &ZwpPrimarySelectionSourceV1, |
496 | mime: String, |
497 | write_pipe: WritePipe, |
498 | ) { |
499 | self.send_request(ty:SelectionTarget::Primary, write_pipe, mime); |
500 | } |
501 | |
502 | fn cancelled( |
503 | &mut self, |
504 | _: &Connection, |
505 | _: &QueueHandle<Self>, |
506 | deleted: &ZwpPrimarySelectionSourceV1, |
507 | ) { |
508 | self.primary_sources.retain(|source: &PrimarySelectionSource| source.inner() != deleted) |
509 | } |
510 | } |
511 | |
512 | impl Dispatch<WlKeyboard, ObjectId, State> for State { |
513 | fn event( |
514 | state: &mut State, |
515 | _: &WlKeyboard, |
516 | event: <WlKeyboard as sctk::reexports::client::Proxy>::Event, |
517 | data: &ObjectId, |
518 | _: &Connection, |
519 | _: &QueueHandle<State>, |
520 | ) { |
521 | use sctk::reexports::client::protocol::wl_keyboard::Event as WlKeyboardEvent; |
522 | let seat_state = match state.seats.get_mut(data) { |
523 | Some(seat_state) => seat_state, |
524 | None => return, |
525 | }; |
526 | match event { |
527 | WlKeyboardEvent::Key { serial, .. } | WlKeyboardEvent::Modifiers { serial, .. } => { |
528 | seat_state.latest_serial = serial; |
529 | state.latest_seat = Some(data.clone()); |
530 | }, |
531 | // NOTE both selections rely on keyboard focus. |
532 | WlKeyboardEvent::Enter { serial, .. } => { |
533 | seat_state.latest_serial = serial; |
534 | seat_state.has_focus = true; |
535 | }, |
536 | WlKeyboardEvent::Leave { .. } => { |
537 | seat_state.latest_serial = 0; |
538 | seat_state.has_focus = false; |
539 | }, |
540 | _ => (), |
541 | } |
542 | } |
543 | } |
544 | |
545 | delegate_seat!(State); |
546 | delegate_pointer!(State); |
547 | delegate_data_device!(State); |
548 | delegate_primary_selection!(State); |
549 | delegate_registry!(State); |
550 | |
551 | #[derive (Debug, Clone, Copy)] |
552 | pub enum SelectionTarget { |
553 | /// The target is clipboard selection. |
554 | Clipboard, |
555 | /// The target is primary selection. |
556 | Primary, |
557 | } |
558 | |
559 | #[derive (Debug, Default)] |
560 | struct ClipboardSeatState { |
561 | keyboard: Option<WlKeyboard>, |
562 | pointer: Option<WlPointer>, |
563 | data_device: Option<DataDevice>, |
564 | primary_device: Option<PrimarySelectionDevice>, |
565 | has_focus: bool, |
566 | |
567 | /// The latest serial used to set the selection content. |
568 | latest_serial: u32, |
569 | } |
570 | |
571 | impl Drop for ClipboardSeatState { |
572 | fn drop(&mut self) { |
573 | if let Some(keyboard) = self.keyboard.take() { |
574 | if keyboard.version() >= 3 { |
575 | keyboard.release(); |
576 | } |
577 | } |
578 | |
579 | if let Some(pointer) = self.pointer.take() { |
580 | if pointer.version() >= 3 { |
581 | pointer.release(); |
582 | } |
583 | } |
584 | } |
585 | } |
586 | |
587 | unsafe fn set_non_blocking(raw_fd: RawFd) -> std::io::Result<()> { |
588 | let flags: i32 = libc::fcntl(raw_fd, cmd:libc::F_GETFL); |
589 | |
590 | if flags < 0 { |
591 | return Err(std::io::Error::last_os_error()); |
592 | } |
593 | |
594 | let result: i32 = libc::fcntl(raw_fd, cmd:libc::F_SETFL, flags | libc::O_NONBLOCK); |
595 | if result < 0 { |
596 | return Err(std::io::Error::last_os_error()); |
597 | } |
598 | |
599 | Ok(()) |
600 | } |
601 | |