Innteresting. It seems that the `output` collection is much more

reliable as a source of truth about which monitors and screens are
actually in use.  Each one comes with its own collection of `crtc`,
but you can just skip the ones that return `null`, giving you a
reliable list of the "active and visible" screens that the user is
currently looking at.  Excellent!  This means that I'm one step closer
to having a viable solution!

I also discovered the [xcb_util](https://xcb.freedesktop.org/XcbUtil/)
library of helpful utilities has a function called
`xcb_aux_get_screen` for getting the root screen because everyone was
weirded out by that list traversal algorithm.

The source code to [xedgewarp](https://github.com/Airblader/xedgewarp)
was invaluable in revealing these secrets to me.

So far this little toy compiles down to only 35KB, and that includes
using `std::cout` and `std::vector`!  I wonder how big the Rust
version will be.  Yeah, yeah, I know, it cheats by having lots of
itself hidden in the kernel.

Next up: Actually knowing what the rotation status is.
This commit is contained in:
Elf M. Sternberg 2022-02-24 15:59:43 -08:00
parent 8a700005c3
commit 2cd9b268a8
12 changed files with 525 additions and 12 deletions

178
.clang-format Normal file
View File

@ -0,0 +1,178 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequires: false
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: true
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...

View File

@ -12,3 +12,6 @@ build/xrandr: build/Makefile src/xrandr.cpp CMakeLists.txt
clean:
rm -fr build
run: build/xrandr
./build/xrandr

View File

@ -19,7 +19,10 @@ obviously X Windows.
In progress.
**DETAILS:**
DETAILS:
**Notes:**
**BUILDING:**

40
docs/Progress.md Normal file
View File

@ -0,0 +1,40 @@
Resources used in the production of this program:
- [The XCB Reference from FreeDesktop](https://xcb.freedesktop.org/manual/index.html)
- [The Xorg Basic Programming with the XCB
Library](https://www.x.org/releases/X11R7.6/doc/libxcb/tutorial/index.html),
- [The KDE Source Code for
XCBWrapper](https://github.com/KDE/libkscreen/tree/master/backends),
- [xedgewarp](https://github.com/Airblader/xedgewarp)
[Vlad Zahorodnii](https://github.com/zzag) et. al., ongoing.
- [C++ in a
Nutshell](https://www.oreilly.com/library/view/c-in-a/059600298X/)
Ray Lischner, O'Reilly Publishing, 2003
The sheer variety of sources I needed to cobble together an
understanding how the XCB library works is an example of what teachers
call [extrinsic
load](https://betterhumans.pub/skyrocket-your-learning-top-3-studying-techniques-based-on-cognitive-load-theory-1641b5e56508),
an unnecessary extra load on learning because the documentation is
poorly organized and lacks instruction. *Basic Programming with XCB*
is a
*[tutorial](https://www.writethedocs.org/videos/eu/2017/the-four-kinds-of-documentation-and-why-you-need-to-understand-what-they-are-daniele-procida/)*
oriented toward commonplace tasks, but peters out before getting to an
XCB extensions such as RandR. The reference is a *reference*; it
doesn't help you understand how to use the library. `XCBWrapper` is
an excellent example of how to use XCB, but it uses some fairly
high-level C++ to accomplish all that it does, and untangling the
relationship between the wrapper template, the macro that does wrapper
declarations, and the XCB Reference; once you find the insight that
XCB's declarations are all derived from a massive XML file, you can
start to understand that XCBWrapper exploits the patterns produced by
the derivative file, but it requires insight and effort that's
unrelated to understanding XCB in the first place.
This project does show the usual trajectory of one of my learning
exercises, especially since I'm fond of delving in places where no man
has documented before. I've dumped a ton of stuff into my brain and
now it's all starting to make sense. I also note that I'm doing
_better_ than a lot of the open-source examples, in that I'm batching
many of my requests before processing them. I'm not batching storing
the replies yet, but I don't see why that couldn't happen.

8
notes.md Normal file
View File

@ -0,0 +1,8 @@
- A Display has Screens
- A Screen has Monitors & Windows
- The root Screen is accessed by traversing to the head of the list of
screens using an interator.
- The root screen's root window is used as the parameter for
'get_screen_resources'.
- The screen_resources object is contains 'screen_resources_crtcs',
which you again get via an iterator. These are cookies.

View File

@ -1,14 +1,154 @@
#include "xcb/randr.h"
#include "xcb/xcb.h"
#include "xcb/xcb_aux.h"
#include <iostream>
#include <xcb/xcb.h>
#include <memory>
#include <vector>
int main(int argc, const char *argv[]) {
xcb_connection_t *xConnection = xcb_connect(nullptr, nullptr);
for (auto iter = xcb_setup_roots_iterator(xcb_get_setup(xConnection));
iter.rem; xcb_screen_next(&iter)) {
xcb_screen_t *screen = iter.data;
std::cout << "Screen " << iter.index << " (" << screen->width_in_pixels
<< ", " << screen->height_in_pixels << ")" << std::endl;
}
xcb_disconnect(xConnection);
return 0;
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;
}
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) << std::endl;
}
void display_crtc_info(xcb_connection_t* connection, std::vector<xcb_randr_crtc_t>& crtc_cs) {
std::vector<xcb_randr_get_crtc_info_cookie_t> cookies;
xcb_generic_error_t* error = nullptr;
for (const auto& crtc : crtc_cs) {
cookies.push_back(xcb_randr_get_crtc_info(connection, crtc, XCB_CURRENT_TIME));
}
for (const auto& cookie : cookies) {
xcb_randr_get_crtc_info_reply_t* reply =
xcb_randr_get_crtc_info_reply(connection, cookie, &error);
if (error) {
free(error);
} else {
display_one_crtc(reply);
free(reply);
}
}
}
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 =
// (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;
}

BIN
studies/xcb_good_usage Executable file

Binary file not shown.

54
studies/xcb_good_usage.cc Normal file
View File

@ -0,0 +1,54 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <xcb/xcb.h>
double get_time(void) {
struct timeval timev;
gettimeofday(&timev, NULL);
return (double)timev.tv_sec + (((double)timev.tv_usec) / 1000000);
}
int main() {
int count = 500;
xcb_atom_t* atoms = (xcb_atom_t*)malloc(count * sizeof(atoms));
xcb_intern_atom_cookie_t* cookies =
(xcb_intern_atom_cookie_t*)malloc(count * sizeof(xcb_intern_atom_cookie_t));
char** names;
double end, diff_x;
/* init names */
names = (char**)malloc(count * sizeof(char*));
for (int i = 0; i < count; ++i) {
char buf[100];
sprintf(buf, "NAME%d", i);
names[i] = strdup(buf);
}
auto start = get_time();
xcb_connection_t* connection = xcb_connect(NULL, NULL);
for (int i = 0; i < count; ++i) {
cookies[i] = xcb_intern_atom(connection, 0, strlen(names[i]), names[i]);
}
for (int i = 0; i < count; i++) {
xcb_intern_atom_reply_t* r = xcb_intern_atom_reply(connection, cookies[i], 0);
if (r) {
atoms[i] = r->atom;
free(r);
}
}
xcb_disconnect(connection);
end = get_time();
diff_x = end - start;
printf("XCB (good) use time : %f\n", diff_x);
for (int i = 0; i < count; ++i)
free(names[i]);
free(names);
free(atoms);
free(cookies);
return 0;
}

BIN
studies/xcb_poor_usage Executable file

Binary file not shown.

44
studies/xcb_poor_usage.cc Normal file
View File

@ -0,0 +1,44 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <xcb/xcb.h>
double get_time(void) {
struct timeval timev;
gettimeofday(&timev, NULL);
return (double)timev.tv_sec + (((double)timev.tv_usec) / 1000000);
}
int main() {
int count = 500;
xcb_atom_t* atoms = (xcb_atom_t*)malloc(count * sizeof(atoms));
char** names;
double end, diff_x;
/* init names */
names = (char**)malloc(count * sizeof(char*));
for (int i = 0; i < count; ++i) {
char buf[100];
sprintf(buf, "NAME%d", i);
names[i] = strdup(buf);
}
auto start = get_time();
xcb_connection_t* connection = xcb_connect(NULL, NULL);
for (int i = 0; i < count; ++i) {
atoms[i] = xcb_intern_atom_reply(
connection, xcb_intern_atom(connection, 0, strlen(names[i]), names[i]), NULL)
->atom;
}
xcb_disconnect(connection);
end = get_time();
diff_x = end - start;
printf("XCB (poor) use time : %f\n", diff_x);
for (int i = 0; i < count; ++i)
free(names[i]);
free(names);
return 0;
}

BIN
studies/xlib_demo Executable file

Binary file not shown.

43
studies/xlib_demo.cc Normal file
View File

@ -0,0 +1,43 @@
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
double get_time(void) {
struct timeval timev;
gettimeofday(&timev, NULL);
return (double)timev.tv_sec + (((double)timev.tv_usec) / 1000000);
}
int main() {
int count = 500;
Atom* atoms_x = (Atom*)malloc(count * sizeof(atoms_x));
char** names;
double end, diff_x;
/* init names */
names = (char**)malloc(count * sizeof(char*));
for (int i = 0; i < count; ++i) {
char buf[100];
sprintf(buf, "NAME%d", i);
names[i] = strdup(buf);
}
auto start = get_time();
auto disp = XOpenDisplay(getenv("DISPLAY"));
for (int i = 0; i < count; ++i)
atoms_x[i] = XInternAtom(disp, names[i], 0);
end = get_time();
XCloseDisplay(disp);
diff_x = end - start;
printf("Xlib use time : %f\n", diff_x);
free(atoms_x);
for (int i = 0; i < count; ++i)
free(names[i]);
free(names);
return 0;
}