--- /dev/null
+#include <X11/Xlib.h>
+#include <X11/cursorfont.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <glib.h>
+
+gint fail(const gchar *s) {
+ if (s)
+ fprintf(stderr, "%s\n", s);
+ else
+ fprintf
+ (stderr,
+ "Usage: obprop [OPTIONS]\n\n"
+ "Options:\n"
+ " --help Display this help and exit\n"
+ " --display DISPLAY Connect to this X display\n"
+ " --id ID Show the properties for this window\n");
+ return 1;
+}
+
+gint parse_hex(gchar *s) {
+ gint result = 0;
+ while (*s) {
+ gint add;
+ if (*s >= '0' && *s <='9')
+ add = *s-'0';
+ else if (*s >= 'A' && *s <='F')
+ add = *s-'A';
+ else if (*s >= 'a' && *s <='f')
+ add = *s-'a';
+ else
+ break;
+
+ result *= 16;
+ result += add;
+ }
+ return result;
+}
+
+Window find_client(Display *d, Window win)
+{
+ Window r, *children;
+ guint n, i;
+ Atom state = XInternAtom(d, "WM_STATE", True);
+ Atom ret_type;
+ gint ret_format, res;
+ gulong ret_items, ret_bytesleft, *xdata;
+
+ XQueryTree(d, win, &r, &r, &children, &n);
+ for (i = 0; i < n; ++i) {
+ Window w = find_client(d, children[i]);
+ if (w) return w;
+ }
+
+ // try me
+ res = XGetWindowProperty(d, win, state, 0, 1,
+ False, state, &ret_type, &ret_format,
+ &ret_items, &ret_bytesleft,
+ (unsigned char**) &xdata);
+ XFree(xdata);
+ if (res != Success || ret_type == None || ret_items < 1)
+ return None;
+ return win; // found it!
+}
+
+static gboolean get_all(Display *d, Window win, Atom prop,
+ Atom *type, gint *size,
+ guchar **data, guint *num)
+{
+ gboolean ret = FALSE;
+ gint res;
+ guchar *xdata = NULL;
+ gulong ret_items, bytes_left;
+
+ res = XGetWindowProperty(d, win, prop, 0l, G_MAXLONG,
+ FALSE, AnyPropertyType, type, size,
+ &ret_items, &bytes_left, &xdata);
+ if (res == Success) {
+ if (ret_items > 0) {
+ guint i;
+
+ *data = g_malloc(ret_items * (*size / 8));
+ for (i = 0; i < ret_items; ++i)
+ switch (*size) {
+ case 8:
+ (*data)[i] = xdata[i];
+ break;
+ case 16:
+ ((guint16*)*data)[i] = ((gushort*)xdata)[i];
+ break;
+ case 32:
+ ((guint32*)*data)[i] = ((gulong*)xdata)[i];
+ break;
+ default:
+ g_assert_not_reached(); /* unhandled size */
+ }
+ *num = ret_items;
+ ret = TRUE;
+ }
+ XFree(xdata);
+ }
+ return ret;
+}
+
+gchar *append_string(gchar *before, gchar *after, gboolean quote)
+{
+ gchar *tmp;
+ const gchar *q = quote ? "\"" : "";
+ if (before)
+ tmp = g_strdup_printf("%s, %s%s%s", before, q, after, q);
+ else
+ tmp = g_strdup_printf("%s%s%s", q, after, q);
+ g_free(before);
+ return tmp;
+}
+
+gchar *append_int(gchar *before, guint after)
+{
+ gchar *tmp;
+ if (before)
+ tmp = g_strdup_printf("%s, %u", before, after);
+ else
+ tmp = g_strdup_printf("%u", after);
+ g_free(before);
+ return tmp;
+}
+
+gchar* read_strings(gchar *val, guint n, gboolean utf8)
+{
+ GSList *strs = NULL, *it;
+ gchar *ret, *p;
+ guint i;
+
+ p = val;
+ while (p < val + n) {
+ strs = g_slist_append(strs, g_strndup(p, n - (p - val)));
+ p += strlen(p) + 1; /* next string */
+ }
+
+ ret = NULL;
+ for (i = 0, it = strs; it; ++i, it = g_slist_next(it)) {
+ char *data;
+
+ if (utf8) {
+ if (g_utf8_validate(it->data, -1, NULL))
+ data = g_strdup(it->data);
+ else
+ data = g_strdup("");
+ }
+ else
+ data = g_locale_to_utf8(it->data, -1, NULL, NULL, NULL);
+
+ ret = append_string(ret, data, TRUE);
+ g_free(data);
+ }
+
+ while (strs) {
+ g_free(strs->data);
+ strs = g_slist_delete_link(strs, strs);
+ }
+ return ret;
+}
+
+gchar* read_atoms(Display *d, guchar *val, guint n)
+{
+ gchar *ret;
+ guint i;
+
+ ret = NULL;
+ for (i = 0; i < n; ++i)
+ ret = append_string(ret, XGetAtomName(d, ((guint32*)val)[i]), FALSE);
+ return ret;
+}
+
+gchar* read_numbers(guchar *val, guint n, guint size)
+{
+ gchar *ret;
+ guint i;
+
+ ret = NULL;
+ for (i = 0; i < n; ++i)
+ switch (size) {
+ case 8:
+ ret = append_int(ret, ((guint8*)val)[i]);
+ break;
+ case 16:
+ ret = append_int(ret, ((guint16*)val)[i]);
+ break;
+ case 32:
+ ret = append_int(ret, ((guint32*)val)[i]);
+ break;
+ default:
+ g_assert_not_reached(); /* unhandled size */
+ }
+
+ return ret;
+}
+
+gboolean read_prop(Display *d, Window w, Atom prop, const gchar **type, gchar **val)
+{
+ guchar *ret;
+ guint nret;
+ gint size;
+ Atom ret_type;
+
+ ret = NULL;
+ if (get_all(d, w, prop, &ret_type, &size, &ret, &nret)) {
+ *type = XGetAtomName(d, ret_type);
+
+ if (strcmp(*type, "STRING") == 0)
+ *val = read_strings((gchar*)ret, nret, FALSE);
+ else if (strcmp(*type, "UTF8_STRING") == 0)
+ *val = read_strings((gchar*)ret, nret, TRUE);
+ else if (strcmp(*type, "ATOM") == 0) {
+ g_assert(size == 32);
+ *val = read_atoms(d, ret, nret);
+ }
+ else
+ *val = read_numbers(ret, nret, size);
+
+ g_free(ret);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void show_properties(Display *d, Window w)
+{
+ Atom* props;
+ int i, n;
+
+ props = XListProperties(d, w, &n);
+
+ for (i = 0; i < n; ++i) {
+ const char *type;
+ char *name, *val;
+
+ name = XGetAtomName(d, props[i]);
+
+ if (read_prop(d, w, props[i], &type, &val)) {
+ g_print("%s(%s) = %s\n", name, type, val);
+ g_free(val);
+ }
+
+ XFree(name);
+ }
+
+ XFree(props);
+}
+
+int main(int argc, char **argv)
+{
+ Display *d;
+ Window id, userid = None;
+ int i;
+ char *dname = NULL;
+
+ for (i = 1; i < argc; ++i) {
+ if (!strcmp(argv[i], "--help")) {
+ return fail(0);
+ }
+ else if (!strcmp(argv[i], "--id")) {
+ if (++i == argc)
+ return fail(0);
+ if (argv[i][0] == '0' && argv[i][1] == 'x') {
+ /* hex */
+ userid = parse_hex(argv[i]+2);
+ }
+ else {
+ /* decimal */
+ userid = atoi(argv[i]);
+ }
+ break;
+ }
+ else if (!strcmp(argv[i], "--display")) {
+ if (++i == argc)
+ return fail(0);
+ dname = argv[i];
+ }
+ }
+
+ d = XOpenDisplay(dname);
+ if (!d) {
+ return fail("Unable to find an X display. "
+ "Ensure you have permission to connect to the display.");
+ }
+
+ if (userid == None) {
+ i = XGrabPointer(d, RootWindow(d, DefaultScreen(d)),
+ False, ButtonPressMask,
+ GrabModeAsync, GrabModeAsync,
+ None, XCreateFontCursor(d, XC_crosshair),
+ CurrentTime);
+ if (i != GrabSuccess)
+ return fail("Unable to grab the pointer device");
+ while (1) {
+ XEvent ev;
+
+ XNextEvent(d, &ev);
+ if (ev.type == ButtonPress) {
+ XUngrabPointer(d, CurrentTime);
+ userid = ev.xbutton.subwindow;
+ break;
+ }
+ }
+ }
+
+ id = find_client(d, userid);
+
+ if (id == None)
+ return fail("Unable to find window with the requested ID");
+
+ show_properties(d, id);
+
+ XCloseDisplay(d);
+
+ return 0;
+}