From 0e6fb46b96c252e7a6e14eeebf9b5abc2c455550 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Fri, 15 Apr 2022 14:41:03 -0700 Subject: [PATCH] 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`). --- notes.md | 10 ++ src/xrandr.cpp | 158 ------------------ CMakeLists.txt => xcb_read/CMakeLists.txt | 0 xcb_read/Makefile | 17 ++ xcb_read/Understanding_XRandr.md | 44 +++++ xcb_read/src/xrandr.cpp | 186 ++++++++++++++++++++++ 6 files changed, 257 insertions(+), 158 deletions(-) delete mode 100644 src/xrandr.cpp rename CMakeLists.txt => xcb_read/CMakeLists.txt (100%) create mode 100644 xcb_read/Makefile create mode 100644 xcb_read/Understanding_XRandr.md create mode 100644 xcb_read/src/xrandr.cpp diff --git a/notes.md b/notes.md index 4b7490e..2d32c51 100644 --- a/notes.md +++ b/notes.md @@ -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 ` 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. + + diff --git a/src/xrandr.cpp b/src/xrandr.cpp deleted file mode 100644 index 0bde342..0000000 --- a/src/xrandr.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "xcb/randr.h" -#include "xcb/xcb.h" -#include "xcb/xcb_aux.h" -#include -#include -#include -#include - -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 get_crtc_cookies(xcb_randr_get_screen_resources_reply_t* resources) { - std::vector 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 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; -} diff --git a/CMakeLists.txt b/xcb_read/CMakeLists.txt similarity index 100% rename from CMakeLists.txt rename to xcb_read/CMakeLists.txt diff --git a/xcb_read/Makefile b/xcb_read/Makefile new file mode 100644 index 0000000..e114f3f --- /dev/null +++ b/xcb_read/Makefile @@ -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 diff --git a/xcb_read/Understanding_XRandr.md b/xcb_read/Understanding_XRandr.md new file mode 100644 index 0000000..0d2f0d2 --- /dev/null +++ b/xcb_read/Understanding_XRandr.md @@ -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. + + + diff --git a/xcb_read/src/xrandr.cpp b/xcb_read/src/xrandr.cpp new file mode 100644 index 0000000..e28d9de --- /dev/null +++ b/xcb_read/src/xrandr.cpp @@ -0,0 +1,186 @@ +#include "xcb/randr.h" +#include "xcb/xcb.h" +#include "xcb/xcb_aux.h" +#include +#include +#include + +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 resources, + xcb_timestamp_t timestamp) { + std::vector 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 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; +}