1/*
2Copyright 1989, 1998  The Open Group
3Copyright 2009  Open Text Corporation
4
5Permission to use, copy, modify, distribute, and sell this software and its
6documentation for any purpose is hereby granted without fee, provided that
7the above copyright notice appear in all copies and that both that
8copyright notice and this permission notice appear in supporting
9documentation.
10
11The above copyright notice and this permission notice shall be included in
12all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
17OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
21Except as contained in this notice, the name of The Open Group shall not be
22used in advertising or otherwise to promote the sale, use or other dealings
23in this Software without prior written authorization from The Open Group.
24 * *
25 * Author:  Jim Fulton, MIT X Consortium
26 * Author:  Peter Harris, Open Text Corporation
27 */
28
29#ifdef HAVE_CONFIG_H
30#include "config.h"
31#endif
32
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <ctype.h>
37#include <inttypes.h>
38#include <xcb/xcb.h>
39#include <xcb/xproto.h>
40#ifndef HAVE_STRNLEN
41#include "strnlen.h"
42#endif
43
44#ifndef PRIx32
45#define PRIx32 "x"
46#endif
47#ifndef PRIu32
48#define PRIu32 "u"
49#endif
50
51#ifndef __has_attribute
52# define __has_attribute(x) 0  /* Compatibility with older compilers. */
53#endif
54
55static char *ProgramName;
56
57static xcb_atom_t WM_STATE;
58
59static void lookat (xcb_connection_t *dpy, xcb_window_t root, int verbose, int maxcmdlen);
60static void print_client_properties (xcb_connection_t *dpy, xcb_window_t w,
61				     int verbose, int maxcmdlen );
62static void print_text_field (xcb_connection_t *dpy, const char *s, xcb_get_property_reply_t *tp );
63static int print_quoted_word (char *s, int maxlen);
64static void unknown (xcb_connection_t *dpy, xcb_atom_t actual_type, int actual_format );
65
66/* For convenience: */
67typedef int Bool;
68#define False (0)
69#define True (!False)
70
71static void
72#if __has_attribute(__cold__)
73__attribute__((__cold__))
74#endif
75#if __has_attribute(noreturn)
76__attribute__((noreturn))
77#endif
78usage(const char *errmsg)
79{
80    if (errmsg != NULL)
81	fprintf (stderr, "%s: %s\n\n", ProgramName, errmsg);
82
83    fprintf (stderr,
84	     "usage:  %s  [-display dpy] [-m len] [-[a][l]] [-version]\n",
85	     ProgramName);
86    exit (1);
87}
88
89typedef void (*queue_func)(void *closure);
90typedef struct queue_blob {
91    queue_func func;
92    void *closure;
93    struct queue_blob *next;
94} queue_blob;
95
96static queue_blob *head = NULL;
97static queue_blob **tail = &head;
98
99static void enqueue(queue_func func, void *closure)
100{
101    queue_blob *blob = malloc(sizeof(*blob));
102    if (!blob)
103	return; /* TODO: print OOM error */
104
105    blob->func = func;
106    blob->closure = closure;
107    blob->next = NULL;
108    *tail = blob;
109    tail = &blob->next;
110}
111
112static void run_queue(void)
113{
114    while (head) {
115	queue_blob *blob = head;
116	blob->func(blob->closure);
117	head = blob->next;
118	free(blob);
119    }
120    tail = &head;
121}
122
123typedef struct {
124    xcb_connection_t *c;
125    xcb_intern_atom_cookie_t cookie;
126    xcb_atom_t *atom;
127} atom_state;
128
129static void atom_done(void *closure)
130{
131    xcb_intern_atom_reply_t *reply;
132    atom_state *as = closure;
133
134    reply = xcb_intern_atom_reply(as->c, as->cookie, NULL);
135    if (!reply)
136	goto done; /* TODO: print Error message */
137
138    *(as->atom) = reply->atom;
139    free(reply);
140
141done:
142    free(as);
143}
144
145static void init_atoms(xcb_connection_t *c)
146{
147    atom_state *as;
148
149    as = malloc(sizeof(*as));
150    as->c = c;
151    as->atom = &WM_STATE;
152    as->cookie = xcb_intern_atom(c, 0, strlen("WM_STATE"), "WM_STATE");
153    enqueue(atom_done, as);
154}
155
156int
157main(int argc, char *argv[])
158{
159    int i;
160    char *displayname = NULL;
161    Bool all_screens = False;
162    Bool verbose = False;
163    xcb_connection_t *dpy;
164    const xcb_setup_t *setup;
165    int screen_number = 0;
166    int maxcmdlen = 10000;
167
168    ProgramName = argv[0];
169
170    for (i = 1; i < argc; i++) {
171	char *arg = argv[i];
172
173	if (arg[0] == '-') {
174	    char *cp;
175
176	    switch (arg[1]) {
177	      case 'd':			/* -display dpyname */
178		if (++i >= argc) usage ("-display requires an argument");
179		displayname = argv[i];
180		continue;
181	      case 'm':			/* -max maxcmdlen */
182		if (++i >= argc) usage ("-max requires an argument");
183		maxcmdlen = atoi (argv[i]);
184		continue;
185	      case 'v':			/* -version */
186		printf("%s\n", PACKAGE_STRING);
187		exit(0);
188	    }
189
190	    for (cp = &arg[1]; *cp; cp++) {
191		switch (*cp) {
192		  case 'a':		/* -all */
193		    all_screens = True;
194		    continue;
195		  case 'l':		/* -long */
196		    verbose = True;
197		    continue;
198		  default:
199		    fprintf (stderr, "%s: unrecognized argument -%s\n\n",
200			     ProgramName, cp);
201		    usage (NULL);
202		}
203	    }
204	} else {
205	    fprintf (stderr, "%s: unrecognized argument %s\n\n",
206		     ProgramName, arg);
207	    usage (NULL);
208	}
209    }
210
211    dpy = xcb_connect(displayname, &screen_number);
212    if (xcb_connection_has_error(dpy)) {
213	const char *name = displayname;
214	if (!name)
215	    name = getenv("DISPLAY");
216	if (!name)
217	    name = "";
218	fprintf (stderr, "%s:  unable to open display \"%s\"\r\n",
219		 ProgramName, name);
220	exit (1);
221    }
222
223    init_atoms(dpy);
224
225    setup = xcb_get_setup(dpy);
226    if (all_screens) {
227	xcb_screen_iterator_t screen;
228
229	screen = xcb_setup_roots_iterator(setup);
230	do {
231	    lookat(dpy, screen.data->root, verbose, maxcmdlen);
232	    xcb_screen_next(&screen);
233	} while (screen.rem);
234    } else {
235	xcb_screen_iterator_t screen;
236
237	screen = xcb_setup_roots_iterator(setup);
238	for (i = 0; i < screen_number; i++)
239	    xcb_screen_next(&screen);
240
241	lookat (dpy, screen.data->root, verbose, maxcmdlen);
242    }
243
244    run_queue();
245
246    xcb_disconnect(dpy);
247    exit (0);
248}
249
250typedef struct {
251    xcb_connection_t *c;
252    xcb_get_property_cookie_t *prop_cookie;
253    xcb_query_tree_cookie_t *tree_cookie;
254    xcb_window_t *win;
255    xcb_window_t orig_win;
256    unsigned int list_length;
257    int verbose;
258    int maxcmdlen;
259} child_wm_state;
260
261static void child_info(void *closure)
262{
263    child_wm_state *cs = closure;
264    xcb_window_t orig = cs->orig_win;
265    xcb_connection_t *c = cs->c;
266    int verbose = cs->verbose;
267    int maxcmdlen = cs->maxcmdlen;
268    unsigned int i, j;
269
270    unsigned int child_count, num_rep;
271    xcb_query_tree_reply_t **qt_reply;
272
273    for (i = 0; i < cs->list_length; i++) {
274	xcb_get_property_reply_t *gp_reply;
275	gp_reply = xcb_get_property_reply(c, cs->prop_cookie[i], NULL);
276	if (gp_reply) {
277	    if (gp_reply->type) {
278		/* Show information for this window */
279		print_client_properties(c, cs->win[i], cs->verbose, cs->maxcmdlen);
280
281		free(gp_reply);
282
283		/* drain stale replies */
284		for (j = i+1; j < cs->list_length; j++) {
285		    gp_reply = xcb_get_property_reply(c, cs->prop_cookie[j], NULL);
286		    if (gp_reply)
287			free(gp_reply);
288		}
289		for (j = 0; j < cs->list_length; j++) {
290		    xcb_query_tree_reply_t *rep;
291		    rep = xcb_query_tree_reply(c, cs->tree_cookie[j], NULL);
292		    if (rep)
293			free(rep);
294		}
295		goto done;
296	    }
297	    free(gp_reply);
298	}
299    }
300
301    /* WM_STATE not found. Recurse into children: */
302    num_rep = 0;
303    qt_reply = malloc(sizeof(*qt_reply) * cs->list_length);
304    if (!qt_reply)
305	goto done; /* TODO: print OOM message, drain reply queue */
306
307    for (i = 0; i < cs->list_length; i++) {
308	qt_reply[num_rep] = xcb_query_tree_reply(c, cs->tree_cookie[i], NULL);
309	if (qt_reply[num_rep])
310	    num_rep++;
311    }
312
313    child_count = 0;
314    for (i = 0; i < num_rep; i++)
315	child_count += qt_reply[i]->children_len;
316
317    if (!child_count) {
318	/* No children have CS_STATE; try the parent window */
319	print_client_properties(c, cs->orig_win, cs->verbose, cs->maxcmdlen);
320	goto reply_done;
321    }
322
323    cs = malloc(sizeof(*cs) + child_count * (sizeof(*cs->prop_cookie) + sizeof(*cs->tree_cookie) + sizeof(*cs->win)));
324    if (!cs)
325	goto reply_done; /* TODO: print OOM message */
326
327    cs->c = c;
328    cs->verbose = verbose;
329    cs->maxcmdlen = maxcmdlen;
330    cs->orig_win = orig;
331    cs->prop_cookie = (void *)&cs[1];
332    cs->tree_cookie = (void *)&cs->prop_cookie[child_count];
333    cs->win = (void *)&cs->tree_cookie[child_count];
334    cs->list_length = child_count;
335
336    child_count = 0;
337    for (i = 0; i < num_rep; i++) {
338	xcb_window_t *child = xcb_query_tree_children(qt_reply[i]);
339	for (j = 0; j < qt_reply[i]->children_len; j++) {
340	    cs->win[child_count] = child[j];
341	    cs->prop_cookie[child_count] = xcb_get_property(c, 0, child[j],
342			    WM_STATE, XCB_GET_PROPERTY_TYPE_ANY,
343			    0, 0);
344	    /* Just in case the property isn't there, get the tree too */
345	    cs->tree_cookie[child_count++] = xcb_query_tree(c, child[j]);
346	}
347    }
348
349    enqueue(child_info, cs);
350
351reply_done:
352    for (i = 0; i < num_rep; i++)
353	free(qt_reply[i]);
354    free(qt_reply);
355
356done:
357    free(closure);
358}
359
360typedef struct {
361    xcb_connection_t *c;
362    xcb_query_tree_cookie_t cookie;
363    int verbose;
364    int maxcmdlen;
365} root_list_state;
366
367static void root_list(void *closure)
368{
369    int i;
370    xcb_window_t *child;
371    xcb_query_tree_reply_t *reply;
372    root_list_state *rl = closure;
373
374    reply = xcb_query_tree_reply(rl->c, rl->cookie, NULL);
375    if (!reply)
376	goto done;
377
378    child = xcb_query_tree_children(reply);
379    for (i = 0; i < reply->children_len; i++) {
380	/* Get information about each child */
381	child_wm_state *cs = malloc(sizeof(*cs) + sizeof(*cs->prop_cookie) + sizeof(*cs->tree_cookie) + sizeof(*cs->win));
382	if (!cs)
383	    goto done; /* TODO: print OOM message */
384	cs->c = rl->c;
385	cs->verbose = rl->verbose;
386	cs->maxcmdlen = rl->maxcmdlen;
387	cs->prop_cookie = (void *)&cs[1];
388	cs->tree_cookie = (void *)&cs->prop_cookie[1];
389	cs->win = (void *)&cs->tree_cookie[1];
390
391	cs->orig_win = child[i];
392	cs->win[0] = child[i];
393
394	cs->prop_cookie[0] = xcb_get_property(rl->c, 0, child[i],
395			WM_STATE, XCB_GET_PROPERTY_TYPE_ANY,
396			0, 0);
397	/* Just in case the property isn't there, get the tree too */
398	cs->tree_cookie[0] = xcb_query_tree(rl->c, child[i]);
399
400	cs->list_length = 1;
401	enqueue(child_info, cs);
402    }
403    free(reply);
404
405done:
406    free(rl);
407}
408
409static void
410lookat(xcb_connection_t *dpy, xcb_window_t root, int verbose, int maxcmdlen)
411{
412    root_list_state *rl = malloc(sizeof(*rl));
413
414    if (!rl)
415	return; /* TODO: OOM message */
416
417    /*
418     * get the list of windows
419     */
420
421    rl->c = dpy;
422    rl->cookie = xcb_query_tree(dpy, root);
423    rl->verbose = verbose;
424    rl->maxcmdlen = maxcmdlen;
425    enqueue(root_list, rl);
426}
427
428static const char *Nil = "(nil)";
429
430typedef struct {
431    xcb_connection_t *c;
432    xcb_get_property_cookie_t client_machine;
433    xcb_get_property_cookie_t command;
434    xcb_get_property_cookie_t name;
435    xcb_get_property_cookie_t icon_name;
436    xcb_get_property_cookie_t wm_class;
437    xcb_window_t w;
438    int verbose;
439    int maxcmdlen;
440} client_state;
441
442static void
443show_client_properties(void *closure)
444{
445    client_state *cs = closure;
446    xcb_get_property_reply_t *client_machine;
447    xcb_get_property_reply_t *command;
448    xcb_get_property_reply_t *name = NULL;
449    xcb_get_property_reply_t *icon_name = NULL;
450    xcb_get_property_reply_t *wm_class = NULL;
451    char *argv;
452    int charsleft = cs->maxcmdlen;
453    unsigned int i;
454
455    /*
456     * get the WM_MACHINE and WM_COMMAND list of strings
457     */
458    client_machine = xcb_get_property_reply(cs->c, cs->client_machine, NULL);
459    command = xcb_get_property_reply(cs->c, cs->command, NULL);
460    if (cs->verbose) {
461	name = xcb_get_property_reply(cs->c, cs->name, NULL);
462	icon_name = xcb_get_property_reply(cs->c, cs->icon_name, NULL);
463	wm_class = xcb_get_property_reply(cs->c, cs->wm_class, NULL);
464    }
465
466    if (!command || !command->type)
467	goto done;
468
469    /*
470     * do header information
471     */
472    if (cs->verbose) {
473	printf ("Window 0x%" PRIx32 ":\n", cs->w);
474	print_text_field (cs->c, "  Machine:  ", client_machine);
475	if (name && name->type)
476	    print_text_field (cs->c, "  Name:  ", name);
477    } else {
478	print_text_field (cs->c, NULL, client_machine);
479	putchar (' ');
480	putchar (' ');
481    }
482
483
484    if (cs->verbose)
485	if (icon_name && icon_name->type)
486	    print_text_field (cs->c, "  Icon Name:  ", icon_name);
487
488
489    /*
490     * do the command
491     */
492    if (cs->verbose)
493	printf ("  Command:  ");
494    argv = xcb_get_property_value(command);
495    for (i = 0; i < command->value_len && charsleft > 0; ) {
496	charsleft -= print_quoted_word (argv + i, charsleft);
497	i += strnlen(argv + i, command->value_len - i) + 1;
498	if (i < command->value_len && charsleft > 0) {
499	    putchar (' ');
500	    charsleft--;
501	}
502    }
503    putchar ('\n');
504
505
506    /*
507     * do trailer information
508     */
509    if (cs->verbose) {
510	if (wm_class && wm_class->type) {
511	    const char *res_name, *res_class;
512	    int name_len, class_len; /* Must be int for use with %.*s */
513	    res_name = xcb_get_property_value(wm_class);
514	    name_len = (int) strnlen(res_name, wm_class->value_len) + 1;
515	    class_len = (int) wm_class->value_len - name_len;
516	    if (class_len > 0) {
517		res_class = res_name + name_len;
518	    } else {
519		res_class = Nil;
520		class_len = (int) strlen(res_class);
521	    }
522
523	    printf ("  Instance/Class:  %.*s/%.*s",
524		    name_len, res_name,
525		    class_len, res_class);
526	    putchar ('\n');
527	}
528    }
529
530done:
531    if (client_machine)
532	free(client_machine);
533    if (command)
534	free(command);
535    if (cs->verbose) {
536	if (name)
537	    free(name);
538	if (icon_name)
539	    free(icon_name);
540	if (wm_class)
541	    free(wm_class);
542    }
543    free(cs);
544}
545
546static void
547print_client_properties(xcb_connection_t *dpy, xcb_window_t w, int verbose, int maxcmdlen)
548{
549    client_state *cs = malloc(sizeof(*cs));
550    if (!cs)
551	return; /* TODO: print OOM message */
552
553    cs->c = dpy;
554    cs->w = w;
555    cs->verbose = verbose;
556    cs->maxcmdlen = maxcmdlen;
557
558    /*
559     * get the WM_CLIENT_MACHINE and WM_COMMAND list of strings
560     */
561    cs->client_machine = xcb_get_property(dpy, 0, w,
562			    XCB_ATOM_WM_CLIENT_MACHINE, XCB_GET_PROPERTY_TYPE_ANY,
563			    0, 1000000L);
564    cs->command = xcb_get_property(dpy, 0, w,
565			    XCB_ATOM_WM_COMMAND, XCB_GET_PROPERTY_TYPE_ANY,
566			    0, 1000000L);
567
568    if (verbose) {
569	cs->name = xcb_get_property(dpy, 0, w,
570			    XCB_ATOM_WM_NAME, XCB_GET_PROPERTY_TYPE_ANY,
571			    0, 1000000L);
572	cs->icon_name = xcb_get_property(dpy, 0, w,
573			    XCB_ATOM_WM_ICON_NAME, XCB_GET_PROPERTY_TYPE_ANY,
574			    0, 1000000L);
575	cs->wm_class = xcb_get_property(dpy, 0, w,
576			    XCB_ATOM_WM_CLASS, XCB_ATOM_STRING,
577			    0, 1000000L);
578    }
579
580    enqueue(show_client_properties, cs);
581}
582
583static void
584print_text_field(xcb_connection_t *dpy, const char *s, xcb_get_property_reply_t *tp)
585{
586    if (tp->type == XCB_NONE || tp->format == 0) {  /* Or XCB_ATOM_NONE after libxcb 1.5 */
587	printf ("''");
588	return;
589    }
590
591    if (s) printf ("%s", s);
592    if (tp->type == XCB_ATOM_STRING && tp->format == 8) {
593	printf ("%.*s", (int)tp->value_len, (char *)xcb_get_property_value(tp));
594    } else {
595	unknown (dpy, tp->type, tp->format);
596    }
597    if (s) putchar ('\n');
598}
599
600/* returns the number of characters printed */
601static int
602print_quoted_word(char *s,
603		  int maxlen)		/* max number of chars we can print */
604{
605    register char *cp;
606    Bool need_quote = False, in_quote = False;
607    char quote_char = '\'', other_quote = '"';
608    int charsprinted = 0;
609
610    /*
611     * walk down seeing whether or not we need to quote
612     */
613    for (cp = s; *cp; cp++) {
614
615	if (! ((isascii(*cp) && isalnum(*cp)) ||
616	       (*cp == '-' || *cp == '_' || *cp == '.' || *cp == '+' ||
617		*cp == '/' || *cp == '=' || *cp == ':' || *cp == ','))) {
618	    need_quote = True;
619	    break;
620	}
621    }
622
623    /*
624     * write out the string: if we hit a quote, then close any previous quote,
625     * emit the other quote, swap quotes and continue on.
626     */
627    in_quote = need_quote;
628    if (need_quote) {
629	putchar (quote_char);
630	charsprinted++; maxlen--;
631    }
632    for (cp = s; *cp && maxlen>0; cp++) {
633	if (*cp == quote_char) {
634	    if (in_quote) {
635		putchar (quote_char);
636		charsprinted++; maxlen--;
637	    }
638	    putchar (other_quote);
639	    charsprinted++; maxlen--;
640	    {
641		char tmp = other_quote;
642		other_quote = quote_char; quote_char = tmp;
643	    }
644	    in_quote = True;
645	}
646	putchar (*cp);
647	charsprinted++; maxlen--;
648    }
649    /* close the quote if we opened one and if we printed the whole string */
650    if (in_quote && maxlen>0) {
651	putchar (quote_char);
652	charsprinted++; maxlen--;
653    }
654
655    return charsprinted;
656}
657
658static void
659unknown(xcb_connection_t *dpy, xcb_atom_t actual_type, int actual_format)
660{
661    printf ("<unknown type ");
662    if (actual_type == XCB_NONE)
663	printf ("None");
664    else {
665	/* This should happen so rarely as to make no odds. Eat a round-trip: */
666	xcb_get_atom_name_reply_t *atom =
667	    xcb_get_atom_name_reply(dpy,
668		xcb_get_atom_name(dpy, actual_type), NULL);
669	if (atom) {
670	    printf("%.*s", xcb_get_atom_name_name_length(atom),
671			  xcb_get_atom_name_name(atom));
672	    free(atom);
673	} else
674	    fputs (Nil, stdout);
675    }
676    printf (" (%" PRIu32 ") or format %d>", actual_type, actual_format);
677}
678