Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update project doc #242

Merged
merged 3 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,28 @@
All PRs are welcome.

When issueing a pull-request, make sure that the repo is formatted with `cargo fmt`
and that `cargo clippy --all-features` and `cargo test --all-features` return no error.
and that `cargo test --all-features` return no error.

The source code is organize as follow:

## `build/`

The code generation program (aka. the build script).
This program scans the XML definition to produce the Rust source code.

It is often tedious to understand changes of the Rust generated code only by looking at the code generation source code.
If your PR include a modification of the code generation, please consider including a diff of the generated code.
It is easily done as follow:
- run `./gen.sh previous` before starting your PR
- run `./gen.sh current` when you think your work is ready.
- include the output of `diff -ur gen/previous gen/current` in the text of the PR.
(see [#110](https://github.com/rust-x-bindings/rust-xcb/pull/110) for an example)

In VsCode you can observe a diff directly in the editor by running `gen/diff.sh [module name]`.
This program scans the XML definition to produce the Rust source code of the protocol implementation.

Sometimes, you have to hardcode exceptions in the code generation (e.g. generated different code for a specific request or event).
If you do this (it is better if you don't), add an entry to the `EXCEPTION_LIST.txt` file.

If you need to write unit test for the generated code, you can add them under `src/test.rs`
If you need to write unit test for the generated code, you can add them under `src/test.rs`.
There are also a few generated unit tests in the protocol modules.

It is often tedious to understand changes of the Rust generated code only by looking at the code generation source code.
There is utility to observe the changes to the generated code
- run `./gen.sh previous` before starting your PR
- run `./gen.sh current` when you think your work is ready.
- generate a diff:
- In VsCode you can open a diff editor by running `gen/diff.sh [module name]`.
- Otherwise run `diff -ur gen/previous gen/current` to generate a textual diff

## `examples/`

Expand All @@ -45,3 +44,4 @@ Base library source code.

The XML definitions from the XCB project.
These defintions were slightly modified compared to the upstream definitions to ease a part of the code generation.
The unmodified upstream XML definitions are kept in the `xml/upstream` folder.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Copyright (c) 2013 James Miller <james@aatch.net>
Copyright (c) 2016-2023 Remi Thebault <remi.thebault@gmail.com> and many others
Copyright (c) 2016-2023 Rémi Thebault <@rtbo> and many others

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
Expand Down
151 changes: 41 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ https://rust-x-bindings.github.io/rust-xcb/xcb/

Rust-XCB is constituted of the following components:
- the core library
- the X protocol and its extensions
- the X protocol and its extensions (generated from XML)

See [CONTRIBUTING.md](https://github.com/rust-x-bindings/rust-xcb/blob/main/CONTRIBUTING.md) for contributions.

Expand All @@ -31,94 +31,38 @@ The `Raw` trait is also provided to convert into and from C event or error types

## The protocol implementation

The core X protocol and all the extensions present in XCB are generated by the
build scripts, entirely written in Rust.
The core X protocol and all the extensions present in XCB are generated from (almost exactly) the same XML
as the XCB C bindings.
The generation is done by the build scripts, entirely written in Rust.
The build script do not generate bindings to the C protocol extensions, it
generates a safe Rust implementation of the protocol:
generates directly a safe Rust implementation of the protocol:
- Simple structures that have the same memory layout than C are translated in
Rust directly (e.g. points etc.)
- More complex structures wrap a slice of raw data and provide accessor methods
(`Debug` will still print all the members)
(`Debug` will still print all the members correctly)
- The masks use the `bitflags` crate macro
- The enums are enums!
- The unions are also enums, but they carry data
- The Xids (handles to windows, pixmaps etc.) are type-safe implementations of
the `Xid` trait, not just integers.
- The unions are also enums that carry data
- The Xids (handles to windows, pixmaps etc.) are type-safe implementations of the `Xid` trait.
- The requests are structures that serialize themselves when passed to the
`Connection`.
- Each request has two type of cookie, for checked and unchecked requests.
This allows type safe reply fetching and error checking
- The protocol and each extension provide an `Event` and an `Error` enum,
which are unified by the core library.

## Debugging
## API

All types in Rust XCB implement `Debug` in a way that allows recursive debug print.
E.g. iterators will not print a useless pointer value, but will recurse down to each element.

There is in addition the optional `"debug_atom_names"` cargo feature under which each atom
will print its name for easier debugging in some situations.
For example, Xinput provide some information about input devices with atom identifiers.
This allows you to quickly look-up which atoms you need to intern and seek for.
E.g. the feature would turn:
```
Atom {
res_id: 303,
}
```
into
```
Atom("Abs Pressure" ; 303)
```

The feature sets global variable to have access to the connection in the `Debug::fmt` call,
so it should be activated only when needed.

## The v1.0 API

Here are some highlights of the `v1.0` API compared to the `v0.x` API.
Here are some highlights of the API.

### Modules

Previously, the core X protocol was directly accessible under the `xcb` crate
namespace. This is no longer the case, the protocol is under the `x` module,
and each extension gets its own module too like before.
Directly under the `xcb` crate is the hand-written core library.
The core protocol is generated in the `x` module and each extension protocol is also generated in its own module.

Only the core library is directly accessible under `xcb`.
This helps for a clean separation of concerns and save a lot of confusion in
regards to the new event and error types.

### Window creation
### Window creation request

```rust
// v0.x
let window = conn.generate_id(); // this is a u32

let values = [
(xcb::CW_BACK_PIXEL, screen.white_pixel()),
(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_KEY_PRESS,
),
];

xcb::create_window(
&conn,
xcb::COPY_FROM_PARENT as u8,
window,
screen.root(),
0,
0,
150,
150,
10,
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root_visual(),
&values,
);

// v1.0

let window: x::Window = conn.generate_id();

conn.send_request(&x::CreateWindow {
Expand All @@ -141,56 +85,20 @@ conn.send_request(&x::CreateWindow {

### Checked void request
```rust
// v0.x
// same cookie than for xcb::map_window
let cookie = xcb::map_window_checked(&conn, window);
// this error is a simple wrapper over xcb_generic_error_t
cookie.request_check()?;

// v1.0
// specific cookie type for the checked request
// code would not compile with `conn.send_request(..)`
let cookie = conn.send_request_checked(&x::MapWindow {window});
// reports a resolved error enum (e.g. x::Error::Drawable(..))
// reports `Ok` or a resolved error enum (e.g. x::Error::Drawable(..))
conn.check_request(cookie)?;

```

### Event and error handling
```rust
// 0.x
loop {
let event = conn.wait_for_event();
// Errors are only wrappers around generic C errors and must be casted
// from the event if `response_type` is 0. That seem very safe Rust...
// I am the core author and I don't exactly know how to do proper error
// handling with the 0.x API...
match event {
None => {
break;
}
Some(event) => {
let r = event.response_type();
if r == 0 {
// This is an error.
panic!("received error from the X server");
} else if r == xcb::KEY_PRESS as u8 {
let key_press: &xcb::KeyPressEvent = unsafe {
// so much for the safe interface
xcb::cast_event(&event)
};

// do stuff
Events of the core protocol and each activated extension are unified in the `xcb::Event` enum.
Therefore, all events can be handled in a single match expression.
Many functions (such as `wait_for_event`) return a `xcb::Result` which allows idiomatic error handling.

} else if r == xkb_first_event {
// resolving extension events like we would in C
// very poor support is provided (not better than C)
}
}
}
}

// 1.0
```rust
fn main() -> xcb::Result<()> {
// ...
loop {
Expand All @@ -206,3 +114,26 @@ fn main() -> xcb::Result<()> {
}
}
```

### Debugging

All types in Rust XCB implement `Debug` in a way that allows recursive debug print.
E.g. iterators will not print a useless pointer value, but will recurse down to each element.

There is in addition the optional `"debug_atom_names"` cargo feature under which each atom
will print its name for easier debugging in some situations.
For example, Xinput provide some information about input devices with atom identifiers.
This allows you to quickly look-up which atoms you need to intern and seek for.
E.g. the feature would turn:
```
Atom {
res_id: 303,
}
```
into
```
Atom("Abs Pressure" ; 303)
```

The feature sets global variable to have access to the connection in the `Debug::fmt` call,
so it should be activated only when needed.
Loading