t_rust_less_lib/clipboard/
unix_x11.rs1use crate::api::{ClipboardProviding, EventData, EventHub};
2use crate::clipboard::selection_provider_holder::SelectionProviderHolder;
3use crate::clipboard::{ClipboardError, ClipboardResult, SelectionProvider};
4use log::{debug, error};
5use std::ffi::CString;
6use std::mem::MaybeUninit;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::{Arc, Mutex, RwLock};
9use std::{env, thread};
10use x11::xlib;
11use zeroize::Zeroize;
12
13use super::ClipboardCommon;
14
15#[derive(Debug)]
16struct Atoms {
17 pub primary: xlib::Atom,
18 pub clipboard: xlib::Atom,
19 pub targets: xlib::Atom,
20 pub string: xlib::Atom,
21 pub utf8_string: xlib::Atom,
22}
23
24struct Context {
25 display: *mut xlib::Display,
26 window: xlib::Window,
27 atoms: Atoms,
28 open: AtomicBool,
29 provider_holder: RwLock<SelectionProviderHolder>,
30 event_hub: Arc<dyn EventHub>,
31}
32
33impl Context {
34 fn new<T>(event_hub: Arc<dyn EventHub>, provider: T) -> ClipboardResult<Self>
35 where
36 T: SelectionProvider + 'static,
37 {
38 unsafe {
39 let display_name = env::var("DISPLAY")?;
40 let c_display_name = CString::new(display_name)?;
41 let display = xlib::XOpenDisplay(c_display_name.as_ptr());
42
43 if display.is_null() {
44 return Err(ClipboardError::Other("Cannot open display".to_string()));
45 }
46 let root = xlib::XDefaultRootWindow(display);
47 let black = xlib::XBlackPixel(display, xlib::XDefaultScreen(display));
48 let window = xlib::XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, black, black);
49
50 debug!("Window id: {window}");
51
52 xlib::XSelectInput(display, window, xlib::StructureNotifyMask | xlib::PropertyChangeMask);
53
54 let primary = Self::get_atom(display, "PRIMARY");
55 if primary != xlib::XA_PRIMARY {
56 debug!("XA_PRIMARY is not named PRIMARY");
57 }
58 let clipboard = Self::get_atom(display, "CLIPBOARD");
59 let targets = Self::get_atom(display, "TARGETS");
60 let string = Self::get_atom(display, "STRING");
61 if string != xlib::XA_STRING {
62 debug!("XA_STRING is not named STRING");
63 }
64 let utf8_string = Self::get_atom(display, "UTF8_STRING");
65
66 let atoms = Atoms {
67 primary,
68 clipboard,
69 targets,
70 string,
71 utf8_string,
72 };
73
74 debug!("{atoms:?}");
75
76 Ok(Context {
77 display,
78 window,
79 atoms,
80 open: AtomicBool::new(true),
81 provider_holder: RwLock::new(SelectionProviderHolder::new(provider)),
82 event_hub,
83 })
84 }
85 }
86
87 fn get_atom(display: *mut xlib::Display, name: &str) -> xlib::Atom {
88 unsafe {
89 let c_name = CString::new(name).unwrap();
90 xlib::XInternAtom(display, c_name.as_ptr(), xlib::False)
91 }
92 }
93
94 fn destroy(&self) {
95 if self.open.swap(false, Ordering::Relaxed) {
96 unsafe {
97 xlib::XDestroyWindow(self.display, self.window);
98 xlib::XFlush(self.display);
99 }
100 }
101 }
102
103 fn own_selection(&self) -> bool {
104 unsafe {
105 xlib::XSetSelectionOwner(self.display, self.atoms.clipboard, self.window, xlib::CurrentTime);
106
107 let owner = xlib::XGetSelectionOwner(self.display, self.atoms.clipboard);
108 if owner != self.window {
109 debug!("Failed taking ownership of {}", self.atoms.clipboard);
110 return false;
111 }
112 }
113
114 true
115 }
116
117 fn clear_selection(&self) {
118 unsafe {
119 for selection in &[self.atoms.primary, self.atoms.clipboard] {
120 xlib::XSetSelectionOwner(self.display, *selection, 0, xlib::CurrentTime);
121 }
122 xlib::XFlush(self.display);
123 }
124 }
125
126 fn is_open(&self) -> bool {
127 self.open.load(Ordering::Relaxed)
128 }
129
130 fn currently_providing(&self) -> Option<ClipboardProviding> {
131 self.provider_holder.read().ok()?.current_selection()
132 }
133
134 fn provide_next(&self) {
135 if let Ok(mut provider_holder) = self.provider_holder.write() {
136 provider_holder.get_value();
137 }
138 }
139}
140
141impl Drop for Context {
142 fn drop(&mut self) {
143 unsafe {
144 xlib::XCloseDisplay(self.display);
145 }
146 }
147}
148
149unsafe impl Send for Context {}
150
151unsafe impl Sync for Context {}
152
153pub struct Clipboard {
154 context: Arc<Context>,
155 handle: Mutex<Option<thread::JoinHandle<()>>>,
156}
157
158impl ClipboardCommon for Clipboard {
159 fn new<T>(selection_provider: T, event_hub: Arc<dyn EventHub>) -> ClipboardResult<Self>
160 where
161 T: SelectionProvider + Clone + 'static,
162 {
163 match selection_provider.current_selection() {
164 Some(providing) => event_hub.send(EventData::ClipboardProviding(providing)),
165 None => return Err(ClipboardError::Other("Empty provider".to_string())),
166 };
167
168 let context = Arc::new(Context::new(event_hub, selection_provider)?);
169
170 let handle = thread::spawn({
171 let cloned = context.clone();
172 move || run(cloned)
173 });
174
175 Ok(Clipboard {
176 context,
177 handle: Mutex::new(Some(handle)),
178 })
179 }
180
181 fn destroy(&self) {
182 self.context.destroy()
183 }
184
185 fn is_open(&self) -> bool {
186 self.context.is_open()
187 }
188
189 fn currently_providing(&self) -> Option<ClipboardProviding> {
190 self.context.currently_providing()
191 }
192
193 fn provide_next(&self) {
194 self.context.provide_next()
195 }
196
197 fn wait(&self) -> ClipboardResult<()> {
198 let mut maybe_handle = self.handle.lock()?;
199 if let Some(handle) = maybe_handle.take() {
200 handle
201 .join()
202 .map_err(|_| ClipboardError::Other("wait timeout".to_string()))?;
203 }
204 Ok(())
205 }
206}
207
208impl Drop for Clipboard {
209 fn drop(&mut self) {
210 self.destroy()
211 }
212}
213
214fn run(context: Arc<Context>) {
215 unsafe {
216 if !context.own_selection() {
217 return;
218 }
219
220 let mut event: xlib::XEvent = MaybeUninit::zeroed().assume_init();
221
222 loop {
223 xlib::XFlush(context.display);
224 debug!("Wating for event");
225 xlib::XNextEvent(context.display, &mut event);
226
227 debug!("Got event: {}", event.get_type());
228
229 match event.get_type() {
230 xlib::SelectionRequest => {
231 let mut selection: xlib::XSelectionEvent = MaybeUninit::zeroed().assume_init();
232 selection.type_ = xlib::SelectionNotify;
233 selection.display = event.selection_request.display;
234 selection.requestor = event.selection_request.requestor;
235 selection.selection = event.selection_request.selection;
236 selection.time = event.selection_request.time;
237 selection.target = event.selection_request.target;
238 selection.property = event.selection_request.property;
239
240 debug!("Selection requestor: {}", selection.requestor);
241 debug!("Selection target: {}", selection.target);
242
243 if selection.target == context.atoms.targets {
244 let atoms = [context.atoms.targets, context.atoms.string, context.atoms.utf8_string];
245 xlib::XChangeProperty(
246 context.display,
247 selection.requestor,
248 selection.property,
249 xlib::XA_ATOM,
250 32,
251 xlib::PropModeReplace,
252 &atoms as *const xlib::Atom as *const u8,
253 atoms.len() as i32,
254 );
255 } else if selection.target == context.atoms.string || selection.target == context.atoms.utf8_string {
256 match context.provider_holder.write() {
257 Ok(mut provider_holder) => {
258 match provider_holder.get_value() {
259 Some(mut value) => {
260 let content: &[u8] = value.as_ref();
261
262 xlib::XChangeProperty(
263 context.display,
264 selection.requestor,
265 selection.property,
266 selection.target,
267 8,
268 xlib::PropModeReplace,
269 content.as_ptr(),
270 content.len() as i32,
271 );
272 value.zeroize();
273 }
274 None => {
275 context.clear_selection();
276 debug!("Last part: Reply with NONE");
277 selection.property = 0;
278 }
279 };
280 }
281 Err(err) => {
282 error!("Unable to lock provider {err}");
283 context.clear_selection();
284 selection.property = 0;
285 }
286 };
287 } else {
288 debug!("Reply with NONE");
289 selection.property = 0;
290 }
291
292 xlib::XSendEvent(
293 context.display,
294 selection.requestor,
295 xlib::False,
296 xlib::NoEventMask,
297 &mut xlib::XEvent { selection } as *mut xlib::XEvent,
298 );
299
300 xlib::XSync(context.display, xlib::False);
301 }
302 xlib::SelectionClear => {
303 debug!("Lost ownership");
304 break;
305 }
306 xlib::DestroyNotify => {
307 debug!("Window destroyed");
308
309 break;
310 }
311 ignored => debug!("Ignoring event: {ignored}"),
312 }
313 }
314
315 debug!("Ending event loop");
316 context.event_hub.send(EventData::ClipboardDone);
317 context.destroy();
318 }
319}