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