Added mucho documentation.
This commit is contained in:
parent
03f0fc82b3
commit
9c7c7b9914
|
@ -4,24 +4,90 @@ XCB is a library for communicating with the X-Windows system used on
|
|||
Linux, FreeBSD, and other Unix-like operating systems. XCB's interface
|
||||
is written in C.
|
||||
|
||||
- Connecting, Verifying Connection, and Disconnecting the server.
|
||||
-
|
||||
## Understanding X, at least the parts I'm dealing with.
|
||||
|
||||
The X Windows System (hereafter, **X**) is a server. It listens for
|
||||
events (keyboard events, mouse events, timer events from connected
|
||||
programs, etc.) and "stores" the results on a *display*. The
|
||||
`display` is the root the of the **X** hierarchy, and the object to
|
||||
which you initially connect. The `display` is described by a string
|
||||
which encapsulates the address of the **X** server. The format of the
|
||||
string predates the URL standard.
|
||||
|
||||
Part of the problem with untangling the hierarchy in X-Windows is that
|
||||
it's meant to be extremely malleable and re-usable. [The Wikipedia
|
||||
article](https://en.wikipedia.org/wiki/X_Window_System_protocols_and_architecture)
|
||||
says that the `display` has a top-level window; The [Xlib
|
||||
Tutorial](https://tronche.com/gui/x/xlib/introduction/overview.html)
|
||||
says a `screen` is a physical monitor, and a workstation can have more
|
||||
than one screen, but each screen has its own top-level window. But
|
||||
both of these statements are inaccurate!
|
||||
|
||||
A top-level window can span multiple screens, and each screen may or
|
||||
may not have an monitor at all. Headless **X** systems used for
|
||||
testing may have an `output`, which is a managed region of memory into
|
||||
which **X** is "drawing", but may have no physical device at all!
|
||||
|
||||
For the purposes of modern **X**, the server manages one or more (in
|
||||
commonplace practice, only one) logical `screen` objects, which has
|
||||
multiple `output` and `crtc` objects. An `output` is the video output
|
||||
manager on your device, such as your GPU, but it may just be a
|
||||
virtualized chunk of memory for the headless scenario described above.
|
||||
The **X** server is responsible for figuring out at start time what
|
||||
`output` objects you have and what `crtc` objects are connected to
|
||||
them, assigning `crtc` devices to each `output`, and choosing a
|
||||
default set-up that will support running your
|
||||
instance of GTK or KDE or whatever.
|
||||
|
||||
<aside>You kids have it easy. Before the existence of modern,
|
||||
self-describing hardware, we had to hand-enter every single one of
|
||||
these details, and if you got a detail wrong it was possible to burn
|
||||
out your video card or monitor!</aside>
|
||||
|
||||
For example, in a two-monitor set up X will have a single `screen`
|
||||
with a single root `window` spanning both monitors, but there would be
|
||||
two `output` devices, each with its own `crtc`, mapping a client
|
||||
program's output to the physical pixels on the screen. This is how
|
||||
it's possible to drag an application window from one monitor to
|
||||
another, and have it be visible as it crosses the bezels between them.
|
||||
The `output` and `ctrc` together act as the framebuffer manager for
|
||||
displays.
|
||||
|
||||
My goal is to enable autorotation on tablets running **X**, using the
|
||||
XCB interface. For the purposes of that fairly straightforward goal,
|
||||
I want to find the base `screen`, assert that it has a single `output`
|
||||
and a single `crtc`, and then send a command to the `crtc` object to
|
||||
rotate its contents to the orientation I desire. The change to the
|
||||
`crtc` will cause the `output` object to remap all of the pixels it is
|
||||
currently tracking to the new orientation. Most modern window
|
||||
managers are pretty good about re-arranging the screen to manage this
|
||||
change!
|
||||
|
||||
<aside>It makes no sense for a virtualized `output` device, one which
|
||||
has no actual display visible to the human eye, to have a `crtc`.
|
||||
It's orientation doesn't matter. If it ever _is_ displayed to a human
|
||||
being it will be in a virtualizing environment such as
|
||||
[Xephyr](https://en.wikipedia.org/wiki/Xephyr), in which case it will
|
||||
be getting its `crtc` information from the host **X** display.</aside>
|
||||
|
||||
<aside>I'm still not sure how all this interacts with a window manager
|
||||
that has 'workspaces', such as Gnome-Mate. Which means I could be
|
||||
entirely wrong about this whole thing! On the other hand, it could be
|
||||
as simple as every workspace having it's own pseudo-root-window, and
|
||||
being dependent upon the `crtc` object for screen dimensions and pixel
|
||||
mapping. [I have to read this
|
||||
carefully](https://jichu4n.com/posts/how-x-window-managers-work-and-how-to-write-one-part-i/).</aside>
|
||||
|
||||
TODO: I haven't yet figured out the bit about remapping the tablet's
|
||||
touchscreen inputs, so that when you place your finger or stylus on
|
||||
the screen **X** maps the pointer location to the right place.
|
||||
|
||||
## Connecting.
|
||||
|
||||
X-Windows is a server. It listens for events (keyboard events, mouse
|
||||
events, timer events from connected programs, etc.) and "stores" the
|
||||
results on a *display*, which is intended to be seen with the human
|
||||
eye. A display is made up of one or more *screens*. Screens can be
|
||||
literal (one of the physical devices in a multi-monitor setup) or
|
||||
virtual (a virtualized desktop where the window manager supports
|
||||
different "pages" on the same monitor), or even just parts of the same
|
||||
physical screen space broken up by some logic.
|
||||
|
||||
To connect to X via XCB, you use the `xcb_connect` function. It takes two
|
||||
arguments, a string with the name of the display, and a
|
||||
pointer-to-int to the preferred screen. It returns an opaque data
|
||||
structure, 'xcb_connection_t'.
|
||||
To connect to **X** via XCB, you use the `xcb_connect` function. It
|
||||
takes two arguments, a string with the name of the display, and a
|
||||
pointer-to-int to the default screen's ID, which is a returned value.
|
||||
It returns an opaque data structure, 'xcb_connection_t'.
|
||||
|
||||
```
|
||||
xcb_connection_t* xcbConnection = xcb_connect(const char* display, int* screen);
|
||||
|
@ -50,4 +116,129 @@ free the memory XCB used to report the connection failure:
|
|||
void xcb_disconnect(xcb_connection_t* xcbConnection);
|
||||
```
|
||||
|
||||
## Getting the default screen structure
|
||||
|
||||
Once you've connected, you need the default `screen` structure.
|
||||
Oddly, it's at the bottom of a linked list of `screen` structures that
|
||||
you have to find by counting down from the default screen ID retrieved
|
||||
during the connection, using XCB's supplied traversal functions.
|
||||
|
||||
Doing this is so commonplace that there's a function for doing it
|
||||
provided by the `xcb-aux` extension, which on Ubuntu is accessed
|
||||
through `libxcb-util-dev`:
|
||||
|
||||
```
|
||||
xcb_screen_t* screen = xcb_aux_get_screen(xConnection, default_screen_id);
|
||||
```
|
||||
|
||||
The screen structure is not opaque; it contains the root window, as
|
||||
well as the width and height of the total display (covering all
|
||||
monitors!) that it is expected to manage, along with other details
|
||||
that, well, aren't relevant (at least, not yet, and maybe I hope not).
|
||||
|
||||
## Getting the screen's resources
|
||||
|
||||
Now we're getting into RandR's portion of the business. RandR is an
|
||||
extension to **X** that allows userspace programs to manipulate the
|
||||
core functionality of outputs and displays. Those are the resources
|
||||
the **X** screen has to draw on. This is also the first time we're
|
||||
going to encounter the "standard" XCB interface.
|
||||
|
||||
XCB has an idiom of sending a request to the **X** server and storing
|
||||
a token (a `uint32_t`) that it calls a *cookie*. When it wants to
|
||||
review the reply to that request, it asks for the reply using the
|
||||
cookie.
|
||||
|
||||
It's possible to send many requests, both commands-to-set and
|
||||
requests-for-information, to the **X** server, and then retrieve the
|
||||
replies all at once. If the XCB interface has already received the
|
||||
replies, it can hand them over at once; otherwise, it'll wait for
|
||||
one. In this way, XCB and **X** can work asynchronously, batching
|
||||
transactions and reducing latency.
|
||||
|
||||
For our purposes, we're not going to do that. We're just going to ask
|
||||
for one object. The `get` command uses the rootWindow, which as I
|
||||
mentioned earlier is on the `screen` we retrieved as `screen->root`.
|
||||
|
||||
```
|
||||
xcb_generic_error_t* error = nullptr;
|
||||
|
||||
xrandr_get_screen_resources_cookie_t screen_resources_cookie =
|
||||
xcb_randr_get_screen_resources(connection, screen->root);
|
||||
|
||||
xrandr_get_screen_resources_reply_t* screen_resources =
|
||||
xcb_xrandr_get_screen_resources_reply(connection, screen_resources_cookie, &error);
|
||||
```
|
||||
|
||||
**IMPORTANT** `reply` objects are allocated by XCB. *You* are
|
||||
responsible for `free()`ing them afterward. If there is an error, the
|
||||
reply object will be `null`, but the error object will contain the
|
||||
error response as an allocated object and you are responsible for
|
||||
`free()`ing it. Only `reply` and `error` objects are allocated; all
|
||||
the rest are part of the `connection` object and will be freed on
|
||||
disconnect.
|
||||
|
||||
## Getting the outputs and crtcs
|
||||
|
||||
Now that we have a screen, we want to get all the `output` devices, find
|
||||
the `crtc` associated with it, and see the rotation! As I've been
|
||||
using C++, I'm going to store a collection of cookies in vector.
|
||||
|
||||
There are two idioms in this example; the first collects all the query
|
||||
cookies and then iterates through the replies; the second gets the
|
||||
query cookie and immediately requests the reply. While the second
|
||||
idiom is "slower" by an order of magnitude, on my laptop with a
|
||||
shared-memory connection the difference is 3000 nanoseconds vs 300
|
||||
nanoseconds-- not enough for most people to notice.
|
||||
|
||||
Notice that I do *not* free the return from the
|
||||
`xcb_randr_get_screen_resources_outputs` call; that is simply
|
||||
interpreting the contents of the `screen_resources` object. As
|
||||
before, the `screen_resources` object itself will have to be freed
|
||||
eventually.
|
||||
|
||||
```
|
||||
std::vector<xcb_randr_get_output_info_cookie_t> output_get_cookies;
|
||||
std::vector<xcb_randr_get_output_info_cookie_t> output_crtc_ids;
|
||||
|
||||
xcb_generic_error_t* error = nullptr;
|
||||
|
||||
int len = xcb_randr_get_screen_resources_outputs_length(screen_resources);
|
||||
xcb_randr_output_t* randr_outputs =
|
||||
xcb_randr_get_screen_resources_outputs(screen_resources);
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
output_get_cookies.push_back(
|
||||
xcb_randr_get_output_info(connection, randr_outputs[i], timestamp));
|
||||
}
|
||||
|
||||
for (const auto& cookie : cookies) {
|
||||
xcb_randr_get_output_info_reply_t* reply =
|
||||
xcb_randr_get_output_info_reply(connection, cookie, &error);
|
||||
if (error) {
|
||||
free(error);
|
||||
continue;
|
||||
}
|
||||
|
||||
xcb_randr_get_crtc_info_reply_t* crtc = xcb_randr_get_crtc_info_reply(
|
||||
connection,
|
||||
xcb_randr_get_crtc_info(connection, output->crtc, timestamp), NULL);
|
||||
|
||||
// It's possible that there is no CRTC associated with the
|
||||
token. This isn't an error.
|
||||
|
||||
if (!crtc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout << "(x: " << crtc->x << ", y: " << crtc->y << ") (width: " << crtc->width
|
||||
<< ", height: " << crtc->height << ") status:" << unsigned(crtc->status)
|
||||
<< " rotation: " << rotation_map(crtc->rotation) << std::endl;
|
||||
|
||||
free(crtc);
|
||||
free(reply);
|
||||
}
|
||||
```
|
||||
|
||||
... this is as far as I've gotten. And it's all starting to make
|
||||
sense, but wow, what a journey just to get this far.
|
||||
|
|
Loading…
Reference in New Issue