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