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