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