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