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
5use std::convert::TryInto;
6use std::ffi::CStr;
7use std::io::{Error as IOError, ErrorKind, IoSlice};
8use std::os::raw::c_int;
9#[cfg(unix)]
10use std::os::unix::io::{AsRawFd, RawFd};
11use std::ptr::{null, null_mut};
12use std::sync::{atomic::Ordering, Mutex};
13
14use libc::c_void;
15
16use crate::connection::{
17 compute_length_field, Connection, ReplyOrError, RequestConnection, RequestKind,
18};
19use crate::cookie::{Cookie, CookieWithFds, VoidCookie};
20pub use crate::errors::{ConnectError, ConnectionError, ParseError, ReplyError, ReplyOrIdError};
21use crate::extension_manager::ExtensionManager;
22use crate::protocol::xproto::Setup;
23use crate::utils::{CSlice, RawFdContainer};
24use crate::x11_utils::{ExtensionInformation, TryParse, TryParseFd};
25
26use x11rb_protocol::{DiscardMode, SequenceNumber};
27
28mod atomic_u64;
29mod pending_errors;
30mod raw_ffi;
31
32use atomic_u64::AtomicU64;
33#[cfg(all(not(test), feature = "dl-libxcb"))]
34pub use raw_ffi::libxcb_library::load_libxcb;
35
36type Buffer = <XCBConnection as RequestConnection>::Buf;
37/// The raw bytes of an event received by [`XCBConnection`] and its sequence number.
38pub type RawEventAndSeqNumber = x11rb_protocol::RawEventAndSeqNumber<Buffer>;
39/// A combination of a buffer and a list of file descriptors for use by [`XCBConnection`].
40pub 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)]
48pub 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
56impl 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
358impl 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
524impl 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)]
588impl 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.
595unsafe 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.
604fn 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)]
642mod 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