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