Last active
January 24, 2022 23:23
-
-
Save christian-eriksson/899c8a8d4a7800cd980972efbeba817d to your computer and use it in GitHub Desktop.
Will find a window with the WM_CLASS (eg. Firefox) provided as an argument when starting the program. We detect when a window of the provided WM_CLASS is shown by investigating the tree of the shown window. A short pause is used to allow for the tree creation to be completed before fetching the tree.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <X11/Xlib.h> | |
#include <X11/Xutil.h> | |
#include <assert.h> | |
#include <stdio.h> | |
#include <string> | |
#include <vector> | |
#include <iostream> | |
using namespace std; | |
#define NIL (0) | |
struct win_tree_node | |
{ | |
Window window; | |
Window parent; | |
Window *children; | |
unsigned int nchildren; | |
}; | |
/** | |
* Warn that the window does not exist but allow for continued execution. We | |
* pretend that we know what we are doing. | |
*/ | |
int bad_window_handler(Display *disp, XErrorEvent *err) | |
{ | |
char bad_id[20]; | |
char const *const window_id_format = "0x%lx"; | |
snprintf(bad_id, sizeof(bad_id), window_id_format, err->resourceid); | |
cout << "WARNING: There is no window with ID(" << bad_id << ") any more." << endl; | |
return 0; | |
} | |
/** | |
* Collect the class hint from the window candidate. The class hint doesn't have | |
* to be set (I think) so make sure it is for the window you are looking for. | |
*/ | |
string getClass(Display *dsp, Window candidate) | |
{ | |
XClassHint class_hint; | |
if (XGetClassHint(dsp, candidate, &class_hint)) | |
{ | |
if (class_hint.res_name) | |
{ | |
XFree(class_hint.res_name); | |
} | |
if (class_hint.res_class) | |
{ | |
string wm_class = string(class_hint.res_class); | |
XFree(class_hint.res_class); | |
return wm_class; | |
} | |
} | |
return ""; | |
} | |
bool hasCorrectClass(Display *dsp, Window candidate, string wm_class) | |
{ | |
return getClass(dsp, candidate) == wm_class; | |
} | |
/** | |
* Will get all child nodes currently connected to a node. | |
*/ | |
vector<win_tree_node> getChildNodes(Display *dsp, win_tree_node node) | |
{ | |
vector<win_tree_node> children; | |
Window dsp_root; | |
children.reserve(16); | |
for (int i = 0; i < node.nchildren; i++) | |
{ | |
win_tree_node child; | |
child.window = node.children[i]; | |
if (!XQueryTree(dsp, child.window, &dsp_root, &child.parent, | |
&child.children, &child.nchildren)) | |
{ | |
cout << "XQueryTree didn't work, could not get child :(" << endl; | |
break; | |
} | |
children.push_back(child); | |
if (child.nchildren > 0) | |
{ | |
vector<win_tree_node> grand_children = getChildNodes(dsp, child); | |
children.insert(children.end(), grand_children.begin(), grand_children.end()); | |
} | |
} | |
return children; | |
} | |
/** | |
* returns the full tree (up to but not including the default root node) for a | |
* window. | |
*/ | |
vector<win_tree_node> getTree(Display *dsp, Window candidate) | |
{ | |
vector<win_tree_node> win_tree; | |
win_tree.reserve(16); | |
Window current_win = candidate; | |
Window dsp_root; | |
win_tree_node tree_root_node; | |
do | |
{ | |
tree_root_node.window = current_win; | |
if (!XQueryTree(dsp, tree_root_node.window, &dsp_root, &tree_root_node.parent, | |
&tree_root_node.children, &tree_root_node.nchildren)) | |
{ | |
cout << "XQueryTree didn't work :(" << endl; | |
break; | |
} | |
current_win = tree_root_node.parent; | |
} while (dsp_root != current_win); | |
win_tree.push_back(tree_root_node); | |
vector<win_tree_node> children = getChildNodes(dsp, tree_root_node); | |
win_tree.insert(win_tree.end(), children.begin(), children.end()); | |
return win_tree; | |
} | |
void printTree(vector<win_tree_node> win_tree) | |
{ | |
string indent = "\t"; | |
for (int i = 0; i < win_tree.size(); i++) | |
{ | |
if (i > 0) | |
{ | |
if (win_tree[i].parent == win_tree[i - 1].window) | |
{ | |
indent += "\t"; | |
} | |
else | |
{ | |
int j = 1; | |
while (win_tree[i].parent != win_tree[i - (++j)].window) | |
{ | |
indent.erase(indent.end() - 1, indent.end()); | |
} | |
} | |
} | |
cout << i + 1 << indent << "window: 0x" << hex << win_tree[i].window | |
<< " parent: 0x" << win_tree[i].parent << " #children: " | |
<< dec << win_tree[i].nchildren << endl; | |
} | |
} | |
int pause(int milisec) | |
{ | |
struct timespec req = {0}; | |
req.tv_sec = 0; | |
req.tv_nsec = milisec * 1000000L; | |
return nanosleep(&req, (struct timespec *)NULL); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
if (argc < 2) | |
{ | |
cerr << "Usage: " << argv[0] << "<WM_CLASS>" << endl; | |
return 1; | |
} | |
string wm_class = argv[1]; | |
// Open connection to X server | |
Display *dsp = XOpenDisplay(NIL); | |
assert(dsp); | |
// Start listening to root window, we handle potential errors | |
// with custom error handler. | |
XSelectInput(dsp, DefaultRootWindow(dsp), SubstructureNotifyMask); | |
XSetErrorHandler(bad_window_handler); | |
for (;;) | |
{ | |
XEvent e; | |
XNextEvent(dsp, &e); | |
if (e.type == MapNotify) | |
{ | |
XMapEvent map_event = e.xmap; | |
Window candidate = map_event.window; | |
// it seems that a window's tree stucture is not necessarily completed | |
// when it's mapped. We therefor pause execution in order to be reasonably | |
// sure the the structure is finished before collecting it. | |
pause(500); | |
vector<win_tree_node> win_tree = getTree(dsp, candidate); | |
for (int i = 0; i < win_tree.size(); i++) | |
{ | |
if (hasCorrectClass(dsp, win_tree[i].window, wm_class)) | |
{ | |
cout << "Tree with root: 0x" << hex << win_tree[0].window | |
<< " contains a window (0x" << win_tree[i].window | |
<< ") with the correct class: " << wm_class << endl; | |
printTree(win_tree); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment