Skip to content

Instantly share code, notes, and snippets.

@christian-eriksson
Last active January 24, 2022 23:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save christian-eriksson/899c8a8d4a7800cd980972efbeba817d to your computer and use it in GitHub Desktop.
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.
#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