Crate xcb

source ·
Expand description

Rust bindings to the XCB library.

The X protocol C-language Binding (XCB - https://xcb.freedesktop.org/) is a replacement for Xlib featuring a small footprint, latency hiding, direct access to the protocol, improved threading support, and extensibility.

The communication is established with the X server by the creation of a Connection object.

A client communicates with the server by sending requests. There are 2 types of requests:

Requests are passed to the server by filling a request structure e.g. x::CreateWindow and passing it to Connection::send_request.

The server can also communicate with clients by sending Events. The client listens to events with calls such as Connection::wait_for_event (blocking) or Connection::poll_for_event (non-blocking).

The x module contains definitions of the core X protocol. Each extension is defined in its own module such as xkb or render, and is activated by a cargo feature of the same name.

Example

Here is a walk-through of a simple xcb client.

// we import the necessary modules (only the core X module in this application).
use xcb::{x};
// we need to import the `Xid` trait for the `resource_id` call down there.
use xcb::{Xid};

// Many xcb functions return a `xcb::Result` or compatible result.
fn main() -> xcb::Result<()> {
    // Connect to the X server.
    let (conn, screen_num) = xcb::Connection::connect(None)?;

    // Fetch the `x::Setup` and get the main `x::Screen` object.
    let setup = conn.get_setup();
    let screen = setup.roots().nth(screen_num as usize).unwrap();

    // Generate an `Xid` for the client window.
    // The type inference is needed here.
    let window: x::Window = conn.generate_id();

    // We can now create a window. For this we pass a `Request`
    // object to the `send_request_checked` method. The method
    // returns a cookie that will be used to check for success.
    let cookie = conn.send_request_checked(&x::CreateWindow {
        depth: x::COPY_FROM_PARENT as u8,
        wid: window,
        parent: screen.root(),
        x: 0,
        y: 0,
        width: 150,
        height: 150,
        border_width: 0,
        class: x::WindowClass::InputOutput,
        visual: screen.root_visual(),
        // this list must be in same order than `Cw` enum order
        value_list: &[
            x::Cw::BackPixel(screen.white_pixel()),
            x::Cw::EventMask(x::EventMask::EXPOSURE | x::EventMask::KEY_PRESS)
        ],
    });
    // We now check if the window creation worked.
    // A cookie can't be cloned; it is moved to the function.
    conn.check_request(cookie)?;

    // Let's change the window title
    let cookie = conn.send_request_checked(&x::ChangeProperty {
        mode: x::PropMode::Replace,
        window,
        property: x::ATOM_WM_NAME,
        r#type: x::ATOM_STRING,
        data: b"My XCB Window",
    });
    // And check for success again
    conn.check_request(cookie)?;

    // We now show ("map" in X terminology) the window.
    // This time we do not check for success, so we discard the cookie.
    conn.send_request(&x::MapWindow {
        window,
    });

    // We need a few atoms for our application.
    // We send a few requests in a row and wait for the replies after.
    let (wm_protocols, wm_del_window, wm_state, wm_state_maxv, wm_state_maxh) = {
        let cookies = (
            conn.send_request(&x::InternAtom {
                only_if_exists: true,
                name: b"WM_PROTOCOLS",
            }),
            conn.send_request(&x::InternAtom {
                only_if_exists: true,
                name: b"WM_DELETE_WINDOW",
            }),
            conn.send_request(&x::InternAtom {
                only_if_exists: true,
                name: b"_NET_WM_STATE",
            }),
            conn.send_request(&x::InternAtom {
                only_if_exists: true,
                name: b"_NET_WM_STATE_MAXIMIZED_VERT",
            }),
            conn.send_request(&x::InternAtom {
                only_if_exists: true,
                name: b"_NET_WM_STATE_MAXIMIZED_HORZ",
            }),
        );
        (
            conn.wait_for_reply(cookies.0)?.atom(),
            conn.wait_for_reply(cookies.1)?.atom(),
            conn.wait_for_reply(cookies.2)?.atom(),
            conn.wait_for_reply(cookies.3)?.atom(),
            conn.wait_for_reply(cookies.4)?.atom(),
        )
    };

    // We now activate the window close event by sending the following request.
    // If we don't do this we can still close the window by clicking on the "x" button,
    // but the event loop is notified through a connection shutdown error.
    conn.check_request(conn.send_request_checked(&x::ChangeProperty {
        mode: x::PropMode::Replace,
        window,
        property: wm_protocols,
        r#type: x::ATOM_ATOM,
        data: &[wm_del_window],
    }))?;

    // Previous request was checked, so a flush is not necessary in this case.
    // Otherwise, here is how to perform a connection flush.
    conn.flush()?;

    let mut maximized = false;

    // We enter the main event loop
    loop {
        match conn.wait_for_event()? {
            xcb::Event::X(x::Event::KeyPress(ev)) => {
                if ev.detail() == 0x3a {
                    // The M key was pressed
                    // (M only on qwerty keyboards. Keymap support is done
                    // with the `xkb` extension and the `xkbcommon-rs` crate)

                    // We toggle maximized state, for this we send a message
                    // by building a `x::ClientMessageEvent` with the proper
                    // atoms and send it to the server.

                    let data = x::ClientMessageData::Data32([
                        if maximized { 0 } else { 1 },
                        wm_state_maxv.resource_id(),
                        wm_state_maxh.resource_id(),
                        0,
                        0,
                    ]);
                    let event = x::ClientMessageEvent::new(window, wm_state, data);
                    let cookie = conn.send_request_checked(&x::SendEvent {
                        propagate: false,
                        destination: x::SendEventDest::Window(screen.root()),
                        event_mask: x::EventMask::STRUCTURE_NOTIFY,
                        event: &event,
                    });
                    conn.check_request(cookie)?;

                    // Same as before, if we don't check for error, we have to flush
                    // the connection.
                    // conn.flush()?;

                    maximized = !maximized;
                } else if ev.detail() == 0x18 {
                    // Q (on qwerty)

                    // We exit the event loop (and the program)
                    break Ok(());
                }
            }
            xcb::Event::X(x::Event::ClientMessage(ev)) => {
                // We have received a message from the server
                if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
                    if atom == wm_del_window.resource_id() {
                        // The received atom is "WM_DELETE_WINDOW".
                        // We can check here if the user needs to save before
                        // exit, or in our case, exit right away.
                        break Ok(());
                    }
                }
            }
            _ => {}
        }
    }
}

Cargo features

The following Cargo features are available

xlib_xcb

This feature activates the use of xlib::Display to connect to XCB, therefore making available both Xlib and XCB functions to the same connection. While XCB is sufficient to handle all communication with the X server, some things can still only be done by Xlib. E.g. hardware initialization for OpenGL is done by Xlib only.

debug_atom_names

When this feature is activated, the fmt::Debug implementation for x::Atom will print out the name of the atom in addition to its value.

E.g. the feature would turn Atom { res_id: 303 } into Atom("Abs Pressure" ; 303).

This can be useful in situations where you are not sure which atom you have to intern in order to look up some data in a reply.

It should be noted that the feature sets global variable to have access to the connection in the fmt::Debug implementation, and that the Debug print have side effects (x::GetAtomName requests) which can sometimes not be desirable. The feature should therefore only be activated when needed.

libxcb_v1_14

This feature is enabled by default and activates the libxcb API version 1.14. To use a version of the libxcb API prior to 1.14, you must disable it.

Extension features

The following X extensions are activated by a cargo feature:

Extension nameCargo feature
Compositecomposite
DAMAGEdamage
DPMSdpms
DRI2dri2
DRI3dri3
Generic Event Extensionge
GLXglx
Presentpresent
RANDRrandr
RECORDrecord
RENDERrender
X-Resourceres
MIT-SCREEN-SAVERscreensaver
SHAPEshape
MIT-SHMshm
SYNCsync
XEVIExevie
XFree86-DRIxf86dri
XFree86-VidModeExtensionxf86vidmode
XFIXESxfixes
XINERAMAxinerama
XInputExtensionxinput
XKEYBOARDxkb
XpExtensionxprint
SELinuxxselinux
TESTxtest
XVideoxv
XVideo-MotionCompensationxvmc

Modules

  • The BIG-REQUESTS extension.
  • The Composite X extension.
  • The DAMAGE X extension.
  • The DPMS X extension.
  • The DRI2 X extension.
  • The DRI3 X extension.
  • Module for Foreign Function Interface bindings.
  • The Generic Event Extension X extension.
  • The GLX X extension.
  • The Present X extension.
  • The RANDR X extension.
  • The RECORD X extension.
  • The RENDER X extension.
  • The X-Resource X extension.
  • The MIT-SCREEN-SAVER X extension.
  • The SHAPE X extension.
  • The MIT-SHM X extension.
  • The SYNC X extension.
  • The core X protocol definitions
  • The XC-MISC extension.
  • The XEVIE X extension.
  • The XFree86-DRI X extension.
  • The XFree86-VidModeExtension X extension.
  • The XFIXES X extension.
  • The XINERAMA X extension.
  • The XInputExtension X extension.
  • The XKEYBOARD X extension.
  • The XpExtension X extension.
  • The SELinux X extension.
  • The XTEST X extension.
  • The XVideo X extension.
  • The XVideo-MotionCompensation X extension.

Macros

  • An helper macro that generate a struct of atoms.

Structs

  • Container for authentication information to connect to the X server
  • Connection is the central object of XCB.
  • Display info returned by parse_display
  • Extension data as returned by each extensions get_extension_data.
  • A slice to a Latin-1 (aka. ISO 8859-1) string.
  • Latin-1 (aka. ISO 8859-1) of fixed size
  • A struct owning a Latin-1 (aka. ISO 8859-1) string.
  • A struct that serve as an identifier for internal special queue in XCB
  • an event was not recognized as part of the core protocol or any enabled extension
  • The default cookie type returned by void-requests.
  • The checked cookie type returned by void-requests.

Enums

  • Error type that is returned by Connection::has_error.
  • The general error type for Rust-XCB.
  • Unified Event type from the X server.
  • Determines whether Xlib or XCB owns the event queue of Connection.
  • Refers to a X protocol extension.
  • Error that can produce Latin-1 string operations
  • A protocol error issued from the X server

Traits

  • A trait to designate base protocol errors.
  • Trait for base events (aka. non GE_GENERIC events)
  • General trait for cookies returned by requests.
  • A marker trait for a cookie that allows synchronized error checking.
  • A trait for checked cookies of requests that send a reply.
  • A trait for unchecked cookies of requests that send a reply.
  • A trait for GE_GENERIC events
  • Trait for types that own a C allocated pointer and are represented by the data pointed to.
  • Trait implemented by all requests to send the serialized data over the wire.
  • Trait for request replies
  • Trait implemented by requests types.
  • Trait for requests that return a reply.
  • Marker trait for requests that do not return a reply.
  • A X resource trait
  • Trait for X resources that can be created directly from Connection::generate_id

Functions

Type Aliases