Last active
January 24, 2022 23:23
-
-
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.
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> | |
#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, ¤t_node.parent, | |
¤t_node.children, ¤t_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