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 if let Some(mime_types) = state.offers.get_mut(&offer) {
139 mime_types.push(mime_type);
140 }
141 }
142});
143
144#[derive(Clone)]
148pub struct CancelHandle(Arc<PipeWriter>);
149
150impl CancelHandle {
151 pub fn cancel(&self) {
155 let _ = rustix::io::write(&*self.0, &[0u8]);
156 }
157}
158
159#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]
161pub enum ClipboardType {
162 #[default]
164 Regular,
165 Primary,
170 Both,
175}
176
177impl ClipboardType {
178 fn includes(self, clipboard: paste::ClipboardType) -> bool {
179 match self {
180 ClipboardType::Regular => clipboard == paste::ClipboardType::Regular,
181 ClipboardType::Primary => clipboard == paste::ClipboardType::Primary,
182 ClipboardType::Both => true,
183 }
184 }
185}
186
187impl From<paste::ClipboardType> for ClipboardType {
188 fn from(clipboard: paste::ClipboardType) -> Self {
189 match clipboard {
190 paste::ClipboardType::Regular => ClipboardType::Regular,
191 paste::ClipboardType::Primary => ClipboardType::Primary,
192 }
193 }
194}
195
196pub enum ClipboardEvent<'a> {
198 Changed {
200 clipboard: paste::ClipboardType,
202 mime_types: Vec<String>,
204 offer: Offer<'a>,
206 },
207 Cleared {
209 clipboard: paste::ClipboardType,
211 },
212}
213
214pub struct Watcher {
223 queue: EventQueue<State>,
224 state: State,
225 clipboard: ClipboardType,
226 watched: WlSeat,
228 cancel_read: PipeReader,
231 cancel_write: Arc<PipeWriter>,
232}
233
234impl Watcher {
235 pub fn new(clipboard: ClipboardType, seat: Seat<'_>) -> Result<Self, Error> {
240 Self::with_socket(clipboard, seat, None)
241 }
242
243 pub(crate) fn with_socket(
245 clipboard: ClipboardType,
246 seat: Seat<'_>,
247 socket_name: Option<OsString>,
248 ) -> Result<Self, Error> {
249 let (mut queue, mut common) = initialize(clipboard != ClipboardType::Regular, socket_name)?;
250
251 if common.seats.is_empty() {
252 return Err(Error::NoSeats);
253 }
254
255 for (seat, data) in &mut common.seats {
256 let device =
257 common
258 .clipboard_manager
259 .get_data_device(seat, &queue.handle(), seat.clone());
260 data.set_device(Some(device));
261 }
262
263 let mut state = State {
264 common,
265 offers: HashMap::new(),
266 selection_events: VecDeque::new(),
267 };
268
269 queue
270 .roundtrip(&mut state)
271 .map_err(Error::WaylandCommunication)?;
272
273 if clipboard != ClipboardType::Regular && !state.got_primary_selection() {
274 return Err(Error::PrimarySelectionUnsupported);
275 }
276
277 let seats = &state.common.seats;
278 let watched = match seat {
279 Seat::Unspecified => seats.keys().next().cloned(),
280 Seat::Specific(name) => seats
281 .iter()
282 .find(|(_, data)| data.name.as_deref() == Some(name))
283 .map(|(seat, _)| seat.clone()),
284 };
285 let Some(watched) = watched else {
286 return Err(Error::SeatNotFound);
287 };
288
289 let (cancel_read, cancel_write) = pipe().map_err(Error::PipeCreation)?;
290
291 Ok(Watcher {
292 queue,
293 state,
294 clipboard,
295 watched,
296 cancel_read,
297 cancel_write: Arc::new(cancel_write),
298 })
299 }
300
301 pub fn cancel_handle(&self) -> CancelHandle {
306 CancelHandle(Arc::clone(&self.cancel_write))
307 }
308
309 pub fn next_event<'a>(&'a mut self) -> Result<Option<ClipboardEvent<'a>>, Error> {
315 while !self.front_matches() {
316 if self.wait()? {
317 return Ok(None);
318 }
319 }
320 Ok(Some(self.take_front_event()))
321 }
322
323 fn front_matches(&mut self) -> bool {
326 while let Some(event) = self.state.selection_events.front() {
327 if self.clipboard.includes(event.clipboard) && event.seat == self.watched {
328 return true;
329 }
330 self.state.selection_events.pop_front();
331 }
332 false
333 }
334
335 fn take_front_event<'a>(&'a mut self) -> ClipboardEvent<'a> {
338 let SelectionEvent {
339 clipboard, offer, ..
340 } = self.state.selection_events.pop_front().unwrap();
341 match offer {
342 Some(SelectionOffer { offer, mime_types }) => ClipboardEvent::Changed {
343 clipboard,
344 mime_types,
345 offer: Offer {
346 watcher: self,
347 offer,
348 },
349 },
350 None => ClipboardEvent::Cleared { clipboard },
351 }
352 }
353
354 fn wait(&mut self) -> Result<bool, Error> {
356 self.queue
357 .flush()
358 .map_err(|e| Error::WaylandCommunication(e.into()))?;
359
360 if let Some(guard) = self.queue.prepare_read() {
361 let wayland_fd = guard.connection_fd();
362 let mut poll_fds = [
363 PollFd::new(&wayland_fd, PollFlags::IN | PollFlags::ERR),
364 PollFd::new(&self.cancel_read, PollFlags::IN),
365 ];
366 loop {
367 match rustix::event::poll(&mut poll_fds, None) {
368 Ok(_) => break,
369 Err(rustix::io::Errno::INTR) => continue,
370 Err(e) => {
371 return Err(Error::WaylandCommunication(
372 WaylandError::Io(e.into()).into(),
373 ))
374 }
375 }
376 }
377 if poll_fds[1].revents().contains(PollFlags::IN) {
379 return Ok(true);
380 }
381 match guard.read() {
382 Ok(_) => {}
383 Err(WaylandError::Io(e)) if e.kind() == io::ErrorKind::WouldBlock => {}
384 Err(e) => return Err(Error::WaylandCommunication(e.into())),
385 }
386 }
387
388 self.queue
389 .dispatch_pending(&mut self.state)
390 .map_err(Error::WaylandCommunication)?;
391 Ok(false)
392 }
393}
394
395pub struct Offer<'a> {
397 watcher: &'a mut Watcher,
398 offer: OwnedOffer,
399}
400
401impl Offer<'_> {
402 pub fn receive(&mut self, mime_type: &str) -> Result<PipeReader, Error> {
406 let (read, write) = pipe().map_err(Error::PipeCreation)?;
407 self.offer.0.receive(mime_type.to_string(), write.as_fd());
408 drop(write);
409 self.watcher
410 .queue
411 .flush()
412 .map_err(|e| Error::WaylandCommunication(e.into()))?;
413 Ok(read)
414 }
415}