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 { fd, events: libc::POLLIN, revents: 0 };
59 let n = unsafe { libc::poll(&mut pfd, 1, timeout.as_millis().max(1) as libc::c_int) };
60 if n < 0 {
61 let err = std::io::Error::last_os_error();
62 if err.raw_os_error() == Some(libc::EINTR) {
63 continue;
64 }
65 return Err(format!("poll: {err}"));
66 }
67 return Ok(n > 0);
68 }
69}
70
71pub(crate) fn wait_readable2(
75 a: RawFd,
76 b: RawFd,
77 timeout: Option<Duration>,
78) -> Result<(bool, bool), String> {
79 loop {
80 let mut pfds = [
81 libc::pollfd { fd: a, events: libc::POLLIN, revents: 0 },
82 libc::pollfd { fd: b, events: libc::POLLIN, revents: 0 },
83 ];
84 let ms = timeout.map(|t| t.as_millis().max(1) as libc::c_int).unwrap_or(-1);
85 let n = unsafe { libc::poll(pfds.as_mut_ptr(), 2, ms) };
86 if n < 0 {
87 let err = std::io::Error::last_os_error();
88 if err.raw_os_error() == Some(libc::EINTR) {
89 continue;
90 }
91 return Err(format!("poll: {err}"));
92 }
93 let hit = |r: libc::c_short| r & (libc::POLLIN | libc::POLLHUP | libc::POLLERR) != 0;
94 return Ok((hit(pfds[0].revents), hit(pfds[1].revents)));
95 }
96}
97
98pub(crate) fn drain_pipe(fd: RawFd) {
100 let mut buf = [0u8; 64];
101 loop {
102 let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
103 if n <= 0 {
104 return;
105 }
106 }
107}
108
109pub(crate) fn wake_write(fd: RawFd) {
111 let b = [1u8];
112 unsafe { libc::write(fd, b.as_ptr() as *const libc::c_void, 1) };
113}
114
115pub(crate) fn bounded_roundtrip<S>(
118 conn: &Connection,
119 queue: &mut EventQueue<S>,
120 state: &mut S,
121) -> Result<(), String>
122where
123 S: SyncState + Dispatch<wl_callback::WlCallback, ()> + 'static,
124{
125 *state.sync_done_mut() = false;
126 let _cb = conn.display().sync(&queue.handle(), ());
127 let deadline = Instant::now() + IO_TIMEOUT;
128 loop {
129 queue
130 .dispatch_pending(state)
131 .map_err(|e| format!("dispatch: {e}"))?;
132 if *state.sync_done_mut() {
133 return Ok(());
134 }
135 queue.flush().map_err(|e| format!("flush: {e}"))?;
136 let remaining = deadline
137 .checked_duration_since(Instant::now())
138 .ok_or("compositor round-trip timed out")?;
139 let Some(guard) = conn.prepare_read() else {
140 continue;
141 };
142 if !wait_readable(guard.connection_fd().as_raw_fd(), remaining)? {
143 return Err("compositor round-trip timed out".into());
144 }
145 guard.read().map_err(|e| format!("read: {e}"))?;
146 }
147}
148
149pub(crate) fn memfd_with(data: &[u8]) -> Result<OwnedFd, String> {
151 let name = b"pixelflux-wl\0";
152 let fd =
153 unsafe { libc::memfd_create(name.as_ptr() as *const libc::c_char, libc::MFD_CLOEXEC) };
154 if fd < 0 {
155 return Err(format!("memfd_create: {}", std::io::Error::last_os_error()));
156 }
157 let owned = unsafe { OwnedFd::from_raw_fd(fd) };
158 let mut written = 0;
159 while written < data.len() {
160 let n = unsafe {
161 libc::write(
162 owned.as_raw_fd(),
163 data[written..].as_ptr() as *const libc::c_void,
164 data.len() - written,
165 )
166 };
167 if n < 0 {
168 return Err(format!("write memfd: {}", std::io::Error::last_os_error()));
169 }
170 written += n as usize;
171 }
172 Ok(owned)
173}
174
175pub(crate) fn pipe_cloexec() -> Result<(OwnedFd, OwnedFd), String> {
177 let mut fds = [0i32; 2];
178 if unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) } < 0 {
179 return Err(format!("pipe2: {}", std::io::Error::last_os_error()));
180 }
181 Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) })
182}
183
184pub(crate) fn wake_pipe() -> Result<(OwnedFd, OwnedFd), String> {
186 let mut fds = [0i32; 2];
187 if unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) } < 0 {
188 return Err(format!("pipe2: {}", std::io::Error::last_os_error()));
189 }
190 Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) })
191}
192
193pub(crate) fn read_fd_to_end(fd: &OwnedFd, idle: Duration) -> Result<Vec<u8>, String> {
196 read_fd_to_end_capped(fd, idle, READ_CAP_MAX)
197}
198
199const READ_CAP_MAX: usize = 64 * 1024 * 1024;
202
203pub(crate) fn read_fd_to_end_capped(
210 fd: &OwnedFd,
211 idle: Duration,
212 cap: usize,
213) -> Result<Vec<u8>, String> {
214 let mut out = Vec::new();
215 let mut chunk = [0u8; 65536];
216 loop {
217 if !wait_readable(fd.as_raw_fd(), idle)? {
218 return Err("clipboard source stalled".into());
219 }
220 let n = unsafe {
221 libc::read(fd.as_raw_fd(), chunk.as_mut_ptr() as *mut libc::c_void, chunk.len())
222 };
223 if n < 0 {
224 let err = std::io::Error::last_os_error();
225 if err.raw_os_error() == Some(libc::EINTR) {
226 continue;
227 }
228 return Err(format!("read: {err}"));
229 }
230 if n == 0 {
231 return Ok(out);
232 }
233 if out.len() + n as usize > cap {
234 return Err(format!("clipboard source exceeds the {cap} byte cap"));
235 }
236 out.extend_from_slice(&chunk[..n as usize]);
237 }
238}
239
240pub(crate) fn write_fd_all(fd: &OwnedFd, data: &[u8], idle: Duration) -> Result<(), String> {
249 let raw = fd.as_raw_fd();
250 let old_flags = unsafe { libc::fcntl(raw, libc::F_GETFL) };
251 if old_flags < 0 {
252 return Err(format!("fcntl F_GETFL: {}", std::io::Error::last_os_error()));
253 }
254 if unsafe { libc::fcntl(raw, libc::F_SETFL, old_flags | libc::O_NONBLOCK) } < 0 {
255 return Err(format!("fcntl F_SETFL: {}", std::io::Error::last_os_error()));
256 }
257 let result = write_fd_all_nb(raw, data, idle);
258 unsafe { libc::fcntl(raw, libc::F_SETFL, old_flags) };
259 result
260}
261
262fn write_fd_all_nb(raw: i32, data: &[u8], idle: Duration) -> Result<(), String> {
263 let mut written = 0;
264 while written < data.len() {
265 let mut pfd = libc::pollfd { fd: raw, events: libc::POLLOUT, revents: 0 };
266 let n = unsafe { libc::poll(&mut pfd, 1, idle.as_millis().max(1) as libc::c_int) };
267 if n < 0 {
268 let err = std::io::Error::last_os_error();
269 if err.raw_os_error() == Some(libc::EINTR) {
270 continue;
271 }
272 return Err(format!("poll: {err}"));
273 }
274 if n == 0 {
275 return Err("clipboard reader stalled".into());
276 }
277 let w = unsafe {
278 libc::write(
279 raw,
280 data[written..].as_ptr() as *const libc::c_void,
281 data.len() - written,
282 )
283 };
284 if w < 0 {
285 let err = std::io::Error::last_os_error();
286 match err.raw_os_error() {
287 Some(libc::EINTR) | Some(libc::EAGAIN) => continue,
288 Some(libc::EPIPE) => return Ok(()),
289 _ => return Err(format!("write: {err}")),
290 }
291 }
292 written += w as usize;
293 }
294 Ok(())
295}