1use std::collections::{HashMap, VecDeque};
4use std::ffi::OsString;
5use std::io;
6use std::os::fd::AsFd;
7use std::sync::Arc;
8
9use os_pipe::{pipe, PipeReader, PipeWriter};
10use rustix::event::{PollFd, PollFlags};
11use wayland_backend::client::WaylandError;
12use wayland_client::globals::GlobalListContents;
13use wayland_client::protocol::wl_registry::WlRegistry;
14use wayland_client::protocol::wl_seat::WlSeat;
15use wayland_client::{delegate_dispatch, event_created_child, Dispatch, EventQueue};
16
17use crate::common::{self, initialize};
18use crate::data_control::{self, impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer};
19use crate::paste::{self, Error, Seat};
20
21struct State {
22 common: common::State,
23 offers: HashMap<data_control::Offer, Vec<String>>,
26 selection_events: VecDeque<SelectionEvent>,
27}
28
29impl State {
30 fn got_primary_selection(&self) -> bool {
31 self.selection_events
32 .iter()
33 .any(|event| event.clipboard == paste::ClipboardType::Primary)
34 }
35
36 fn push_selection_event(
37 &mut self,
38 clipboard: paste::ClipboardType,
39 seat: &WlSeat,
40 offer: Option<data_control::Offer>,
41 ) {
42 let offer = offer.map(|offer| {
43 let mime_types = self.offers.remove(&offer).unwrap_or_default();
45 SelectionOffer {
46 offer: OwnedOffer(offer),
47 mime_types,
48 }
49 });
50 self.selection_events.push_back(SelectionEvent {
51 clipboard,
52 seat: seat.clone(),
53 offer,
54 });
55 }
56}
57
58struct SelectionEvent {
60 clipboard: paste::ClipboardType,
61 seat: WlSeat,
62 offer: Option<SelectionOffer>,
63}
64
65struct SelectionOffer {
66 offer: OwnedOffer,
67 mime_types: Vec<String>,
68}
69
70struct OwnedOffer(data_control::Offer);
72
73impl Drop for OwnedOffer {
74 fn drop(&mut self) {
75 self.0.destroy();
76 }
77}
78
79delegate_dispatch!(State: [WlSeat: ()] => common::State);
80
81impl AsMut<common::State> for State {
82 fn as_mut(&mut self) -> &mut common::State {
83 &mut self.common
84 }
85}
86
87impl Dispatch<WlRegistry, GlobalListContents> for State {
88 fn event(
89 _state: &mut Self,
90 _proxy: &WlRegistry,
91 _event: <WlRegistry as wayland_client::Proxy>::Event,
92 _data: &GlobalListContents,
93 _conn: &wayland_client::Connection,
94 _qhandle: &wayland_client::QueueHandle<Self>,
95 ) {
96 }
97}
98
99impl_dispatch_manager!(State);
100
101impl_dispatch_device!(State, WlSeat, |state: &mut Self, event, seat: &WlSeat| {
102 match event {
103 Event::DataOffer { id } => {
104 let offer = data_control::Offer::from(id);
105 state.offers.insert(offer, Vec::new());
106 }
107 Event::Selection { id } => {
108 state.push_selection_event(
109 paste::ClipboardType::Regular,
110 seat,
111 id.map(data_control::Offer::from),
112 );
113 }
114 Event::Finished => {
115 let seat_data = state.common.seats.get_mut(seat).unwrap();
117 seat_data.set_device(None);
118 }
119 Event::PrimarySelection { id } => {
120 state.push_selection_event(
121 paste::ClipboardType::Primary,
122 seat,
123 id.map(data_control::Offer::from),
124 );
125 }
126 _ => (),
127 }
128});
129
130impl_dispatch_offer!(State, |state: &mut Self,
131 offer: data_control::Offer,
132 event| {
133 if let Event::Offer { mime_type } = event {
134 state.offers.get_mut(&offer).unwrap().push(mime_type);
135 }
136});
137
138#[derive(Clone)]
142pub struct CancelHandle(Arc<PipeWriter>);
143
144impl CancelHandle {
145 pub fn cancel(&self) {
149 let _ = rustix::io::write(&*self.0, &[0u8]);
150 }
151}
152
153#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]
155pub enum ClipboardType {
156 #[default]
158 Regular,
159 Primary,
164 Both,
169}
170
171impl ClipboardType {
172 fn includes(self, clipboard: paste::ClipboardType) -> bool {
173 match self {
174 ClipboardType::Regular => clipboard == paste::ClipboardType::Regular,
175 ClipboardType::Primary => clipboard == paste::ClipboardType::Primary,
176 ClipboardType::Both => true,
177 }
178 }
179}
180
181impl From<paste::ClipboardType> for ClipboardType {
182 fn from(clipboard: paste::ClipboardType) -> Self {
183 match clipboard {
184 paste::ClipboardType::Regular => ClipboardType::Regular,
185 paste::ClipboardType::Primary => ClipboardType::Primary,
186 }
187 }
188}
189
190pub enum ClipboardEvent<'a> {
192 Changed {
194 clipboard: paste::ClipboardType,
196 mime_types: Vec<String>,
198 offer: Offer<'a>,
200 },
201 Cleared {
203 clipboard: paste::ClipboardType,
205 },
206}
207
208pub struct Watcher {
217 queue: EventQueue<State>,
218 state: State,
219 clipboard: ClipboardType,
220 watched: WlSeat,
222 cancel_read: PipeReader,
225 cancel_write: Arc<PipeWriter>,
226}
227
228impl Watcher {
229 pub fn new(clipboard: ClipboardType, seat: Seat<'_>) -> Result<Self, Error> {
234 Self::with_socket(clipboard, seat, None)
235 }
236
237 pub(crate) fn with_socket(
239 clipboard: ClipboardType,
240 seat: Seat<'_>,
241 socket_name: Option<OsString>,
242 ) -> Result<Self, Error> {
243 let (mut queue, mut common) = initialize(clipboard != ClipboardType::Regular, socket_name)?;
244
245 if common.seats.is_empty() {
246 return Err(Error::NoSeats);
247 }
248
249 for (seat, data) in &mut common.seats {
250 let device =
251 common
252 .clipboard_manager
253 .get_data_device(seat, &queue.handle(), seat.clone());
254 data.set_device(Some(device));
255 }
256
257 let mut state = State {
258 common,
259 offers: HashMap::new(),
260 selection_events: VecDeque::new(),
261 };
262
263 queue
264 .roundtrip(&mut state)
265 .map_err(Error::WaylandCommunication)?;
266
267 if clipboard != ClipboardType::Regular && !state.got_primary_selection() {
268 return Err(Error::PrimarySelectionUnsupported);
269 }
270
271 let seats = &state.common.seats;
272 let watched = match seat {
273 Seat::Unspecified => seats.keys().next().cloned(),
274 Seat::Specific(name) => seats
275 .iter()
276 .find(|(_, data)| data.name.as_deref() == Some(name))
277 .map(|(seat, _)| seat.clone()),
278 };
279 let Some(watched) = watched else {
280 return Err(Error::SeatNotFound);
281 };
282
283 let (cancel_read, cancel_write) = pipe().map_err(Error::PipeCreation)?;
284
285 Ok(Watcher {
286 queue,
287 state,
288 clipboard,
289 watched,
290 cancel_read,
291 cancel_write: Arc::new(cancel_write),
292 })
293 }
294
295 pub fn cancel_handle(&self) -> CancelHandle {
300 CancelHandle(Arc::clone(&self.cancel_write))
301 }
302
303 pub fn next_event<'a>(&'a mut self) -> Result<Option<ClipboardEvent<'a>>, Error> {
309 while !self.front_matches() {
310 if self.wait()? {
311 return Ok(None);
312 }
313 }
314 Ok(Some(self.take_front_event()))
315 }
316
317 fn front_matches(&mut self) -> bool {
320 while let Some(event) = self.state.selection_events.front() {
321 if self.clipboard.includes(event.clipboard) && event.seat == self.watched {
322 return true;
323 }
324 self.state.selection_events.pop_front();
325 }
326 false
327 }
328
329 fn take_front_event<'a>(&'a mut self) -> ClipboardEvent<'a> {
332 let SelectionEvent {
333 clipboard, offer, ..
334 } = self.state.selection_events.pop_front().unwrap();
335 match offer {
336 Some(SelectionOffer { offer, mime_types }) => ClipboardEvent::Changed {
337 clipboard,
338 mime_types,
339 offer: Offer {
340 watcher: self,
341 offer,
342 },
343 },
344 None => ClipboardEvent::Cleared { clipboard },
345 }
346 }
347
348 fn wait(&mut self) -> Result<bool, Error> {
350 self.queue
351 .flush()
352 .map_err(|e| Error::WaylandCommunication(e.into()))?;
353
354 if let Some(guard) = self.queue.prepare_read() {
355 let wayland_fd = guard.connection_fd();
356 let mut poll_fds = [
357 PollFd::new(&wayland_fd, PollFlags::IN | PollFlags::ERR),
358 PollFd::new(&self.cancel_read, PollFlags::IN),
359 ];
360 loop {
361 match rustix::event::poll(&mut poll_fds, None) {
362 Ok(_) => break,
363 Err(rustix::io::Errno::INTR) => continue,
364 Err(e) => {
365 return Err(Error::WaylandCommunication(
366 WaylandError::Io(e.into()).into(),
367 ))
368 }
369 }
370 }
371 if poll_fds[1].revents().contains(PollFlags::IN) {
373 return Ok(true);
374 }
375 match guard.read() {
376 Ok(_) => {}
377 Err(WaylandError::Io(e)) if e.kind() == io::ErrorKind::WouldBlock => {}
378 Err(e) => return Err(Error::WaylandCommunication(e.into())),
379 }
380 }
381
382 self.queue
383 .dispatch_pending(&mut self.state)
384 .map_err(Error::WaylandCommunication)?;
385 Ok(false)
386 }
387}
388
389pub struct Offer<'a> {
391 watcher: &'a mut Watcher,
392 offer: OwnedOffer,
393}
394
395impl Offer<'_> {
396 pub fn receive(&mut self, mime_type: &str) -> Result<PipeReader, Error> {
400 let (read, write) = pipe().map_err(Error::PipeCreation)?;
401 self.offer.0.receive(mime_type.to_string(), write.as_fd());
402 drop(write);
403 self.watcher
404 .queue
405 .flush()
406 .map_err(|e| Error::WaylandCommunication(e.into()))?;
407 Ok(read)
408 }
409}