xcb-studies/xcb_read/src/xrandr.cpp

198 lines
7.8 KiB
C++

#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);
// Note: This is part of the output structure natively; it's just not
// automagically exposed. (Opaque structures suck.) But it's both accessible
// and null-terminated, so it's relatively safe to use this way, and it does
// not require free() at the end.
auto name = xcb_randr_get_output_info_name(output);
if (name) {
std::cout << "DISPLAY: " << name << std::endl;
}
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;
}