Moving the 'learning xcb reading' into its own folder.

This matches the learning process that I describe in rigged_regex.  Step by
step, recording everything multiple times and in multiple ways, so that all the
learning that I care about is preserved.  There's XCB in here, and some C++
(mostly around `unique_ptr`).
This commit is contained in:
Elf M. Sternberg 2022-04-15 14:41:03 -07:00
parent e0e59f8d9d
commit 0e6fb46b96
6 changed files with 257 additions and 158 deletions

View File

@ -9,3 +9,13 @@
CXX Opts looks like a really useful version of GetOpts that will do what you want:
https://github.com/jarro2783/cxxopts
# Git
- `git submodule add <url>` is how you add a submodule to your project. It
creates a new folder for the submodule and it creates an entry in the
`$(git-root)/.gitmodule` file that explains where the submodule should go and
how to reference it.

View File

@ -1,158 +0,0 @@
#include "xcb/randr.h"
#include "xcb/xcb.h"
#include "xcb/xcb_aux.h"
#include <iostream>
#include <memory>
#include <vector>
#include <xcb>
xcb_randr_get_screen_resources_reply_t* get_screen_resources(xcb_connection_t* connection,
xcb_window_t rootWindow) {
return xcb_randr_get_screen_resources_reply(
connection, xcb_randr_get_screen_resources(connection, rootWindow), nullptr);
}
std::vector<xcb_randr_crtc_t> get_crtc_cookies(xcb_randr_get_screen_resources_reply_t* resources) {
std::vector<xcb_randr_crtc_t> reply;
xcb_randr_crtc_t* crtcs = xcb_randr_get_screen_resources_crtcs(resources);
const int crtcsCount = xcb_randr_get_screen_resources_crtcs_length(resources);
for (int i = 0; i < crtcsCount; ++i) {
reply.push_back(crtcs[i]);
}
return reply;
}
const char* rotation_map(uint16_t rotation) {
switch (rotation) {
case XCB_RANDR_ROTATION_ROTATE_0:
return "normal";
case XCB_RANDR_ROTATION_ROTATE_90:
return "portrait";
case XCB_RANDR_ROTATION_ROTATE_180:
return "inverted";
case XCB_RANDR_ROTATION_ROTATE_270:
return "portrait inverted";
default:
return "unknown";
}
}
void display_one_crtc(xcb_randr_get_crtc_info_reply_t* crtc) {
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;
}
void display_one_output(xcb_connection_t* connection, xcb_randr_get_output_info_reply_t* output,
xcb_timestamp_t timestamp) {
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);
if (!crtc) {
return;
}
display_one_crtc(crtc);
free(crtc);
}
void display_outputs(xcb_connection_t* connection,
xcb_randr_get_screen_resources_reply_t* resources, xcb_timestamp_t timestamp) {
std::vector<xcb_randr_get_output_info_cookie_t> cookies;
xcb_generic_error_t* error = nullptr;
int len = xcb_randr_get_screen_resources_outputs_length(resources);
xcb_randr_output_t* randr_outputs = xcb_randr_get_screen_resources_outputs(resources);
for (int i = 0; i < len; ++i) {
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;
} else {
display_one_output(connection, reply, timestamp);
free(reply);
}
}
}
// Lesson of the day: All _reply_t* objects must be freed.
//
// xcb_randr_rotation_t)
// }
// get_rotation(xcb_connection_t* connection, xcb_randr_crtc_t crtc) {
// xcb_generic_error_t* error = nullptr;
// xcb_randr_get_crtc_info_cookie_t cookie =
// xcb_randr_get_crtc_info(connection, crtc, XCB_CURRENT_TIME);
// xcb_randr_get_crtc_info_reply_t* reply =
// xcb_randr_get_crtc_info_reply(connection, cookie, &error);
// xcb_randr_rotation_t rotation =
// bool MyClass::operator!=(const MyClass &other) const {
return !(*this == other);
}
(xcb_randr_rotation_t) reply->rotation;
free(reply);
return rotation;
// }
//
xcb_randr_query_version_reply_t* getVersion(xcb_connection_t* connection) {
xcb_generic_error_t* error = nullptr;
xcb_randr_query_version_reply_t* version = xcb_randr_query_version_reply(
connection,
xcb_randr_query_version(connection, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION),
&error);
if (error) {
std::cout << "Error code:" << error->error_code << std::endl;
free(error);
return NULL;
}
if (!version) {
return NULL;
}
return version;
}
void display_screen_info(int screen_id, xcb_screen_t* screen) {
std::cout << "Screen " << screen_id << " (" << screen->width_in_pixels << ", "
<< screen->height_in_pixels << ")" << std::endl;
}
void display_randr_version(xcb_randr_query_version_reply_t* version) {
if (!version) {
std::cout << "Something went wrong retrieving the version number" << std::endl;
exit(0);
}
std::cout << "Server reports RandR version (" << version->major_version << "."
<< version->minor_version << ")" << std::endl;
}
// This gives us a connection and a screen object.
int main(int argc, const char* argv[]) {
int default_screen_id;
xcb_connection_t* xConnection = xcb_connect(nullptr, &default_screen_id);
auto screen = xcb_aux_get_screen(xConnection, default_screen_id);
display_screen_info(default_screen_id, screen);
xcb_randr_query_version_reply_t* version = getVersion(xConnection);
display_randr_version(version);
xcb_randr_get_screen_resources_reply_t* screen_resources =
get_screen_resources(xConnection, screen->root);
xcb_timestamp_t timestamp = screen_resources->config_timestamp;
display_outputs(xConnection, screen_resources, timestamp);
free(screen_resources);
xcb_disconnect(xConnection);
return 0;
}

17
xcb_read/Makefile Normal file
View File

@ -0,0 +1,17 @@
all: build/xrandr
.PHONY: all
build:
mkdir -p build
build/Makefile: build CMakeLists.txt
cd build && cmake ..
build/xrandr: build/Makefile src/xrandr.cpp CMakeLists.txt
cd build && make
clean:
rm -fr build
run: build/xrandr
./build/xrandr

View File

@ -0,0 +1,44 @@
# Understanding XRandR
XRandR (X Rotate and Resize) controls the visible aspects of what is shown on a
screen, but to do so it has a rather maddening collection of inter-related
components that help it make sense of the X Server's responsbilities.
The purpose of the X Windows System is to display _windows_, rectangular regions
on a human-visible display. In much the same way that every process on a
computer is nested in a hierarchy of parents and children all the way up to the
root process, in X every window is nested in a hierachy of parent and child
windows and atomic components all the way up to the `root` window. The root
window, in turn, belongs to a `screen`, which is the total addressable
collection of pixels for which an X11 instance is responsible.
That screen and its window are typically singular-- the screen you see before
you. But X11 is infinitely malleable. It is possible for X11 to have multiple
screens, or to have a single screen that spans multiple monitors. And it is
possible for X11 to drive those multiple monitors from one graphics card with
multiple outputs, or from multiple graphics cards installed on the same
computer.
The following objects need to be understood:
- Server: The server that you'll be communicating with.
- Connection: The connection your client has with that server.
- Screen: A virtualized collection of pixels that the server is responsible for
- CRCT: Cathode Ray Tube Controller, what we now call a 'graphics card', that
the server uses to show a portion (possibly a portion as large as 100%) of the
screen on a monitor.
- Output: The physical connection between a CRTC and a monitor. Depending on the
XRandR protocol and the modernity of the monitor, XRandR can probably extract
information about the monitor from that monitor.
And the following facts:
- A screen has both CRTCs and Outputs.
- Although a CRTC can have an array of Outputs, at the moment each CRTC supports
only one at a time. If a graphics card has multiple (phyisical) connectors,
each connector has its own CRTC. As [How Video Cards
Work](https://www.x.org/wiki/Development/Documentation/HowVideoCardsWork/)... [TK]
- An output can have multiple CRTCs... but usually has only one.

186
xcb_read/src/xrandr.cpp Normal file
View File

@ -0,0 +1,186 @@
#include "xcb/randr.h"
#include "xcb/xcb.h"
#include "xcb/xcb_aux.h"
#include <iostream>
#include <memory>
#include <vector>
const char* rotation_map(uint16_t rotation) {
switch (rotation) {
case XCB_RANDR_ROTATION_ROTATE_0:
return "normal";
case XCB_RANDR_ROTATION_ROTATE_90:
return "portrait";
case XCB_RANDR_ROTATION_ROTATE_180:
return "inverted";
case XCB_RANDR_ROTATION_ROTATE_270:
return "portrait inverted";
default:
return "unknown";
}
}
void display_one_crtc(xcb_randr_get_crtc_info_reply_t* crtc) {
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;
}
// Given the output, get the corresponding CRTCs. This is much more immediate-mode,
// so there's an instant free at the end.
void display_one_output(xcb_connection_t* connection, xcb_randr_get_output_info_reply_t* output,
xcb_timestamp_t timestamp) {
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);
if (!crtc) {
return;
}
display_one_crtc(crtc);
free(crtc);
}
void display_outputs(xcb_connection_t* connection,
std::unique_ptr<xcb_randr_get_screen_resources_reply_t> resources,
xcb_timestamp_t timestamp) {
std::vector<xcb_randr_get_output_info_cookie_t> cookies;
xcb_generic_error_t* error = nullptr;
// 10. With the resources, we use the macro to get the count of resources,
// and then we get the list of output IDs. That's all we have from the
// resources object. It's also important to realize that these are both
// macros; we're just getting a pointer into the array.
int len = xcb_randr_get_screen_resources_outputs_length(resources.get());
xcb_randr_output_t* randr_outputs = xcb_randr_get_screen_resources_outputs(resources.get());
// And here, we're getting these unique cookies from the server, which are
// just uint32 objects and easily copyable. The server will be retrieving
// those value immediately.
for (int i = 0; i < len; ++i) {
cookies.push_back(xcb_randr_get_output_info(connection, randr_outputs[i], timestamp));
}
// Finally, we retrieve the reply objects, which _are_ allocated on the client to
// hold that data. Once we have them, we need to free them when we're done.
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;
} else {
display_one_output(connection, reply, timestamp);
free(reply);
}
}
}
// 8. Here, we immediately do make a request for the resource we want. This
// means that what we return will need to be freed manually by the end of its
// lifespan.
xcb_randr_get_screen_resources_reply_t* get_screen_resources(xcb_connection_t* connection,
xcb_window_t rootWindow) {
return xcb_randr_get_screen_resources_reply(
connection, xcb_randr_get_screen_resources(connection, rootWindow), nullptr);
}
// 6. Simply display the object. Note that C++ does not, to the best of my
// knowledge, have a proper flow analysis, so there's no way to guarantee that
// the version object that was passed is non-null.
void display_randr_version(xcb_randr_query_version_reply_t* version) {
if (!version) {
std::cout << "Something went wrong retrieving the version number" << std::endl;
exit(0);
}
std::cout << "Server reports RandR version (" << version->major_version << "."
<< version->minor_version << ")" << std::endl;
}
// 5. Retrieve the version of XRandR in use and display it. We don't do very
// much with it, but it's worth exercise. More importantly, this illustrates an
// important issue: every XCB type that ends with `*_reply_t` is retrieved from
// the server, and the space to hold that information is allocated by the
// retrieval, and so it has to be freed when you're done with it. And if the
// call fails, the `error` object returned has to be freed.
void display_version(xcb_connection_t* connection) {
xcb_generic_error_t* error = nullptr;
xcb_randr_query_version_reply_t* version = xcb_randr_query_version_reply(
connection,
xcb_randr_query_version(connection, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION),
&error);
if (error) {
std::cout << "Error code:" << error->error_code << std::endl;
free(error);
return;
}
if (!version) {
return;
}
display_randr_version(version);
free(version);
}
// 3. The `xcb_screen` object extracted from `xcb_connection` contains raw
// information about the screen. The screen is not *necessarily* the whole of a
// physical object. The relationship between screens, CRTS, outputs, and windows
// is fraught with bookkeeping. Again, consult the Understandxng XRandR
// document.
void display_screen_info(int screen_id, xcb_screen_t* screen) {
std::cout << "Screen " << screen_id << " (" << screen->width_in_pixels << ", "
<< screen->height_in_pixels << ")" << std::endl;
}
int main(int argc, const char* argv[]) {
int default_screen_id;
// 1. xcb_connect is provided by the xcb library. It takes a pointer to
// [TK], and an optional reference to an int to be filled in. In returns an
// opaque (and in memory, rather large) data structure that indicates the
// live connection, and it fills in the reference address with the default
// screen ID.
// See [Understanding XRandR](../Understanding_Xrandr.md) for the difference
// between screens, outputs, devices, and windows.
xcb_connection_t* xConnection = xcb_connect(nullptr, &default_screen_id);
// 2. Inside the `xcb_connection` structure is a linked list of screens, and
// the default screen is the item with the index of `default_screen_id` in
// that linked list. The algorithm for traversing that list looks a bit
// involved and peculiar, and is repeated so often during XCB development,
// that the `XCB Aux` extension provides it as a function.
auto screen = xcb_aux_get_screen(xConnection, default_screen_id);
display_screen_info(default_screen_id, screen);
// 4. Different version of XRandR suppot different capabilities.
display_version(xConnection);
// 7. We need to fetch the screen's resources, which is all about the CTRC's
// and outputs, and because it's a _reply_t, we're going to experiment with
// the `unique_ptr` feature. I find it fascinating the the pointer object is
// a _constructor_ and it gets the pointer by construction, rather than
// assignment.
std::unique_ptr<xcb_randr_get_screen_resources_reply_t> screen_resources(
get_screen_resources(xConnection, screen->root));
xcb_timestamp_t timestamp = screen_resources->config_timestamp;
// 9. Now we send the resulting object to this function, which will iterate
// through the resources we've specified.
display_outputs(xConnection, std::move(screen_resources), timestamp);
xcb_disconnect(xConnection);
return 0;
}