smithay_clipboard/
state.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::io::{Error, ErrorKind, Read, Result, Write};
4use std::mem;
5use std::os::unix::io::{AsRawFd, RawFd};
6use std::rc::Rc;
7use std::sync::mpsc::Sender;
8
9use sctk::data_device_manager::data_device::{DataDevice, DataDeviceHandler};
10use sctk::data_device_manager::data_offer::{DataOfferError, DataOfferHandler, DragOffer};
11use sctk::data_device_manager::data_source::{CopyPasteSource, DataSourceHandler};
12use sctk::data_device_manager::{DataDeviceManagerState, WritePipe};
13use sctk::primary_selection::device::{PrimarySelectionDevice, PrimarySelectionDeviceHandler};
14use sctk::primary_selection::selection::{PrimarySelectionSource, PrimarySelectionSourceHandler};
15use sctk::primary_selection::PrimarySelectionManagerState;
16use sctk::registry::{ProvidesRegistryState, RegistryState};
17use sctk::seat::pointer::{PointerData, PointerEvent, PointerEventKind, PointerHandler};
18use sctk::seat::{Capability, SeatHandler, SeatState};
19use sctk::{
20    delegate_data_device, delegate_pointer, delegate_primary_selection, delegate_registry,
21    delegate_seat, registry_handlers,
22};
23
24use sctk::reexports::calloop::{LoopHandle, PostAction};
25use sctk::reexports::client::globals::GlobalList;
26use sctk::reexports::client::protocol::wl_data_device::WlDataDevice;
27use sctk::reexports::client::protocol::wl_data_device_manager::DndAction;
28use sctk::reexports::client::protocol::wl_data_source::WlDataSource;
29use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
30use sctk::reexports::client::protocol::wl_pointer::WlPointer;
31use sctk::reexports::client::protocol::wl_seat::WlSeat;
32use sctk::reexports::client::protocol::wl_surface::WlSurface;
33use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle};
34use sctk::reexports::protocols::wp::primary_selection::zv1::client::{
35    zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1,
36    zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
37};
38use wayland_backend::client::ObjectId;
39
40use crate::mime::{normalize_to_lf, MimeType, ALLOWED_MIME_TYPES};
41
42pub 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
65impl 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
281impl 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
364impl 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
398impl 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
420impl 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
452impl 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
472impl ProvidesRegistryState for State {
473    registry_handlers![SeatState];
474
475    fn registry(&mut self) -> &mut RegistryState {
476        &mut self.registry_state
477    }
478}
479
480impl PrimarySelectionDeviceHandler for State {
481    fn selection(
482        &mut self,
483        _: &Connection,
484        _: &QueueHandle<Self>,
485        _: &ZwpPrimarySelectionDeviceV1,
486    ) {
487    }
488}
489
490impl 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(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| source.inner() != deleted)
509    }
510}
511
512impl 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
545delegate_seat!(State);
546delegate_pointer!(State);
547delegate_data_device!(State);
548delegate_primary_selection!(State);
549delegate_registry!(State);
550
551#[derive(Debug, Clone, Copy)]
552pub enum SelectionTarget {
553    /// The target is clipboard selection.
554    Clipboard,
555    /// The target is primary selection.
556    Primary,
557}
558
559#[derive(Debug, Default)]
560struct 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
571impl 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
587unsafe fn set_non_blocking(raw_fd: RawFd) -> std::io::Result<()> {
588    let flags = libc::fcntl(raw_fd, libc::F_GETFL);
589
590    if flags < 0 {
591        return Err(std::io::Error::last_os_error());
592    }
593
594    let result = libc::fcntl(raw_fd, 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}