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:
parent
e0e59f8d9d
commit
0e6fb46b96
10
notes.md
10
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 <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.
|
||||
|
||||
|
||||
|
|
158
src/xrandr.cpp
158
src/xrandr.cpp
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue