t_rust_less_lib/clipboard/
unix_wayland.rs1use std::{
2 cell::RefCell,
3 error::Error,
4 fs::File,
5 io::Write,
6 os::unix::io::FromRawFd,
7 rc::Rc,
8 sync::{
9 atomic::{AtomicBool, Ordering},
10 Arc, Mutex, RwLock,
11 },
12 thread,
13};
14
15use log::{debug, error};
16use wayland_client::{global_filter, protocol::wl_seat::WlSeat, Display, GlobalManager, Main};
17use wayland_protocols::wlr::unstable::data_control::v1::client::{
18 zwlr_data_control_manager_v1::ZwlrDataControlManagerV1, zwlr_data_control_source_v1::Event,
19};
20use zeroize::Zeroize;
21
22use crate::api::{ClipboardProviding, EventData, EventHub};
23use crate::clipboard::selection_provider_holder::SelectionProviderHolder;
24
25use super::{ClipboardCommon, ClipboardError, ClipboardResult, SelectionProvider};
26
27const TEXT_MIMES: &[&str] = &[
28 "text/plain;charset=utf-8",
29 "text/plain",
30 "STRING",
31 "UTF8_STRING",
32 "TEXT",
33];
34
35struct Context {
36 display: Display,
37 open: AtomicBool,
38 cancel: AtomicBool,
39 provider_holder: RwLock<SelectionProviderHolder>,
40}
41
42impl Context {
43 fn new<T>(display: Display, provider: T) -> Self
44 where
45 T: SelectionProvider + 'static,
46 {
47 Context {
48 display,
49 open: AtomicBool::new(false),
50 cancel: AtomicBool::new(false),
51 provider_holder: RwLock::new(SelectionProviderHolder::new(provider)),
52 }
53 }
54
55 fn is_open(&self) -> bool {
56 self.open.load(Ordering::Relaxed)
57 }
58
59 fn currently_providing(&self) -> Option<ClipboardProviding> {
60 self.provider_holder.read().ok()?.current_selection()
61 }
62
63 fn provide_next(&self) {
64 if let Ok(mut provider_holder) = self.provider_holder.write() {
65 provider_holder.get_value();
66 }
67 }
68
69 fn destroy(&self) {
70 self.cancel.store(true, Ordering::Relaxed)
71 }
72}
73
74pub struct Clipboard {
75 context: Arc<Context>,
76 handle: Mutex<Option<thread::JoinHandle<()>>>,
77}
78
79impl ClipboardCommon for Clipboard {
80 fn new<T>(selection_provider: T, event_hub: Arc<dyn EventHub>) -> ClipboardResult<Self>
81 where
82 T: SelectionProvider + Clone + 'static,
83 {
84 let display = Display::connect_to_env()?;
85
86 debug!("Got display: {}", display.id());
87
88 match selection_provider.current_selection() {
89 Some(providing) => event_hub.send(EventData::ClipboardProviding(providing)),
90 None => return Err(ClipboardError::Other("Empty provider".to_string())),
91 };
92
93 let context = Arc::new(Context::new(display, selection_provider));
94
95 let handle = thread::spawn({
96 let cloned = context.clone();
97 move || {
98 if let Err(err) = try_run(cloned.clone()) {
99 cloned.open.store(false, Ordering::Relaxed);
100 error!("Wayland clipboard error: {}", err);
101 }
102 }
103 });
104
105 Ok(Clipboard {
106 context,
107 handle: Mutex::new(Some(handle)),
108 })
109 }
110
111 fn is_open(&self) -> bool {
112 self.context.is_open()
113 }
114
115 fn currently_providing(&self) -> Option<ClipboardProviding> {
116 self.context.currently_providing()
117 }
118
119 fn provide_next(&self) {
120 self.context.provide_next()
121 }
122
123 fn destroy(&self) {
124 self.context.destroy()
125 }
126
127 fn wait(&self) -> ClipboardResult<()> {
128 let mut maybe_handle = self.handle.lock()?;
129 if let Some(handle) = maybe_handle.take() {
130 handle
131 .join()
132 .map_err(|_| ClipboardError::Other("wait timeout".to_string()))?;
133 }
134 Ok(())
135 }
136}
137
138fn try_run(context: Arc<Context>) -> Result<(), Box<dyn Error>> {
139 let mut queue = context.display.create_event_queue();
140 let display = context.display.attach(queue.token());
141 let seats = Rc::new(RefCell::new(vec![]));
142 let mut devices = vec![];
143
144 let seats_cloned = seats.clone();
145
146 let manager = GlobalManager::new_with_cb(
147 &display,
148 global_filter!([WlSeat, 2, move |seat: Main<WlSeat>, _: DispatchData| {
149 seats_cloned.borrow_mut().push(seat);
150 }]),
151 );
152
153 queue.sync_roundtrip(&mut (), |_, _, _| {})?;
154
155 debug!("Seats: {:?}", seats.borrow());
156 debug!("Globals: {:?}", manager.list());
157
158 let clipboard_manager: Main<ZwlrDataControlManagerV1> = manager.instantiate_exact(1)?;
159
160 let data_source = clipboard_manager.create_data_source();
161 let context_cloned = context.clone();
162
163 data_source.quick_assign(move |_, event, _| match event {
164 Event::Send { mime_type, fd } if TEXT_MIMES.contains(&mime_type.as_str()) => {
165 debug!("Event send: {} {}", mime_type, fd);
166 match context_cloned.provider_holder.write() {
167 Ok(mut selection_provider) => {
168 if let Some(mut content) = selection_provider.get_value() {
169 let mut f = unsafe { File::from_raw_fd(fd) };
170 f.write_all(content.as_bytes()).ok();
171 content.zeroize();
172 } else {
173 debug!("No more values");
174 context_cloned.cancel.store(true, Ordering::Relaxed);
175 }
176 }
177 Err(err) => {
178 error!("Lock error: {}", err);
179 context_cloned.cancel.store(true, Ordering::Relaxed);
180 }
181 }
182 }
183 Event::Cancelled => {
184 debug!("Event cancel: Lost ownership");
185 context_cloned.cancel.store(true, Ordering::Relaxed)
186 }
187 _ => (),
188 });
189
190 for &mime_type in TEXT_MIMES {
191 data_source.offer(mime_type.to_string());
192 }
193
194 for seat in seats.borrow_mut().iter_mut() {
195 let device = clipboard_manager.get_data_device(seat);
196 device.quick_assign(|_, _, _| {});
197 device.set_selection(Some(&data_source));
198 devices.push(device);
199 }
200
201 debug!("Start event loop");
202 context.open.store(true, Ordering::Relaxed);
203 while !context.cancel.load(Ordering::Relaxed) {
204 queue.dispatch(&mut (), |_, _, _| {})?;
205 }
206 context.open.store(false, Ordering::Relaxed);
207 debug!("End event loop");
208
209 for device in devices {
210 device.destroy();
211 }
212 data_source.destroy();
213 for seat in seats.borrow_mut().iter_mut() {
214 seat.detach();
215 }
216 display.detach();
217
218 Ok(())
219}