#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); // 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 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; }