1use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
8use std::time::{Duration, Instant};
9
10use wayland_client::protocol::wl_callback;
11use wayland_client::{Connection, Dispatch, EventQueue};
12
13pub(crate) const IO_TIMEOUT: Duration = Duration::from_secs(5);
14
15pub(crate) fn socket_path(name: &str) -> Option<String> {
18 if name.starts_with('/') {
19 return Some(name.to_string());
20 }
21 let rt = std::env::var("XDG_RUNTIME_DIR").ok()?;
22 Some(format!("{}/{}", rt.trim_end_matches('/'), name))
23}
24
25pub(crate) trait SyncState {
28 fn sync_done_mut(&mut self) -> &mut bool;
29}
30
31macro_rules! impl_sync_callback {
34 ($t:ty) => {
35 impl wayland_client::Dispatch<wayland_client::protocol::wl_callback::WlCallback, ()>
36 for $t
37 {
38 fn event(
39 state: &mut Self,
40 _: &wayland_client::protocol::wl_callback::WlCallback,
41 event: wayland_client::protocol::wl_callback::Event,
42 _: &(),
43 _: &wayland_client::Connection,
44 _: &wayland_client::QueueHandle<Self>,
45 ) {
46 if let wayland_client::protocol::wl_callback::Event::Done { .. } = event {
47 *crate::wayland::wlclient::SyncState::sync_done_mut(state) = true;
48 }
49 }
50 }
51 };
52}
53pub(crate) use impl_sync_callback;
54
55pub(crate) fn wait_readable(fd: RawFd, timeout: Duration) -> Result<bool, String> {
57 loop {
58 let mut pfd = libc::pollfd {
59 fd,
60 events: libc::POLLIN,
61 revents: 0,
62 };
63 let n = unsafe { libc::poll(&mut pfd, 1, timeout.as_millis().max(1) as libc::c_int) };
64 if n < 0 {
65 let err = std::io::Error::last_os_error();
66 if err.raw_os_error() == Some(libc::EINTR) {
67 continue;
68 }
69 return Err(format!("poll: {err}"));
70 }
71 return Ok(n > 0);
72 }
73}
74
75pub(crate) fn wait_readable2(
79 a: RawFd,
80 b: RawFd,
81 timeout: Option<Duration>,
82) -> Result<(bool, bool), String> {
83 loop {
84 let mut pfds = [
85 libc::pollfd {
86 fd: a,
87 events: libc::POLLIN,
88 revents: 0,
89 },
90 libc::pollfd {
91 fd: b,
92 events: libc::POLLIN,
93 revents: 0,
94 },
95 ];
96 let ms = timeout
97 .map(|t| t.as_millis().max(1) as libc::c_int)
98 .unwrap_or(-1);
99 let n = unsafe { libc::poll(pfds.as_mut_ptr(), 2, ms) };
100 if n < 0 {
101 let err = std::io::Error::last_os_error();
102 if err.raw_os_error() == Some(libc::EINTR) {
103 continue;
104 }
105 return Err(format!("poll: {err}"));
106 }
107 let hit = |r: libc::c_short| r & (libc::POLLIN | libc::POLLHUP | libc::POLLERR) != 0;
108 return Ok((hit(pfds[0].revents), hit(pfds[1].revents)));
109 }
110}
111
112pub(crate) fn drain_pipe(fd: RawFd) {
114 let mut buf = [0u8; 64];
115 loop {
116 let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
117 if n <= 0 {
118 return;
119 }
120 }
121}
122
123pub(crate) fn wake_write(fd: RawFd) {
125 let b = [1u8];
126 unsafe { libc::write(fd, b.as_ptr() as *const libc::c_void, 1) };
127}
128
129pub(crate) fn bounded_roundtrip<S>(
132 conn: &Connection,
133 queue: &mut EventQueue<S>,
134 state: &mut S,
135) -> Result<(), String>
136where
137 S: SyncState + Dispatch<wl_callback::WlCallback, ()> + 'static,
138{
139 *state.sync_done_mut() = false;
140 let _cb = conn.display().sync(&queue.handle(), ());
141 let deadline = Instant::now() + IO_TIMEOUT;
142 loop {
143 queue
144 .dispatch_pending(state)
145 .map_err(|e| format!("dispatch: {e}"))?;
146 if *state.sync_done_mut() {
147 return Ok(());
148 }
149 queue.flush().map_err(|e| format!("flush: {e}"))?;
150 let remaining = deadline
151 .checked_duration_since(Instant::now())
152 .ok_or("compositor round-trip timed out")?;
153 let Some(guard) = conn.prepare_read() else {
154 continue;
155 };
156 if !wait_readable(guard.connection_fd().as_raw_fd(), remaining)? {
157 return Err("compositor round-trip timed out".into());
158 }
159 guard.read().map_err(|e| format!("read: {e}"))?;
160 }
161}
162
163pub(crate) fn memfd_with(data: &[u8]) -> Result<OwnedFd, String> {
165 let name = b"pixelflux-wl\0";
166 let fd = unsafe { libc::memfd_create(name.as_ptr() as *const libc::c_char, libc::MFD_CLOEXEC) };
167 if fd < 0 {
168 return Err(format!("memfd_create: {}", std::io::Error::last_os_error()));
169 }
170 let owned = unsafe { OwnedFd::from_raw_fd(fd) };
171 let mut written = 0;
172 while written < data.len() {
173 let n = unsafe {
174 libc::write(
175 owned.as_raw_fd(),
176 data[written..].as_ptr() as *const libc::c_void,
177 data.len() - written,
178 )
179 };
180 if n < 0 {
181 return Err(format!("write memfd: {}", std::io::Error::last_os_error()));
182 }
183 written += n as usize;
184 }
185 Ok(owned)
186}
187
188pub(crate) fn pipe_cloexec() -> Result<(OwnedFd, OwnedFd), String> {
190 let mut fds = [0i32; 2];
191 if unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) } < 0 {
192 return Err(format!("pipe2: {}", std::io::Error::last_os_error()));
193 }
194 Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) })
195}
196
197pub(crate) fn wake_pipe() -> Result<(OwnedFd, OwnedFd), String> {
199 let mut fds = [0i32; 2];
200 if unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) } < 0 {
201 return Err(format!("pipe2: {}", std::io::Error::last_os_error()));
202 }
203 Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) })
204}
205
206pub(crate) fn read_fd_to_end(fd: &OwnedFd, idle: Duration) -> Result<Vec<u8>, String> {
209 read_fd_to_end_capped(fd, idle, READ_CAP_MAX)
210}
211
212const READ_CAP_MAX: usize = 64 * 1024 * 1024;
215
216pub(crate) fn read_fd_to_end_capped(
223 fd: &OwnedFd,
224 idle: Duration,
225 cap: usize,
226) -> Result<Vec<u8>, String> {
227 let mut out = Vec::new();
228 let mut chunk = [0u8; 65536];
229 loop {
230 if !wait_readable(fd.as_raw_fd(), idle)? {
231 return Err("clipboard source stalled".into());
232 }
233 let n = unsafe {
234 libc::read(
235 fd.as_raw_fd(),
236 chunk.as_mut_ptr() as *mut libc::c_void,
237 chunk.len(),
238 )
239 };
240 if n < 0 {
241 let err = std::io::Error::last_os_error();
242 if err.raw_os_error() == Some(libc::EINTR) {
243 continue;
244 }
245 return Err(format!("read: {err}"));
246 }
247 if n == 0 {
248 return Ok(out);
249 }
250 if out.len() + n as usize > cap {
251 return Err(format!("clipboard source exceeds the {cap} byte cap"));
252 }
253 out.extend_from_slice(&chunk[..n as usize]);
254 }
255}
256
257pub(crate) fn write_fd_all(fd: &OwnedFd, data: &[u8], idle: Duration) -> Result<(), String> {
266 let raw = fd.as_raw_fd();
267 let old_flags = unsafe { libc::fcntl(raw, libc::F_GETFL) };
268 if old_flags < 0 {
269 return Err(format!(
270 "fcntl F_GETFL: {}",
271 std::io::Error::last_os_error()
272 ));
273 }
274 if unsafe { libc::fcntl(raw, libc::F_SETFL, old_flags | libc::O_NONBLOCK) } < 0 {
275 return Err(format!(
276 "fcntl F_SETFL: {}",
277 std::io::Error::last_os_error()
278 ));
279 }
280 let result = write_fd_all_nb(raw, data, idle);
281 unsafe { libc::fcntl(raw, libc::F_SETFL, old_flags) };
282 result
283}
284
285fn write_fd_all_nb(raw: i32, data: &[u8], idle: Duration) -> Result<(), String> {
286 let mut written = 0;
287 while written < data.len() {
288 let mut pfd = libc::pollfd {
289 fd: raw,
290 events: libc::POLLOUT,
291 revents: 0,
292 };
293 let n = unsafe { libc::poll(&mut pfd, 1, idle.as_millis().max(1) as libc::c_int) };
294 if n < 0 {
295 let err = std::io::Error::last_os_error();
296 if err.raw_os_error() == Some(libc::EINTR) {
297 continue;
298 }
299 return Err(format!("poll: {err}"));
300 }
301 if n == 0 {
302 return Err("clipboard reader stalled".into());
303 }
304 let w = unsafe {
305 libc::write(
306 raw,
307 data[written..].as_ptr() as *const libc::c_void,
308 data.len() - written,
309 )
310 };
311 if w < 0 {
312 let err = std::io::Error::last_os_error();
313 match err.raw_os_error() {
314 Some(libc::EINTR) | Some(libc::EAGAIN) => continue,
315 Some(libc::EPIPE) => return Ok(()),
316 _ => return Err(format!("write: {err}")),
317 }
318 }
319 written += w as usize;
320 }
321 Ok(())
322}