Pass NULL, not 0, handle invalid --id better.
[dana/openbox.git] / tools / obxprop / obxprop.c
1 #include <X11/Xlib.h>
2 #include <X11/cursorfont.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <stdio.h>
6 #include <assert.h>
7 #include <glib.h>
8
9 gint fail(const gchar *s) {
10     if (s)
11         fprintf(stderr, "%s\n", s);
12     else
13         fprintf
14             (stderr,
15              "Usage: obxprop [OPTIONS] [--] [PROPERTIES ...]\n\n"
16              "Options:\n"
17              "    --help              Display this help and exit\n"
18              "    --display DISPLAY   Connect to this X display\n"
19              "    --id ID             Show the properties for this window\n"
20              "    --root              Show the properties for the root window\n");
21     return 1;
22 }
23
24 gint parse_hex(gchar *s) {
25     gint result = 0;
26     while (*s) {
27         gint add;
28         if (*s >= '0' && *s <='9')
29             add = *s-'0';
30         else if (*s >= 'A' && *s <='F')
31             add = *s-'A';
32         else if (*s >= 'a' && *s <='f')
33             add = *s-'a';
34         else
35             break;
36
37         result *= 16;
38         result += add;
39     }
40     return result;
41 }
42
43 Window find_client(Display *d, Window win)
44 {
45     Window r, *children;
46     guint n, i;
47     Atom state = XInternAtom(d, "WM_STATE", True);
48     Atom ret_type;
49     gint ret_format, res;
50     gulong ret_items, ret_bytesleft, *xdata;
51
52     XQueryTree(d, win, &r, &r, &children, &n);
53     for (i = 0; i < n; ++i) {
54         Window w = find_client(d, children[i]);
55         if (w) return w;
56     }
57
58     // try me
59     res = XGetWindowProperty(d, win, state, 0, 1,
60                        False, state, &ret_type, &ret_format,
61                        &ret_items, &ret_bytesleft,
62                        (unsigned char**) &xdata);
63     XFree(xdata);
64     if (res != Success || ret_type == None || ret_items < 1)
65         return None;
66     return win; // found it!
67 }
68
69 static gboolean get_all(Display *d, Window win, Atom prop,
70                         Atom *type, gint *size,
71                         guchar **data, guint *num)
72 {
73     gboolean ret = FALSE;
74     gint res;
75     guchar *xdata = NULL;
76     gulong ret_items, bytes_left;
77
78     res = XGetWindowProperty(d, win, prop, 0l, G_MAXLONG,
79                              FALSE, AnyPropertyType, type, size,
80                              &ret_items, &bytes_left, &xdata);
81     if (res == Success) {
82         if (ret_items > 0) {
83             guint i;
84
85             *data = g_malloc(ret_items * (*size / 8));
86             for (i = 0; i < ret_items; ++i)
87                 switch (*size) {
88                 case 8:
89                     (*data)[i] = xdata[i];
90                     break;
91                 case 16:
92                     ((guint16*)*data)[i] = ((gushort*)xdata)[i];
93                     break;
94                 case 32:
95                     ((guint32*)*data)[i] = ((gulong*)xdata)[i];
96                     break;
97                 default:
98                     g_assert_not_reached(); /* unhandled size */
99                 }
100             *num = ret_items;
101             ret = TRUE;
102         }
103         XFree(xdata);
104     }
105     return ret;
106 }
107
108 gchar *append_string(gchar *before, gchar *after, gboolean quote)
109 {
110     gchar *tmp;
111     const gchar *q = quote ? "\"" : "";
112     if (before)
113         tmp = g_strdup_printf("%s, %s%s%s", before, q, after, q);
114     else
115         tmp = g_strdup_printf("%s%s%s", q, after, q);
116     g_free(before);
117     return tmp;
118 }
119
120 gchar *append_int(gchar *before, guint after)
121 {
122     gchar *tmp;
123     if (before)
124         tmp = g_strdup_printf("%s, %u", before, after);
125     else
126         tmp = g_strdup_printf("%u", after);
127     g_free(before);
128     return tmp;
129 }
130
131 gchar* read_strings(gchar *val, guint n, gboolean utf8)
132 {
133     GSList *strs = NULL, *it;
134     gchar *ret, *p;
135     guint i;
136
137     p = val;
138     while (p < val + n) {
139         strs = g_slist_append(strs, g_strndup(p, n - (p - val)));
140         p += strlen(p) + 1; /* next string */
141     }
142
143     ret = NULL;
144     for (i = 0, it = strs; it; ++i, it = g_slist_next(it)) {
145         char *data;
146
147         if (utf8) {
148             if (g_utf8_validate(it->data, -1, NULL))
149                 data = g_strdup(it->data);
150             else
151                 data = g_strdup("");
152         }
153         else
154             data = g_locale_to_utf8(it->data, -1, NULL, NULL, NULL);
155
156         ret = append_string(ret, data, TRUE);
157         g_free(data);
158     }
159
160     while (strs) {
161         g_free(strs->data);
162         strs = g_slist_delete_link(strs, strs);
163     }
164     return ret;
165 }
166
167 gchar* read_atoms(Display *d, guchar *val, guint n)
168 {
169     gchar *ret;
170     guint i;
171
172     ret = NULL;
173     for (i = 0; i < n; ++i)
174         ret = append_string(ret, XGetAtomName(d, ((guint32*)val)[i]), FALSE);
175     return ret;
176 }
177
178 gchar* read_numbers(guchar *val, guint n, guint size)
179 {
180     gchar *ret;
181     guint i;
182
183     ret = NULL;
184     for (i = 0; i < n; ++i)
185         switch (size) {
186         case 8:
187             ret = append_int(ret, ((guint8*)val)[i]);
188             break;
189         case 16:
190             ret = append_int(ret, ((guint16*)val)[i]);
191             break;
192         case 32:
193             ret = append_int(ret, ((guint32*)val)[i]);
194             break;
195         default:
196             g_assert_not_reached(); /* unhandled size */
197         }
198
199     return ret;
200 }
201
202 gboolean read_prop(Display *d, Window w, Atom prop, const gchar **type, gchar **val)
203 {
204     guchar *ret;
205     guint nret;
206     gint size;
207     Atom ret_type;
208
209     ret = NULL;
210     if (get_all(d, w, prop, &ret_type, &size, &ret, &nret)) {
211         *type = XGetAtomName(d, ret_type);
212
213         if (strcmp(*type, "STRING") == 0)
214             *val = read_strings((gchar*)ret, nret, FALSE);
215         else if (strcmp(*type, "UTF8_STRING") == 0)
216             *val = read_strings((gchar*)ret, nret, TRUE);
217         else if (strcmp(*type, "ATOM") == 0) {
218             g_assert(size == 32);
219             *val = read_atoms(d, ret, nret);
220         }
221        else
222             *val = read_numbers(ret, nret, size);
223
224         g_free(ret);
225         return TRUE;
226     }
227     return FALSE;
228 }
229
230 void show_properties(Display *d, Window w, int argc, char **argv)
231 {
232     Atom* props;
233     int i, n;
234
235     props = XListProperties(d, w, &n);
236
237     for (i = 0; i < n; ++i) {
238         const char *type;
239         char *name, *val;
240
241         name = XGetAtomName(d, props[i]);
242
243         if (read_prop(d, w, props[i], &type, &val)) {
244             int found = 1;
245             if (argc) {
246                 int i;
247
248                 found = 0;
249                 for (i = 0; i < argc; i++)
250                     if (!strcmp(name, argv[i])) {
251                         found = 1;
252                         break;
253                     }
254             }
255             if (found)
256                 g_print("%s(%s) = %s\n", name, type, val);
257             g_free(val);
258         }
259
260         XFree(name);
261     }
262
263     XFree(props);
264 }
265
266 int main(int argc, char **argv)
267 {
268     Display *d;
269     Window id, userid = None;
270     int i;
271     char *dname = NULL;
272     gboolean root = FALSE;
273
274     for (i = 1; i < argc; ++i) {
275         if (!strcmp(argv[i], "--help")) {
276             return fail(NULL);
277         }
278         else if (!strcmp(argv[i], "--root"))
279             root = TRUE;
280         else if (!strcmp(argv[i], "--id")) {
281             if (++i == argc)
282                 return fail(NULL);
283             if (argv[i][0] == '0' && argv[i][1] == 'x') {
284                 /* hex */
285                 userid = parse_hex(argv[i]+2);
286             }
287             else {
288                 /* decimal */
289                 userid = atoi(argv[i]);
290             }
291             if (!userid)
292                 return fail("Unable to parse argument to --id.");
293         }
294         else if (!strcmp(argv[i], "--display")) {
295             if (++i == argc)
296                 return fail(NULL);
297             dname = argv[i];
298         }
299         else if (*argv[i] != '-')
300             break;
301         else if (!strcmp(argv[i], "--")) {
302             i++;
303             break;
304         }
305         else
306             return fail(NULL);
307     }
308
309     d = XOpenDisplay(dname);
310     if (!d) {
311         return fail("Unable to find an X display. "
312                     "Ensure you have permission to connect to the display.");
313     }
314
315     if (root)
316         userid = RootWindow(d, DefaultScreen(d));
317
318     if (userid == None) {
319         int j;
320         j = XGrabPointer(d, RootWindow(d, DefaultScreen(d)),
321                          False, ButtonPressMask,
322                          GrabModeAsync, GrabModeAsync,
323                          None, XCreateFontCursor(d, XC_crosshair),
324                          CurrentTime);
325         if (j != GrabSuccess)
326             return fail("Unable to grab the pointer device");
327         while (1) {
328             XEvent ev;
329
330             XNextEvent(d, &ev);
331             if (ev.type == ButtonPress) {
332                 XUngrabPointer(d, CurrentTime);
333                 userid = ev.xbutton.subwindow;
334                 break;
335             }
336         }
337     }
338
339     id = find_client(d, userid);
340
341     if (id == None)
342         return fail("Unable to find window with the requested ID");
343
344     show_properties(d, id, argc - i, &argv[i]);
345     
346     XCloseDisplay(d);
347
348     return 0;
349 }