x11rb

repository·master·Indexed 19 days ago

https://github.com/psychon/x11rb

High-fidelity Rust bindings for the X11 protocol, designed as a complete implementation of the protocol and its extensions using X11 XML descriptions. It provides a safe alternative to FFI-based libraries by reimplementing protocol serialization and deserialization in pure Rust. The ecosystem includes x11rb-protocol for generated data structures and x11rb-async for asynchronous operations.

Tokens
46K
Snippets
145
Records
229
Agent score
64%

What's inside x11rb

  1. Overview of x11rb

    master
    x11rb provides Rust bindings for the X11 protocol. It supports the full X11 protocol and all extensions available in xcb-proto, including advanced capabilities like FD (File Descriptor) passing with the server.
  2. Understand the structure of generated X11 protocol code

    master

    The x11rb ecosystem uses a code generator based on xcb-proto XML descriptions to produce Rust bindings. The resulting code is split across two main crates:

    • x11rb-protocol: Contains the bulk of the generated code (XIDs, structs, enums, etc.).
    • x11rb: Provides helper functions to simplify sending requests to the X11 server.
    • x11rb-async: Contains asynchronous versions of the generated code, which follow a similar structure to x11rb.

    Generated code is located at the beginning of modules (e.g., xproto in x11rb-protocol) and includes necessary imports for serialization (Serialize), parsing (TryParse), and utility functions.

  3. What is xcb-proto?

    master
    xcb-proto provides the XML-XCB protocol descriptions used by libxcb to generate its code and API. These descriptions are decoupled from the XCB transport layer to enable reuse by other projects, such as language bindings (like x11rb), protocol dissectors, or documentation generators. This architecture allows new X11 extensions to be supported simply by providing an XML description, without requiring manual code duplication.
  4. Structure of generated X11 Events

    master

    X11 events are generated as Rust structs that map directly to the X11 protocol definitions. Each event struct includes standard fields such as response_type and sequence, along with event-specific data. For example, a KeyPressEvent contains information about the keycode, timestamp, window involved, and coordinates. These structs implement TryParse for decoding from raw bytes and Serialize for encoding to bytes, ensuring they match the expected X11 wire format (including necessary padding).

    /// Opcode for the KeyPress event
    pub const KEY_PRESS_EVENT: u8 = 2;
    
    #[derive(Clone, Copy, Default)]
    #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash))]
    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
    pub struct KeyPressEvent {
        pub response_type: u8,
        pub detail: Keycode,
        pub sequence: u16,
        pub time: Timestamp,
        pub root: Window,
        pub event: Window,
        pub child: Window,
        pub root_x: i16,
        pub root_y: i16,
        pub event_x: i16,
        pub event_y: i16,
        pub state: KeyButMask,
        pub same_screen: bool,
    }
  5. How X11 requests are structured in x11rb

    master

    X11 requests are implemented using a two-tier approach involving x11rb-protocol and the x11rb crate:

    1. x11rb-protocol: Defines the raw data structures. For every request, a struct is generated representing the request's fields. These structs implement Request and provide serialize and try_parse_request methods.
    2. x11rb: Provides high-level, ergonomic functions to send these requests. These functions are available both as standalone global functions and as methods on the ConnectionExt extension trait.

    Requests are categorized into three types:

    • Requests without a reply: These return a VoidCookie. Use conn.send_request_without_reply() or the extension trait method.
    • Requests with a reply: These return a Cookie<'_, Conn, ReplyType>. The ReplyType is a generated struct containing the response data.
    • Requests with a switch: These involve optional fields controlled by a bitmask. They use an auxiliary Aux struct to manage the optional fields and the corresponding bitmask.
  6. Use 'Real' enumerations

    master

    To prevent ParseError when an X11 server sends an undefined value, enumerations are not implemented as Rust enums. Instead, they are implemented as newtypes around numbers (e.g., struct BackingStore(u32)).

    Key features:

    • Constants: Defined as pub const values on the struct (e.g., BackingStore::ALWAYS).
    • Conversions: Implement From and TryFrom for various integer types (u8, u16, u32) and Option<u32>.
    • Debug: Uses pretty_print_enum to show the human-readable name of the variant if it matches a known constant.
    #[derive(Clone, Copy, Default, PartialEq, Eq)]
    pub struct BackingStore(u32);
    
    impl BackingStore {
        pub const NOT_USEFUL: Self = Self(0);
        pub const WHEN_MAPPED: Self = Self(1);
        pub const ALWAYS: Self = Self(2);
    }
  7. Use Bitmask enumerations

    master

    Bitmask enumerations are used for fields where multiple bits can be set simultaneously. Like 'Real' enums, these are implemented as newtypes around an integer (e.g., struct ConfigWindow(u16)).

    Key features:

    • Constants: Defined as bit-shifted values (e.g., ConfigWindow::X = Self(1 << 0)).
    • Bitwise Operations: The bitmask_binop! macro is used to implement BitOr and BitOrAssign, allowing you to combine flags using the | operator.
    • Debug: Uses pretty_print_bitmask to show all active flags in a human-readable format.
    #[derive(Clone, Copy, Default, PartialEq, Eq)]
    pub struct ConfigWindow(u16);
    
    impl ConfigWindow {
        pub const X: Self = Self(1 << 0);
        pub const Y: Self = Self(1 << 1);
        pub const WIDTH: Self = Self(1 << 2);
        // ...
    }
    
    // Allows: let config = ConfigWindow::X | ConfigWindow::Y;
    bitmask_binop!(ConfigWindow, u16);
  8. Compare x11rb with other Rust X11 libraries

    master
    When choosing a Rust library for X11 access, x11rb distinguishes itself by reimplementing the X11 protocol serialization and deserialization in pure Rust based on the libxcb XML description. Unlike FFI-based libraries (like rust-xcb, xcb-dl, or xcb-sys) that wrap C libraries and require significant unsafe usage, x11rb aims to provide Rust's safety guarantees by only using libxcb for sending and receiving opaque packets. This approach minimizes unsafe code to a small set of FFI bindings, whereas other libraries often require users to write unsafe code to handle events or interact with the server.
  9. Use common protocol enums in x11rb-protocol

    master

    The x11rb_protocol::protocol module contains common code used across the library. It provides enums covering all possible X11 requests, replies, errors, and events. These enums allow you to interact with protocol messages generically. For example, you can use these enums to access common fields like sequence_number from an event without needing to manually match against every specific event type.

    use x11rb_protocol::protocol; 
    // Example: Accessing common fields via protocol enums
    // (Conceptual usage based on documentation description)
  10. Work with variable-length structs

    master

    Variable-length structs contain lists of other items (e.g., a list of visuals).

    • The length field from the X11 protocol is handled implicitly by a Vec<T> in the Rust struct.
    • A helper method is generated to retrieve the length of the list (e.g., visuals_len()) to match the protocol's expected field value.
    • The Serialize implementation handles the padding and length field required by the X11 wire format.
    #[derive(Clone, Default)]
    pub struct Depth {
        pub depth: u8,
        pub visuals: Vec<Visualtype>,
    }
    
    impl Depth {
        /// Returns the length of the visuals list as a u16.
        pub fn visuals_len(&self) -> u16 {
            self.visuals.len().try_into().unwrap()
        }
    }
  11. Work with fixed-length structs

    master

    Fixed-length structs are used as building blocks for requests and events. They implement two key traits:

    1. TryParse: Allows the struct to be parsed from raw bytes received from the X11 server.
    2. Serialize: Allows the struct to be converted into native-endian bytes to be sent to the server.

    If the extra-traits feature is enabled, these structs also implement common traits like Debug, PartialEq, and Hash.

    #[derive(Clone, Copy, Default)]
    pub struct Point {
        pub x: i16,
        pub y: i16,
    }
    
    // Implements TryParse for reading from the server
    // Implements Serialize for writing to the server