1 | //! A FFI-based connection to an X11 server, using libxcb. |
2 | //! |
3 | //! This module is only available when the `allow-unsafe-code` feature is enabled. |
4 | |
5 | use std::convert::TryInto; |
6 | use std::ffi::CStr; |
7 | use std::io::{Error as IOError, ErrorKind, IoSlice}; |
8 | use std::os::raw::c_int; |
9 | #[cfg (unix)] |
10 | use std::os::unix::io::{AsRawFd, RawFd}; |
11 | use std::ptr::{null, null_mut}; |
12 | use std::sync::{atomic::Ordering, Mutex}; |
13 | |
14 | use libc::c_void; |
15 | |
16 | use crate::connection::{ |
17 | compute_length_field, Connection, ReplyOrError, RequestConnection, RequestKind, |
18 | }; |
19 | use crate::cookie::{Cookie, CookieWithFds, VoidCookie}; |
20 | pub use crate::errors::{ConnectError, ConnectionError, ParseError, ReplyError, ReplyOrIdError}; |
21 | use crate::extension_manager::ExtensionManager; |
22 | use crate::protocol::xproto::Setup; |
23 | use crate::utils::{CSlice, RawFdContainer}; |
24 | use crate::x11_utils::{ExtensionInformation, TryParse, TryParseFd}; |
25 | |
26 | use x11rb_protocol::{DiscardMode, SequenceNumber}; |
27 | |
28 | mod atomic_u64; |
29 | mod pending_errors; |
30 | mod raw_ffi; |
31 | |
32 | use atomic_u64::AtomicU64; |
33 | #[cfg (all(not(test), feature = "dl-libxcb" ))] |
34 | pub use raw_ffi::libxcb_library::load_libxcb; |
35 | |
36 | type Buffer = <XCBConnection as RequestConnection>::Buf; |
37 | /// The raw bytes of an event received by [`XCBConnection`] and its sequence number. |
38 | pub type RawEventAndSeqNumber = x11rb_protocol::RawEventAndSeqNumber<Buffer>; |
39 | /// A combination of a buffer and a list of file descriptors for use by [`XCBConnection`]. |
40 | pub type BufWithFds = crate::connection::BufWithFds<Buffer>; |
41 | |
42 | /// A connection to an X11 server. |
43 | /// |
44 | /// This type wraps `*mut xcb_connection_t` that is provided by libxcb. It provides a rust |
45 | /// interface to this C library. |
46 | #[allow (clippy::upper_case_acronyms)] |
47 | #[derive (Debug)] |
48 | pub struct XCBConnection { |
49 | conn: raw_ffi::XcbConnectionWrapper, |
50 | setup: Setup, |
51 | ext_mgr: Mutex<ExtensionManager>, |
52 | errors: pending_errors::PendingErrors, |
53 | maximum_sequence_received: AtomicU64, |
54 | } |
55 | |
56 | impl XCBConnection { |
57 | unsafe fn connection_error_from_connection( |
58 | c: *mut raw_ffi::xcb_connection_t, |
59 | ) -> ConnectionError { |
60 | Self::connection_error_from_c_error(raw_ffi::xcb_connection_has_error(c)) |
61 | } |
62 | |
63 | fn connection_error_from_c_error(error: c_int) -> ConnectionError { |
64 | use crate::xcb_ffi::raw_ffi::connection_errors::*; |
65 | |
66 | assert_ne!(error, 0); |
67 | match error { |
68 | ERROR => IOError::new(ErrorKind::Other, ConnectionError::UnknownError).into(), |
69 | EXT_NOTSUPPORTED => ConnectionError::UnsupportedExtension, |
70 | MEM_INSUFFICIENT => ConnectionError::InsufficientMemory, |
71 | REQ_LEN_EXCEED => ConnectionError::MaximumRequestLengthExceeded, |
72 | FDPASSING_FAILED => ConnectionError::FdPassingFailed, |
73 | _ => ConnectionError::UnknownError, |
74 | // Not possible here: PARSE_ERR, INVALID_SCREEN |
75 | } |
76 | } |
77 | |
78 | fn connect_error_from_c_error(error: c_int) -> ConnectError { |
79 | use crate::xcb_ffi::raw_ffi::connection_errors::*; |
80 | |
81 | assert_ne!(error, 0); |
82 | match error { |
83 | ERROR => IOError::new(ErrorKind::Other, ConnectionError::UnknownError).into(), |
84 | MEM_INSUFFICIENT => ConnectError::InsufficientMemory, |
85 | PARSE_ERR => ConnectError::DisplayParsingError, |
86 | INVALID_SCREEN => ConnectError::InvalidScreen, |
87 | _ => ConnectError::UnknownError, |
88 | // Not possible here: EXT_NOTSUPPORTED, REQ_LEN_EXCEED, FDPASSING_FAILED |
89 | } |
90 | } |
91 | |
92 | /// Establish a new connection to an X11 server. |
93 | /// |
94 | /// If a `dpy_name` is provided, it describes the display that should be connected to, for |
95 | /// example `127.0.0.1:1`. If no value is provided, the `$DISPLAY` environment variable is |
96 | /// used. |
97 | pub fn connect(dpy_name: Option<&CStr>) -> Result<(XCBConnection, usize), ConnectError> { |
98 | use libc::c_int; |
99 | unsafe { |
100 | let mut screen: c_int = 0; |
101 | let dpy_ptr = dpy_name.map_or(null(), |s| s.as_ptr()); |
102 | let connection = raw_ffi::XcbConnectionWrapper::new( |
103 | raw_ffi::xcb_connect(dpy_ptr, &mut screen), |
104 | true, |
105 | ); |
106 | let error = raw_ffi::xcb_connection_has_error(connection.as_ptr()); |
107 | if error != 0 { |
108 | Err(Self::connect_error_from_c_error(error)) |
109 | } else { |
110 | let setup = raw_ffi::xcb_get_setup(connection.as_ptr()); |
111 | let conn = XCBConnection { |
112 | // `xcb_connect` will never return null. |
113 | conn: connection, |
114 | setup: Self::parse_setup(setup)?, |
115 | ext_mgr: Default::default(), |
116 | errors: Default::default(), |
117 | maximum_sequence_received: AtomicU64::new(0), |
118 | }; |
119 | Ok((conn, screen as usize)) |
120 | } |
121 | } |
122 | } |
123 | |
124 | /// Create a connection wrapper for a raw libxcb `xcb_connection_t`. |
125 | /// |
126 | /// `xcb_disconnect` is called on drop only if `should_drop` is `true`. |
127 | /// If this function returns an `Err()` and `should_drop` was true, then |
128 | /// `xcb_disconnect` was already called. |
129 | /// |
130 | /// # Safety |
131 | /// |
132 | /// If `should_drop` is `false`, the connection must live longer than the returned |
133 | /// `XCBConnection`. If `should_drop` is `true`, the returned `XCBConnection` will |
134 | /// take the ownership of the connection. |
135 | pub unsafe fn from_raw_xcb_connection( |
136 | ptr: *mut c_void, |
137 | should_drop: bool, |
138 | ) -> Result<XCBConnection, ConnectError> { |
139 | let ptr = ptr as *mut raw_ffi::xcb_connection_t; |
140 | let conn = raw_ffi::XcbConnectionWrapper::new(ptr, should_drop); |
141 | let setup = raw_ffi::xcb_get_setup(ptr); |
142 | Ok(XCBConnection { |
143 | conn, |
144 | setup: Self::parse_setup(setup)?, |
145 | ext_mgr: Default::default(), |
146 | errors: Default::default(), |
147 | maximum_sequence_received: AtomicU64::new(0), |
148 | }) |
149 | } |
150 | |
151 | unsafe fn parse_setup(setup: *const raw_ffi::xcb_setup_t) -> Result<Setup, ParseError> { |
152 | use std::slice::from_raw_parts; |
153 | |
154 | // We know that the setup information has at least eight bytes. |
155 | // Use a slice instead of Buffer::CSlice since we must not free() the xcb_setup_t that libxcb owns. |
156 | let wrapper = from_raw_parts(setup as *const u8, 8); |
157 | |
158 | // The length field is in the last two bytes |
159 | let length = u16::from_ne_bytes([wrapper[6], wrapper[7]]); |
160 | |
161 | // The length is in four-byte-units after the known header |
162 | let length = usize::from(length) * 4 + 8; |
163 | |
164 | let slice = from_raw_parts(wrapper.as_ptr(), length); |
165 | let result = Setup::try_parse(slice)?.0; |
166 | |
167 | Ok(result) |
168 | } |
169 | |
170 | // Slince the warning about ioslice.len().try_into().unwrap(). The target type is sometimes |
171 | // usize (where this warning is correct) and sometimes c_int (where we need the conversion). We |
172 | // need this here due to https://github.com/rust-lang/rust/issues/60681. |
173 | #[allow (clippy::useless_conversion)] |
174 | fn send_request( |
175 | &self, |
176 | bufs: &[IoSlice<'_>], |
177 | fds: Vec<RawFdContainer>, |
178 | has_reply: bool, |
179 | reply_has_fds: bool, |
180 | ) -> Result<SequenceNumber, ConnectionError> { |
181 | let mut storage = Default::default(); |
182 | let new_bufs = compute_length_field(self, bufs, &mut storage)?; |
183 | |
184 | // Now wrap the buffers with IoSlice |
185 | let mut new_bufs_ffi = Vec::with_capacity(2 + new_bufs.len()); |
186 | // XCB wants to access bufs[-1] and bufs[-2], so we need to add two empty items in front. |
187 | new_bufs_ffi.push(raw_ffi::iovec { |
188 | iov_base: null_mut(), |
189 | iov_len: 0, |
190 | }); |
191 | new_bufs_ffi.push(raw_ffi::iovec { |
192 | iov_base: null_mut(), |
193 | iov_len: 0, |
194 | }); |
195 | new_bufs_ffi.extend(new_bufs.iter().map(|ioslice| raw_ffi::iovec { |
196 | iov_base: ioslice.as_ptr() as _, |
197 | iov_len: ioslice.len().try_into().unwrap(), |
198 | })); |
199 | |
200 | // Set up the information that libxcb needs |
201 | let protocol_request = raw_ffi::xcb_protocol_request_t { |
202 | count: new_bufs.len(), |
203 | ext: null_mut(), // Not needed since we always use raw |
204 | opcode: 0, |
205 | isvoid: u8::from(!has_reply), |
206 | }; |
207 | let mut flags = raw_ffi::send_request_flags::RAW; |
208 | assert!(has_reply || !reply_has_fds); |
209 | flags |= raw_ffi::send_request_flags::CHECKED; |
210 | if reply_has_fds { |
211 | flags |= raw_ffi::send_request_flags::REPLY_FDS; |
212 | } |
213 | |
214 | let seqno = if fds.is_empty() { |
215 | unsafe { |
216 | raw_ffi::xcb_send_request64( |
217 | self.conn.as_ptr(), |
218 | flags, |
219 | &mut new_bufs_ffi[2], |
220 | &protocol_request, |
221 | ) |
222 | } |
223 | } else { |
224 | #[cfg (unix)] |
225 | { |
226 | // Convert the FDs into an array of ints. libxcb will close the FDs. |
227 | let mut fds: Vec<_> = fds.into_iter().map(RawFdContainer::into_raw_fd).collect(); |
228 | let num_fds = fds.len().try_into().unwrap(); |
229 | let fds_ptr = fds.as_mut_ptr(); |
230 | unsafe { |
231 | raw_ffi::xcb_send_request_with_fds64( |
232 | self.conn.as_ptr(), |
233 | flags, |
234 | &mut new_bufs_ffi[2], |
235 | &protocol_request, |
236 | num_fds, |
237 | fds_ptr, |
238 | ) |
239 | } |
240 | } |
241 | #[cfg (not(unix))] |
242 | { |
243 | unreachable!("it is not possible to create a `RawFdContainer` on non-unix" ); |
244 | } |
245 | }; |
246 | if seqno == 0 { |
247 | unsafe { Err(Self::connection_error_from_connection(self.conn.as_ptr())) } |
248 | } else { |
249 | Ok(seqno) |
250 | } |
251 | } |
252 | |
253 | /// Check if the underlying XCB connection is in an error state. |
254 | pub fn has_error(&self) -> Option<ConnectionError> { |
255 | unsafe { |
256 | let error = raw_ffi::xcb_connection_has_error(self.conn.as_ptr()); |
257 | if error == 0 { |
258 | None |
259 | } else { |
260 | Some(Self::connection_error_from_c_error(error)) |
261 | } |
262 | } |
263 | } |
264 | |
265 | /// Get access to the raw libxcb `xcb_connection_t`. |
266 | /// |
267 | /// The returned pointer is valid for as long as the original object was not dropped. No |
268 | /// ownerhsip is transferred. |
269 | pub fn get_raw_xcb_connection(&self) -> *mut c_void { |
270 | self.conn.as_ptr() as _ |
271 | } |
272 | |
273 | /// Check if a reply to the given request already received. |
274 | /// |
275 | /// Return Err(()) when the reply was not yet received. Returns Ok(None) when there can be no |
276 | /// reply. Returns Ok(buffer) with the reply if there is one (this buffer can be an error or a |
277 | /// reply). |
278 | fn poll_for_reply(&self, sequence: SequenceNumber) -> Result<Option<CSlice>, ()> { |
279 | unsafe { |
280 | let mut reply = null_mut(); |
281 | let mut error = null_mut(); |
282 | let found = |
283 | raw_ffi::xcb_poll_for_reply64(self.conn.as_ptr(), sequence, &mut reply, &mut error); |
284 | if found == 0 { |
285 | return Err(()); |
286 | } |
287 | assert_eq!(found, 1); |
288 | match (reply.is_null(), error.is_null()) { |
289 | (true, true) => Ok(None), |
290 | (true, false) => Ok(Some(self.wrap_error(error as _, sequence))), |
291 | (false, true) => Ok(Some(self.wrap_reply(reply as _, sequence))), |
292 | (false, false) => unreachable!(), |
293 | } |
294 | } |
295 | } |
296 | |
297 | unsafe fn wrap_reply(&self, reply: *const u8, sequence: SequenceNumber) -> CSlice { |
298 | // Update our "max sequence number received" field |
299 | let _ = self |
300 | .maximum_sequence_received |
301 | .fetch_max(sequence, Ordering::Relaxed); |
302 | |
303 | let header = CSlice::new(reply, 32); |
304 | |
305 | let length_field = u32::from_ne_bytes(header[4..8].try_into().unwrap()); |
306 | let length_field: usize = length_field |
307 | .try_into() |
308 | .expect("usize should have at least 32 bits" ); |
309 | |
310 | let length = 32 + length_field * 4; |
311 | CSlice::new(header.into_ptr(), length) |
312 | } |
313 | |
314 | unsafe fn wrap_error(&self, error: *const u8, sequence: SequenceNumber) -> CSlice { |
315 | // Update our "max sequence number received" field |
316 | let _ = self |
317 | .maximum_sequence_received |
318 | .fetch_max(sequence, Ordering::Relaxed); |
319 | |
320 | CSlice::new(error, 32) |
321 | } |
322 | |
323 | unsafe fn wrap_event(&self, event: *mut u8) -> Result<RawEventAndSeqNumber, ParseError> { |
324 | let header = CSlice::new(event, 36); |
325 | let mut length = 32; |
326 | // XCB inserts a uint32_t with the sequence number after the first 32 bytes. |
327 | let seqno = u32::from_ne_bytes([header[32], header[33], header[34], header[35]]); |
328 | let seqno = self.reconstruct_full_sequence(seqno); |
329 | |
330 | // The first byte contains the event type, check for XGE events |
331 | if (*event & 0x7f) == super::protocol::xproto::GE_GENERIC_EVENT { |
332 | // Read the length field of the event to get its length |
333 | let length_field = u32::from_ne_bytes([header[4], header[5], header[6], header[7]]); |
334 | let length_field: usize = length_field |
335 | .try_into() |
336 | .or(Err(ParseError::ConversionFailed))?; |
337 | length += length_field * 4; |
338 | // Discard the `full_sequence` field inserted by xcb at |
339 | // the 32-byte boundary. |
340 | std::ptr::copy(event.add(36), event.add(32), length_field * 4); |
341 | } |
342 | Ok((CSlice::new(header.into_ptr(), length), seqno)) |
343 | } |
344 | |
345 | /// Reconstruct a full sequence number based on a partial value. |
346 | /// |
347 | /// The assumption for the algorithm here is that the given sequence number was received |
348 | /// recently. Thus, the maximum sequence number that was received so far is used to fill in the |
349 | /// missing bytes for the result. |
350 | fn reconstruct_full_sequence(&self, seqno: u32) -> SequenceNumber { |
351 | reconstruct_full_sequence_impl( |
352 | self.maximum_sequence_received.load(Ordering::Relaxed), |
353 | seqno, |
354 | ) |
355 | } |
356 | } |
357 | |
358 | impl RequestConnection for XCBConnection { |
359 | type Buf = CSlice; |
360 | |
361 | fn send_request_with_reply<R>( |
362 | &self, |
363 | bufs: &[IoSlice<'_>], |
364 | fds: Vec<RawFdContainer>, |
365 | ) -> Result<Cookie<'_, Self, R>, ConnectionError> |
366 | where |
367 | R: TryParse, |
368 | { |
369 | Ok(Cookie::new( |
370 | self, |
371 | self.send_request(bufs, fds, true, false)?, |
372 | )) |
373 | } |
374 | |
375 | fn send_request_with_reply_with_fds<R>( |
376 | &self, |
377 | bufs: &[IoSlice<'_>], |
378 | fds: Vec<RawFdContainer>, |
379 | ) -> Result<CookieWithFds<'_, Self, R>, ConnectionError> |
380 | where |
381 | R: TryParseFd, |
382 | { |
383 | Ok(CookieWithFds::new( |
384 | self, |
385 | self.send_request(bufs, fds, true, true)?, |
386 | )) |
387 | } |
388 | |
389 | fn send_request_without_reply( |
390 | &self, |
391 | bufs: &[IoSlice<'_>], |
392 | fds: Vec<RawFdContainer>, |
393 | ) -> Result<VoidCookie<'_, Self>, ConnectionError> { |
394 | Ok(VoidCookie::new( |
395 | self, |
396 | self.send_request(bufs, fds, false, false)?, |
397 | )) |
398 | } |
399 | |
400 | fn discard_reply(&self, sequence: SequenceNumber, _kind: RequestKind, mode: DiscardMode) { |
401 | match mode { |
402 | DiscardMode::DiscardReplyAndError => unsafe { |
403 | // libxcb can throw away everything for us |
404 | raw_ffi::xcb_discard_reply64(self.conn.as_ptr(), sequence); |
405 | }, |
406 | // We have to check for errors ourselves |
407 | DiscardMode::DiscardReply => self.errors.discard_reply(sequence), |
408 | } |
409 | } |
410 | |
411 | fn prefetch_extension_information( |
412 | &self, |
413 | extension_name: &'static str, |
414 | ) -> Result<(), ConnectionError> { |
415 | self.ext_mgr |
416 | .lock() |
417 | .unwrap() |
418 | .prefetch_extension_information(self, extension_name) |
419 | } |
420 | |
421 | fn extension_information( |
422 | &self, |
423 | extension_name: &'static str, |
424 | ) -> Result<Option<ExtensionInformation>, ConnectionError> { |
425 | self.ext_mgr |
426 | .lock() |
427 | .unwrap() |
428 | .extension_information(self, extension_name) |
429 | } |
430 | |
431 | fn wait_for_reply_or_raw_error( |
432 | &self, |
433 | sequence: SequenceNumber, |
434 | ) -> Result<ReplyOrError<CSlice>, ConnectionError> { |
435 | unsafe { |
436 | let mut error = null_mut(); |
437 | let reply = raw_ffi::xcb_wait_for_reply64(self.conn.as_ptr(), sequence, &mut error); |
438 | match (reply.is_null(), error.is_null()) { |
439 | (true, true) => Err(Self::connection_error_from_connection(self.conn.as_ptr())), |
440 | (false, true) => Ok(ReplyOrError::Reply(self.wrap_reply(reply as _, sequence))), |
441 | (true, false) => Ok(ReplyOrError::Error(self.wrap_error(error as _, sequence))), |
442 | // At least one of these pointers must be NULL. |
443 | (false, false) => unreachable!(), |
444 | } |
445 | } |
446 | } |
447 | |
448 | fn wait_for_reply(&self, sequence: SequenceNumber) -> Result<Option<CSlice>, ConnectionError> { |
449 | match self.wait_for_reply_or_raw_error(sequence)? { |
450 | ReplyOrError::Reply(reply) => Ok(Some(reply)), |
451 | ReplyOrError::Error(error) => { |
452 | self.errors.append_error((sequence, error)); |
453 | Ok(None) |
454 | } |
455 | } |
456 | } |
457 | |
458 | #[cfg (unix)] |
459 | fn wait_for_reply_with_fds_raw( |
460 | &self, |
461 | sequence: SequenceNumber, |
462 | ) -> Result<ReplyOrError<BufWithFds, Buffer>, ConnectionError> { |
463 | let buffer = match self.wait_for_reply_or_raw_error(sequence)? { |
464 | ReplyOrError::Reply(reply) => reply, |
465 | ReplyOrError::Error(error) => return Ok(ReplyOrError::Error(error)), |
466 | }; |
467 | |
468 | // Get a pointer to the array of integers where libxcb saved the FD numbers. |
469 | // libxcb saves the list of FDs after the data of the reply. Since the reply's |
470 | // length is encoded in "number of 4 bytes block", the following pointer is aligned |
471 | // correctly (if malloc() returned an aligned chunk, which it does). |
472 | #[allow (clippy::cast_ptr_alignment)] |
473 | let fd_ptr = (unsafe { buffer.as_ptr().add(buffer.len()) }) as *const RawFd; |
474 | |
475 | // The number of FDs is in the second byte (= buffer[1]) in all replies. |
476 | let fd_slice = unsafe { std::slice::from_raw_parts(fd_ptr, usize::from(buffer[1])) }; |
477 | let fd_vec = fd_slice.iter().map(|&fd| RawFdContainer::new(fd)).collect(); |
478 | |
479 | Ok(ReplyOrError::Reply((buffer, fd_vec))) |
480 | } |
481 | |
482 | #[cfg (not(unix))] |
483 | fn wait_for_reply_with_fds_raw( |
484 | &self, |
485 | _sequence: SequenceNumber, |
486 | ) -> Result<ReplyOrError<BufWithFds, Buffer>, ConnectionError> { |
487 | unimplemented!("FD passing is currently only implemented on Unix-like systems" ) |
488 | } |
489 | |
490 | fn check_for_raw_error( |
491 | &self, |
492 | sequence: SequenceNumber, |
493 | ) -> Result<Option<Buffer>, ConnectionError> { |
494 | let cookie = raw_ffi::xcb_void_cookie_t { |
495 | sequence: sequence as _, |
496 | }; |
497 | let error = unsafe { raw_ffi::xcb_request_check(self.conn.as_ptr(), cookie) }; |
498 | if error.is_null() { |
499 | Ok(None) |
500 | } else { |
501 | unsafe { Ok(Some(self.wrap_error(error as _, sequence))) } |
502 | } |
503 | } |
504 | |
505 | fn maximum_request_bytes(&self) -> usize { |
506 | 4 * unsafe { raw_ffi::xcb_get_maximum_request_length(self.conn.as_ptr()) as usize } |
507 | } |
508 | |
509 | fn prefetch_maximum_request_bytes(&self) { |
510 | unsafe { raw_ffi::xcb_prefetch_maximum_request_length(self.conn.as_ptr()) }; |
511 | } |
512 | |
513 | fn parse_error(&self, error: &[u8]) -> Result<crate::x11_utils::X11Error, ParseError> { |
514 | let ext_mgr = self.ext_mgr.lock().unwrap(); |
515 | crate::x11_utils::X11Error::try_parse(error, &*ext_mgr) |
516 | } |
517 | |
518 | fn parse_event(&self, event: &[u8]) -> Result<crate::protocol::Event, ParseError> { |
519 | let ext_mgr = self.ext_mgr.lock().unwrap(); |
520 | crate::protocol::Event::parse(event, &*ext_mgr) |
521 | } |
522 | } |
523 | |
524 | impl Connection for XCBConnection { |
525 | fn wait_for_raw_event_with_sequence(&self) -> Result<RawEventAndSeqNumber, ConnectionError> { |
526 | if let Some(error) = self.errors.get(self) { |
527 | return Ok((error.1, error.0)); |
528 | } |
529 | unsafe { |
530 | let event = raw_ffi::xcb_wait_for_event(self.conn.as_ptr()); |
531 | if event.is_null() { |
532 | return Err(Self::connection_error_from_connection(self.conn.as_ptr())); |
533 | } |
534 | Ok(self.wrap_event(event as _)?) |
535 | } |
536 | } |
537 | |
538 | fn poll_for_raw_event_with_sequence( |
539 | &self, |
540 | ) -> Result<Option<RawEventAndSeqNumber>, ConnectionError> { |
541 | if let Some(error) = self.errors.get(self) { |
542 | return Ok(Some((error.1, error.0))); |
543 | } |
544 | unsafe { |
545 | let event = raw_ffi::xcb_poll_for_event(self.conn.as_ptr()); |
546 | if event.is_null() { |
547 | let err = raw_ffi::xcb_connection_has_error(self.conn.as_ptr()); |
548 | if err == 0 { |
549 | return Ok(None); |
550 | } else { |
551 | return Err(Self::connection_error_from_c_error(err)); |
552 | } |
553 | } |
554 | Ok(Some(self.wrap_event(event as _)?)) |
555 | } |
556 | } |
557 | |
558 | fn flush(&self) -> Result<(), ConnectionError> { |
559 | // xcb_flush() returns 0 if the connection is in (or just entered) an error state, else 1. |
560 | let res = unsafe { raw_ffi::xcb_flush(self.conn.as_ptr()) }; |
561 | if res != 0 { |
562 | Ok(()) |
563 | } else { |
564 | unsafe { Err(Self::connection_error_from_connection(self.conn.as_ptr())) } |
565 | } |
566 | } |
567 | |
568 | fn generate_id(&self) -> Result<u32, ReplyOrIdError> { |
569 | unsafe { |
570 | let id = raw_ffi::xcb_generate_id(self.conn.as_ptr()); |
571 | // XCB does not document the behaviour of `xcb_generate_id` when |
572 | // there is an error. Looking at its source code it seems that it |
573 | // returns `-1` (presumably `u32::max_value()`). |
574 | if id == u32::max_value() { |
575 | Err(Self::connection_error_from_connection(self.conn.as_ptr()).into()) |
576 | } else { |
577 | Ok(id) |
578 | } |
579 | } |
580 | } |
581 | |
582 | fn setup(&self) -> &Setup { |
583 | &self.setup |
584 | } |
585 | } |
586 | |
587 | #[cfg (unix)] |
588 | impl AsRawFd for XCBConnection { |
589 | fn as_raw_fd(&self) -> RawFd { |
590 | unsafe { raw_ffi::xcb_get_file_descriptor(self.conn.as_ptr()) } |
591 | } |
592 | } |
593 | |
594 | // SAFETY: We provide a valid xcb_connection_t that is valid for as long as required by the trait. |
595 | unsafe impl as_raw_xcb_connection::AsRawXcbConnection for XCBConnection { |
596 | fn as_raw_xcb_connection(&self) -> *mut as_raw_xcb_connection::xcb_connection_t { |
597 | self.get_raw_xcb_connection().cast() |
598 | } |
599 | } |
600 | |
601 | /// Reconstruct a partial sequence number based on a recently received 'full' sequence number. |
602 | /// |
603 | /// The new sequence number may be before or after the `recent` sequence number. |
604 | fn reconstruct_full_sequence_impl(recent: SequenceNumber, value: u32) -> SequenceNumber { |
605 | // Expand 'value' to a full sequence number. The high bits are copied from 'recent'. |
606 | let u32_max = SequenceNumber::from(u32::max_value()); |
607 | let expanded_value = SequenceNumber::from(value) | (recent & !u32_max); |
608 | |
609 | // The "step size" is the difference between two sequence numbers that cannot be told apart |
610 | // from their truncated value. |
611 | let step: SequenceNumber = SequenceNumber::from(1u8) << 32; |
612 | |
613 | // There are three possible values for the returned sequence number: |
614 | // - The extended value |
615 | // - The extended value plus one step |
616 | // - The extended value minus one step |
617 | // Pick the value out of the possible values that is closest to `recent`. |
618 | let result = [ |
619 | expanded_value, |
620 | expanded_value + step, |
621 | expanded_value.wrapping_sub(step), |
622 | ] |
623 | .iter() |
624 | .copied() |
625 | .min_by_key(|&value| { |
626 | if value > recent { |
627 | value - recent |
628 | } else { |
629 | recent - value |
630 | } |
631 | }) |
632 | .unwrap(); |
633 | // Just because: Check that the result matches the passed-in value in the low bits |
634 | assert_eq!( |
635 | result & SequenceNumber::from(u32::max_value()), |
636 | SequenceNumber::from(value), |
637 | ); |
638 | result |
639 | } |
640 | |
641 | #[cfg (test)] |
642 | mod test { |
643 | use super::XCBConnection; |
644 | use std::ffi::CString; |
645 | |
646 | #[test ] |
647 | fn xcb_connect_smoke_test() { |
648 | // in cfg(test), raw_ffi does not call XCB, but instead uses a mock. This test calls into |
649 | // that mock and tests a bit of XCBConnection. |
650 | |
651 | let str = CString::new("display name" ).unwrap(); |
652 | let (_conn, screen) = XCBConnection::connect(Some(&str)).expect("Failed to 'connect'" ); |
653 | assert_eq!(screen, 0); |
654 | } |
655 | |
656 | #[test ] |
657 | fn reconstruct_full_sequence() { |
658 | use super::reconstruct_full_sequence_impl; |
659 | let max32 = u32::max_value(); |
660 | let max32_: u64 = max32.into(); |
661 | let max32_p1 = max32_ + 1; |
662 | let large_offset = max32_p1 * u64::from(u16::max_value()); |
663 | for &(recent, value, expected) in &[ |
664 | (0, 0, 0), |
665 | (0, 10, 10), |
666 | // This one is a special case: Technically, -1 is closer and should be reconstructed, |
667 | // but -1 does not fit into an unsigned integer. |
668 | (0, max32, max32_), |
669 | (max32_, 0, max32_p1), |
670 | (max32_, 10, max32_p1 + 10), |
671 | (max32_, max32, max32_), |
672 | (max32_p1, 0, max32_p1), |
673 | (max32_p1, 10, max32_p1 + 10), |
674 | (max32_p1, max32, max32_), |
675 | (large_offset | 0xdead_cafe, 0, large_offset + max32_p1), |
676 | (large_offset | 0xdead_cafe, max32, large_offset + max32_), |
677 | (0xabcd_1234_5678, 0xf000_0000, 0xabcc_f000_0000), |
678 | (0xabcd_8765_4321, 0xf000_0000, 0xabcd_f000_0000), |
679 | ] { |
680 | let actual = reconstruct_full_sequence_impl(recent, value); |
681 | assert_eq!( |
682 | actual, expected, |
683 | "reconstruct( {:x}, {:x}) == {:x}, but was {:x}" , |
684 | recent, value, expected, actual, |
685 | ); |
686 | } |
687 | } |
688 | } |
689 | |