clipboard_master/master/
x11.rs1use crate::{CallbackResult, ClipboardHandler};
2
3use std::io;
4use std::sync::OnceLock;
5use std::sync::mpsc::{self, SyncSender, Receiver, sync_channel};
6
7use x11rb::protocol::xfixes;
8use x11rb::connection::Connection;
9use x11rb::protocol::xproto::ConnectionExt;
10
11pub struct Shutdown {
15 sender: SyncSender<()>,
16}
17
18impl Drop for Shutdown {
19 #[inline(always)]
20 fn drop(&mut self) {
21 let _ = self.sender.send(());
22 }
23}
24
25pub struct Master<H> {
33 handler: H,
34 sender: SyncSender<()>,
35 recv: Receiver<()>
36}
37
38impl<H: ClipboardHandler> Master<H> {
39 #[inline(always)]
40 pub fn new(handler: H) -> io::Result<Self> {
42 let (sender, recv) = sync_channel(0);
43
44 Ok(Self {
45 handler,
46 sender,
47 recv,
48 })
49 }
50
51 #[inline(always)]
52 pub fn shutdown_channel(&self) -> Shutdown {
54 Shutdown {
55 sender: self.sender.clone()
56 }
57 }
58
59
60 pub fn run(&mut self) -> io::Result<()> {
62 let clipboard = match Self::x11_clipboard() {
63 Ok(clipboard) => clipboard,
64 Err(error) => {
65 return Err(io::Error::new(
66 io::ErrorKind::Other,
67 format!("Failed to initialize clipboard: {:?}", error),
68 ))
69 }
70 };
71
72
73 if let Err(error) = xfixes::query_version(&clipboard.getter.connection, 5, 0) {
74 return Err(io::Error::new(io::ErrorKind::Other, error));
75 }
76
77 let mut result = Ok(());
78 'main: loop {
79 let selection = clipboard.getter.atoms.clipboard;
80
81 let screen = match clipboard.getter.connection.setup().roots.get(clipboard.getter.screen) {
82 Some(screen) => screen,
83 None => match self.handler.on_clipboard_error(io::Error::new(io::ErrorKind::Other, "Screen is not available")) {
84 CallbackResult::Next => continue,
85 CallbackResult::Stop => break,
86 CallbackResult::StopWithError(error) => {
87 result = Err(error);
88 break;
89 }
90 }
91 };
92
93 let cookie = xfixes::select_selection_input(
95 &clipboard.getter.connection,
96 screen.root,
97 clipboard.getter.atoms.primary,
98 xfixes::SelectionEventMask::default()
99 ).and_then(|_| xfixes::select_selection_input(
100 &clipboard.getter.connection,
101 screen.root,
102 clipboard.getter.atoms.clipboard,
103 xfixes::SelectionEventMask::default()
104 )).and_then(|_| xfixes::select_selection_input(
106 &clipboard.getter.connection,
107 screen.root,
108 selection,
109 xfixes::SelectionEventMask::SET_SELECTION_OWNER | xfixes::SelectionEventMask::SELECTION_CLIENT_CLOSE | xfixes::SelectionEventMask::SELECTION_WINDOW_DESTROY
110 ));
111
112 if let Err(error) = clipboard.getter.connection.flush() {
113 match self.handler.on_clipboard_error(io::Error::new(io::ErrorKind::Other, error)) {
114 CallbackResult::Next => continue,
115 CallbackResult::Stop => break,
116 CallbackResult::StopWithError(error) => {
117 result = Err(error);
118 break;
119 }
120 }
121 }
122
123 let sequence_number = match cookie {
124 Ok(cookie) => {
125 let sequence_number = cookie.sequence_number();
126 if let Err(error) = cookie.check() {
127 match self.handler.on_clipboard_error(io::Error::new(io::ErrorKind::Other, error)) {
128 CallbackResult::Next => continue,
129 CallbackResult::Stop => break,
130 CallbackResult::StopWithError(error) => {
131 result = Err(error);
132 break;
133 }
134 }
135 }
136 sequence_number
137 },
138 Err(error) => match self.handler.on_clipboard_error(io::Error::new(io::ErrorKind::Other, error)) {
139 CallbackResult::Next => continue,
140 CallbackResult::Stop => break,
141 CallbackResult::StopWithError(error) => {
142 result = Err(error);
143 break;
144 }
145 }
146 };
147
148 'poll: loop {
149 match clipboard.getter.connection.poll_for_event_with_sequence() {
150 Ok(Some((_, seq))) if seq >= sequence_number => {
151 match self.handler.on_clipboard_change() {
152 CallbackResult::Next => break 'poll,
153 CallbackResult::Stop => break 'main,
154 CallbackResult::StopWithError(error) => {
155 result = Err(error);
156 break 'main;
157 }
158 }
159 },
160 Ok(_) => {
161 match self.recv.recv_timeout(self.handler.sleep_interval()) {
162 Ok(()) => break 'main,
163 Err(mpsc::RecvTimeoutError::Timeout) => continue 'poll,
165 Err(mpsc::RecvTimeoutError::Disconnected) => break 'main,
166 }
167 }
168 Err(error) => {
169 let error = io::Error::new(
170 io::ErrorKind::Other,
171 format!("Failed to load clipboard: {:?}", error),
172 );
173
174 match self.handler.on_clipboard_error(error) {
175 CallbackResult::Next => break 'poll,
176 CallbackResult::Stop => break 'main,
177 CallbackResult::StopWithError(error) => {
178 result = Err(error);
179 break 'main;
180 }
181 }
182 }
183 }
184 }
185
186 let delete = clipboard.getter.connection.delete_property(clipboard.getter.window, clipboard.getter.atoms.property)
187 .map_err(|error| io::Error::new(io::ErrorKind::Other, error))
188 .and_then(|cookie| cookie.check().map_err(|error| io::Error::new(io::ErrorKind::Other, error)));
189 if let Err(error) = delete {
190 match self.handler.on_clipboard_error(error) {
191 CallbackResult::Next => (),
192 CallbackResult::Stop => break,
193 CallbackResult::StopWithError(error) => {
194 result = Err(error);
195 break;
196 }
197 }
198 }
199
200 match self.recv.recv_timeout(self.handler.sleep_interval()) {
201 Ok(()) => break,
202 Err(mpsc::RecvTimeoutError::Timeout) => continue,
204 Err(mpsc::RecvTimeoutError::Disconnected) => break,
205 }
206 }
207
208 match clipboard.getter.connection.delete_property(clipboard.getter.window, clipboard.getter.atoms.property) {
209 Ok(cookie) => match cookie.check() {
210 Ok(_) => result,
211 Err(error) => Err(io::Error::new(io::ErrorKind::Other, error)),
212 },
213 Err(error) => Err(io::Error::new(io::ErrorKind::Other, error)),
214 }
215 }
216
217 pub fn x11_clipboard() -> &'static Result<x11_clipboard::Clipboard, x11_clipboard::error::Error> {
226 static CLIP: OnceLock<Result<x11_clipboard::Clipboard, x11_clipboard::error::Error>> = OnceLock::new();
227 CLIP.get_or_init(x11_clipboard::Clipboard::new)
228 }
229}