Skip to content

Instantly share code, notes, and snippets.

@christian-eriksson
Last active January 24, 2022 23:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save christian-eriksson/32795a7185cbc5f968c4fa59b77d1ed0 to your computer and use it in GitHub Desktop.
Save christian-eriksson/32795a7185cbc5f968c4fa59b77d1ed0 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 created and follow some events such as reparenting and destruction.
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <assert.h>
#include <stdio.h>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define NIL (0)
struct win_tree_node
{
Window window;
Window parent;
Window *children;
unsigned int nchildren;
};
struct win_pair
{
Window window;
Window root;
};
/**
* 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;
}
/**
* Collects the tree in a straight line from candidate to the root. It does not
* go down any other branches but it checks the number of children at each node.
*/
vector<win_tree_node> getTree(Display *dsp, Window candidate)
{
vector<win_tree_node> win_tree;
win_tree.reserve(8);
Window current_win = candidate;
Window root;
do
{
win_tree_node current_node;
current_node.window = current_win;
if (XQueryTree(dsp, current_node.window, &root, &current_node.parent,
&current_node.children, &current_node.nchildren))
{
win_tree.push_back(current_node);
}
else
{
cout << "XQueryTree didn't work :(" << endl;
break;
}
current_win = current_node.parent;
} while (root != current_win);
reverse(win_tree.begin(), win_tree.end());
return win_tree;
}
void printTree(vector<win_tree_node> win_tree)
{
for (int i = 0; i < win_tree.size(); i++)
{
cout << i + 1 << "\twindow: 0x" << hex << win_tree[i].window
<< " parent: 0x" << win_tree[i].parent << " #children: "
<< dec << win_tree[i].nchildren << endl;
}
}
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);
vector<win_pair> candidate_windows;
candidate_windows.reserve(16);
// 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 == CreateNotify)
{
XCreateWindowEvent create_event = e.xcreatewindow;
win_pair candidate;
candidate.window = create_event.window;
candidate.root = 0;
if (hasCorrectClass(dsp, candidate.window, wm_class))
{
cout << "Found window 0x" << hex << candidate.window << " with class " << wm_class << endl;
candidate_windows.push_back(candidate);
// Here we push the candidate to the array so we can check later on if
// it is added to the correct structure in a Reparent Event.
// We might also check the structure at this point to see if it already
// has the sought structure.
}
}
else if (e.type == ReparentNotify)
{
XReparentEvent reparent_event = e.xreparent;
Window candidate = reparent_event.window;
Window new_parent = reparent_event.parent;
vector<win_tree_node> win_tree = getTree(dsp, candidate);
int i = 0;
for (; i < candidate_windows.size(); i++)
{
if (candidate_windows[i].window == candidate)
{
cout << "candidate with class " << wm_class << " reparented!" << endl;
printTree(win_tree);
candidate_windows[i].root = win_tree[0].window;
// Here we could check the win_tree structure and perhaps determine if
// the tree matches the sought structure.
}
}
}
else if (e.type == DestroyNotify)
{
XDestroyWindowEvent destroy_event = e.xdestroywindow;
Window candidate = destroy_event.window;
int i = 0;
bool should_remove = false;
for (; i < candidate_windows.size(); i++)
{
if (candidate == candidate_windows[i].root ||
candidate == candidate_windows[i].window)
{
cout << "DestroyNotify: window 0x" << hex << candidate << endl;
should_remove = true;
break;
}
}
if (should_remove)
{
cout << "removing: 0x" << hex << candidate << endl;
candidate_windows.erase(candidate_windows.begin() + i);
cout << "0x" << hex << candidate << " removed" << endl;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment