From 1839eabbdd697039a264fe7ebb3f4d26f08ddabe Mon Sep 17 00:00:00 2001 From: Peter Harris Date: Mon, 19 Oct 2009 18:21:26 -0700 Subject: Rewrite xlsclients to use XCB, avoiding many (many) round trips This version spends bandwidth to reduce latency; in particular, it aggressively calls query_tree before the answer to get_property(WM_STATE) is available. Even on my local box, this version is 4-10 times faster (30ms typical, vs 200ms typical for Xlib). I get similar results over the local network. At least one person reports this version takes less than 2 seconds over ssh, vs over a minute for the Xlib version (~30x faster). Reviewed-by: Jamey Sharp --- configure.ac | 6 +- xlsclients.c | 524 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 430 insertions(+), 100 deletions(-) diff --git a/configure.ac b/configure.ac index 1af6ba2..f665380 100644 --- a/configure.ac +++ b/configure.ac @@ -36,10 +36,14 @@ AM_CONFIG_HEADER(config.h) AC_PROG_CC AC_PROG_INSTALL +# Check for functional strnlen +AC_USE_SYSTEM_EXTENSIONS +AC_FUNC_STRNLEN + XORG_DEFAULT_OPTIONS # Checks for pkg-config packages -PKG_CHECK_MODULES(XLSCLIENTS, x11 xmuu) +PKG_CHECK_MODULES(XLSCLIENTS, xcb xcb-atom) AC_SUBST(XLSCLIENTS_CFLAGS) AC_SUBST(XLSCLIENTS_LIBS) diff --git a/xlsclients.c b/xlsclients.c index 0b6fc15..049bc4c 100644 --- a/xlsclients.c +++ b/xlsclients.c @@ -1,8 +1,6 @@ /* - * $Xorg: xlsclients.c,v 1.4 2001/02/09 02:05:54 xorgcvs Exp $ - * - * Copyright 1989, 1998 The Open Group +Copyright 2009 Open Text Corporation Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that @@ -25,26 +23,36 @@ used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from The Open Group. * * * Author: Jim Fulton, MIT X Consortium + * Author: Peter Harris, Open Text Corporation */ -/* $XFree86: xc/programs/xlsclients/xlsclients.c,v 1.5 2001/04/01 14:00:23 tsi Exp $ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif #include #include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include static char *ProgramName; -static void lookat ( Display *dpy, Window root, Bool verbose, int maxcmdlen ); -static void print_client_properties ( Display *dpy, Window w, - Bool verbose, int maxcmdlen ); -static void print_text_field ( Display *dpy, char *s, XTextProperty *tp ); -static int print_quoted_word ( char *s, int maxlen ); -static void unknown ( Display *dpy, Atom actual_type, int actual_format ); +static xcb_atom_t WM_STATE; + +static void lookat (xcb_connection_t *dpy, xcb_window_t root, int verbose, int maxcmdlen); +static void print_client_properties (xcb_connection_t *dpy, xcb_window_t w, + int verbose, int maxcmdlen ); +static void print_text_field (xcb_connection_t *dpy, char *s, xcb_get_property_reply_t *tp ); +static int print_quoted_word (char *s, int maxlen); +static void unknown (xcb_connection_t *dpy, xcb_atom_t actual_type, int actual_format ); + +/* For convenience: */ +typedef int Bool; +#define False (0) +#define True (!False) static void usage(void) @@ -54,6 +62,73 @@ usage(void) exit (1); } +typedef void (*queue_func)(void *closure); +typedef struct queue_blob { + queue_func func; + void *closure; + struct queue_blob *next; +} queue_blob; + +static queue_blob *head = NULL; +static queue_blob **tail = &head; + +static void enqueue(queue_func func, void *closure) +{ + queue_blob *blob = malloc(sizeof(*blob)); + if (!blob) + return; /* TODO: print OOM error */ + + blob->func = func; + blob->closure = closure; + blob->next = NULL; + *tail = blob; + tail = &blob->next; +} + +static void run_queue(void) +{ + while (head) { + queue_blob *blob = head; + blob->func(blob->closure); + head = blob->next; + free(blob); + } + tail = &head; +} + +typedef struct { + xcb_connection_t *c; + xcb_intern_atom_cookie_t cookie; + xcb_atom_t *atom; +} atom_state; + +static void atom_done(void *closure) +{ + xcb_intern_atom_reply_t *reply; + atom_state *as = closure; + + reply = xcb_intern_atom_reply(as->c, as->cookie, NULL); + if (!reply) + goto done; /* TODO: print Error message */ + + *(as->atom) = reply->atom; + free(reply); + +done: + free(as); +} + +static void init_atoms(xcb_connection_t *c) +{ + atom_state *as; + + as = malloc(sizeof(*as)); + as->c = c; + as->atom = &WM_STATE; + as->cookie = xcb_intern_atom(c, 0, strlen("WM_STATE"), "WM_STATE"); + enqueue(atom_done, as); +} + int main(int argc, char *argv[]) { @@ -61,7 +136,9 @@ main(int argc, char *argv[]) char *displayname = NULL; Bool all_screens = False; Bool verbose = False; - Display *dpy; + xcb_connection_t *dpy; + const xcb_setup_t *setup; + int screen_number = 0; int maxcmdlen = 10000; ProgramName = argv[0]; @@ -100,146 +177,391 @@ main(int argc, char *argv[]) } } - dpy = XOpenDisplay (displayname); + dpy = xcb_connect(displayname, &screen_number); if (!dpy) { + char *name = displayname; + if (!name) + name = getenv("DISPLAY"); + if (!name) + name = ""; fprintf (stderr, "%s: unable to open display \"%s\"\r\n", - ProgramName, XDisplayName (displayname)); + ProgramName, name); exit (1); } + init_atoms(dpy); + + setup = xcb_get_setup(dpy); if (all_screens) { - for (i = 0; i < ScreenCount(dpy); i++) { - lookat (dpy, RootWindow(dpy,i), verbose, maxcmdlen); - } + xcb_screen_iterator_t screen; + + screen = xcb_setup_roots_iterator(setup); + do { + lookat(dpy, screen.data->root, verbose, maxcmdlen); + xcb_screen_next(&screen); + } while (screen.rem); } else { - lookat (dpy, DefaultRootWindow(dpy), verbose, maxcmdlen); + xcb_screen_iterator_t screen; + + screen = xcb_setup_roots_iterator(setup); + for (i = 0; i < screen_number; i++) + xcb_screen_next(&screen); + + lookat (dpy, screen.data->root, verbose, maxcmdlen); } - XCloseDisplay (dpy); + run_queue(); + + xcb_disconnect(dpy); exit (0); } +typedef struct { + xcb_connection_t *c; + xcb_get_property_cookie_t *prop_cookie; + xcb_query_tree_cookie_t *tree_cookie; + xcb_window_t *win; + xcb_window_t orig_win; + int list_length; + int verbose; + int maxcmdlen; +} child_wm_state; + +static void child_info(void *closure) +{ + child_wm_state *cs = closure; + xcb_window_t orig = cs->orig_win; + xcb_connection_t *c = cs->c; + int verbose = cs->verbose; + int maxcmdlen = cs->maxcmdlen; + int i, j; + + int child_count, num_rep; + xcb_query_tree_reply_t **reply; + + for (i = 0; i < cs->list_length; i++) { + xcb_get_property_reply_t *reply; + reply = xcb_get_property_reply(c, cs->prop_cookie[i], NULL); + if (reply) { + if (reply->type) { + /* Show information for this window */ + print_client_properties(c, cs->win[i], cs->verbose, cs->maxcmdlen); + + free(reply); + + /* drain stale replies */ + for (j = i+1; j < cs->list_length; j++) { + reply = xcb_get_property_reply(c, cs->prop_cookie[j], NULL); + if (reply) + free(reply); + } + for (j = 0; j < cs->list_length; j++) { + xcb_query_tree_reply_t *rep; + rep = xcb_query_tree_reply(c, cs->tree_cookie[j], NULL); + if (rep) + free(rep); + } + goto done; + } + free(reply); + } + } + + /* WM_STATE not found. Recurse into children: */ + num_rep = 0; + reply = malloc(sizeof(*reply) * cs->list_length); + if (!reply) + goto done; /* TODO: print OOM message, drain reply queue */ + + for (i = 0; i < cs->list_length; i++) { + reply[num_rep] = xcb_query_tree_reply(c, cs->tree_cookie[i], NULL); + if (reply[num_rep]) + num_rep++; + } + + child_count = 0; + for (i = 0; i < num_rep; i++) + child_count += reply[i]->children_len; + + if (!child_count) { + /* No children have CS_STATE; try the parent window */ + print_client_properties(c, cs->orig_win, cs->verbose, cs->maxcmdlen); + goto reply_done; + } + + cs = malloc(sizeof(*cs) + child_count * (sizeof(*cs->prop_cookie) + sizeof(*cs->tree_cookie) + sizeof(*cs->win))); + if (!cs) + goto reply_done; /* TODO: print OOM message */ + + cs->c = c; + cs->verbose = verbose; + cs->maxcmdlen = maxcmdlen; + cs->orig_win = orig; + cs->prop_cookie = (void *)&cs[1]; + cs->tree_cookie = (void *)&cs->prop_cookie[child_count]; + cs->win = (void *)&cs->tree_cookie[child_count]; + cs->list_length = child_count; + + child_count = 0; + for (i = 0; i < num_rep; i++) { + xcb_window_t *child = xcb_query_tree_children(reply[i]); + for (j = 0; j < reply[i]->children_len; j++) { + cs->win[child_count] = child[j]; + cs->prop_cookie[child_count] = xcb_get_property(c, 0, child[j], + WM_STATE, XCB_GET_PROPERTY_TYPE_ANY, + 0, 0); + /* Just in case the property isn't there, get the tree too */ + cs->tree_cookie[child_count++] = xcb_query_tree(c, child[j]); + } + } + + enqueue(child_info, cs); + +reply_done: + for (i = 0; i < num_rep; i++) + free(reply[i]); + free(reply); + +done: + free(closure); +} + +typedef struct { + xcb_connection_t *c; + xcb_query_tree_cookie_t cookie; + int verbose; + int maxcmdlen; +} root_list_state; + +static void root_list(void *closure) +{ + int i; + xcb_window_t *child; + xcb_query_tree_reply_t *reply; + root_list_state *rl = closure; + + reply = xcb_query_tree_reply(rl->c, rl->cookie, NULL); + if (!reply) + goto done; + + child = xcb_query_tree_children(reply); + for (i = 0; i < reply->children_len; i++) { + /* Get information about each child */ + child_wm_state *cs = malloc(sizeof(*cs) + sizeof(*cs->prop_cookie) + sizeof(*cs->tree_cookie) + sizeof(*cs->win)); + if (!cs) + goto done; /* TODO: print OOM message */ + cs->c = rl->c; + cs->verbose = rl->verbose; + cs->maxcmdlen = rl->maxcmdlen; + cs->prop_cookie = (void *)&cs[1]; + cs->tree_cookie = (void *)&cs->prop_cookie[1]; + cs->win = (void *)&cs->tree_cookie[1]; + + cs->orig_win = child[i]; + cs->win[0] = child[i]; + + cs->prop_cookie[0] = xcb_get_property(rl->c, 0, child[i], + WM_STATE, XCB_GET_PROPERTY_TYPE_ANY, + 0, 0); + /* Just in case the property isn't there, get the tree too */ + cs->tree_cookie[0] = xcb_query_tree(rl->c, child[i]); + + cs->list_length = 1; + enqueue(child_info, cs); + } + free(reply); + +done: + free(rl); +} + static void -lookat(Display *dpy, Window root, Bool verbose, int maxcmdlen) +lookat(xcb_connection_t *dpy, xcb_window_t root, int verbose, int maxcmdlen) { - Window dummy, *children = NULL, client; - unsigned int i, nchildren = 0; + root_list_state *rl = malloc(sizeof(*rl)); - /* - * clients are not allowed to stomp on the root and ICCCM doesn't yet - * say anything about window managers putting stuff there; but, try - * anyway. - */ - print_client_properties (dpy, root, verbose, maxcmdlen); + if (!rl) + return; /* TODO: OOM message */ /* - * then, get the list of windows + * get the list of windows */ - if (!XQueryTree (dpy, root, &dummy, &dummy, &children, &nchildren)) { - return; - } - for (i = 0; i < nchildren; i++) { - client = XmuClientWindow (dpy, children[i]); - if (client != None) - print_client_properties (dpy, client, verbose, maxcmdlen); - } + rl->c = dpy; + rl->cookie = xcb_query_tree(dpy, root); + rl->verbose = verbose; + rl->maxcmdlen = maxcmdlen; + enqueue(root_list, rl); } static char *Nil = "(nil)"; +typedef struct { + xcb_connection_t *c; + xcb_get_property_cookie_t client_machine; + xcb_get_property_cookie_t command; + xcb_get_property_cookie_t name; + xcb_get_property_cookie_t icon_name; + xcb_get_property_cookie_t wm_class; + xcb_window_t w; + int verbose; + int maxcmdlen; +} client_state; + static void -print_client_properties(Display *dpy, Window w, Bool verbose, int maxcmdlen) +show_client_properties(void *closure) { - char **cliargv = NULL; - int i, cliargc; - XTextProperty nametp, machtp, tp; - int charsleft = maxcmdlen; + client_state *cs = closure; + xcb_get_property_reply_t *client_machine; + xcb_get_property_reply_t *command; + xcb_get_property_reply_t *name; + xcb_get_property_reply_t *icon_name; + xcb_get_property_reply_t *wm_class; + char *argv; + int charsleft = cs->maxcmdlen; + int i; /* * get the WM_MACHINE and WM_COMMAND list of strings */ - if (!XGetWMClientMachine (dpy, w, &machtp)) { - machtp.value = NULL; - machtp.encoding = None; + client_machine = xcb_get_property_reply(cs->c, cs->client_machine, NULL); + command = xcb_get_property_reply(cs->c, cs->command, NULL); + if (cs->verbose) { + name = xcb_get_property_reply(cs->c, cs->name, NULL); + icon_name = xcb_get_property_reply(cs->c, cs->icon_name, NULL); + wm_class = xcb_get_property_reply(cs->c, cs->wm_class, NULL); } - if (!XGetCommand (dpy, w, &cliargv, &cliargc)) { - if (machtp.value) XFree ((char *) machtp.value); - return; - } + if (!command || !command->type) + goto done; /* * do header information */ - if (verbose) { - printf ("Window 0x%lx:\n", w); - print_text_field (dpy, " Machine: ", &machtp); - if (XGetWMName (dpy, w, &nametp)) { - print_text_field (dpy, " Name: ", &nametp); - if (nametp.value) XFree ((char *) nametp.value); - } + if (cs->verbose) { + printf ("Window 0x%lx:\n", cs->w); + print_text_field (cs->c, " Machine: ", client_machine); + if (name && name->type) + print_text_field (cs->c, " Name: ", name); } else { - print_text_field (dpy, NULL, &machtp); + print_text_field (cs->c, NULL, client_machine); putchar (' '); putchar (' '); } - if (machtp.value) XFree ((char *) machtp.value); - if (verbose) { - if (XGetWMIconName (dpy, w, &tp)) { - print_text_field (dpy, " Icon Name: ", &tp); - if (tp.value) XFree ((char *) tp.value); - } - } + + if (cs->verbose) + if (icon_name && icon_name->type) + print_text_field (cs->c, " Icon Name: ", icon_name); /* * do the command */ - if (verbose) { + if (cs->verbose) printf (" Command: "); - } - for (i = 0; i < cliargc && charsleft > 0; ) { - charsleft -= print_quoted_word (cliargv[i], charsleft); - i++; - if (i < cliargc && charsleft > 0) { - putchar (' '); charsleft--; + argv = xcb_get_property_value(command); + for (i = 0; i < command->value_len && charsleft > 0; ) { + charsleft -= print_quoted_word (argv + i, charsleft); + i += strnlen(argv + i, command->value_len - i) + 1; + if (i < command->value_len && charsleft > 0) { + putchar (' '); + charsleft--; } } putchar ('\n'); - XFreeStringList (cliargv); /* * do trailer information */ - if (verbose) { - XClassHint clh; - if (XGetClassHint (dpy, w, &clh)) { - printf (" Instance/Class: %s/%s", - clh.res_name ? clh.res_name : Nil, - clh.res_class ? clh.res_class : Nil); - if (clh.res_name) XFree (clh.res_name); - if (clh.res_class) XFree (clh.res_class); + if (cs->verbose) { + if (wm_class && wm_class->type) { + char *res_name, *res_class; + int name_len, class_len; + res_name = xcb_get_property_value(wm_class); + name_len = strnlen(res_name, wm_class->value_len) + 1; + class_len = wm_class->value_len - name_len; + if (class_len > 0) { + res_class = res_name + name_len; + } else { + res_class = Nil; + class_len = strlen(res_class); + } + + printf (" Instance/Class: %.*s/%.*s", + name_len, res_name, + class_len, res_class); putchar ('\n'); } } + +done: + if (client_machine) + free(client_machine); + if (command) + free(command); + if (cs->verbose) { + if (name) + free(name); + if (icon_name) + free(icon_name); + if (wm_class) + free(wm_class); + } + free(cs); } static void -print_text_field(Display *dpy, char *s, XTextProperty *tp) +print_client_properties(xcb_connection_t *dpy, xcb_window_t w, int verbose, int maxcmdlen) { - if (tp->encoding == None || tp->format == 0) { + client_state *cs = malloc(sizeof(*cs)); + if (!cs) + return; /* TODO: print OOM message */ + + cs->c = dpy; + cs->w = w; + cs->verbose = verbose; + cs->maxcmdlen = maxcmdlen; + + /* + * get the WM_CLIENT_MACHINE and WM_COMMAND list of strings + */ + cs->client_machine = xcb_get_property(dpy, 0, w, + WM_CLIENT_MACHINE, XCB_GET_PROPERTY_TYPE_ANY, + 0, 1000000L); + cs->command = xcb_get_property(dpy, 0, w, + WM_COMMAND, XCB_GET_PROPERTY_TYPE_ANY, + 0, 1000000L); + + if (verbose) { + cs->name = xcb_get_property(dpy, 0, w, + WM_NAME, XCB_GET_PROPERTY_TYPE_ANY, + 0, 1000000L); + cs->icon_name = xcb_get_property(dpy, 0, w, + WM_ICON_NAME, XCB_GET_PROPERTY_TYPE_ANY, + 0, 1000000L); + cs->wm_class = xcb_get_property(dpy, 0, w, + WM_CLASS, STRING, + 0, 1000000L); + } + + enqueue(show_client_properties, cs); +} + +static void +print_text_field(xcb_connection_t *dpy, char *s, xcb_get_property_reply_t *tp) +{ + if (tp->type == XCB_NONE || tp->format == 0) { /* Or XCB_ATOM_NONE after libxcb 1.5 */ printf ("''"); return; } if (s) printf ("%s", s); - if (tp->encoding == XA_STRING && tp->format == 8) { - printf ("%s", tp->value ? (char *) tp->value : Nil); + if (tp->type == STRING && tp->format == 8) { + printf ("%.*s", (int)tp->value_len, (char *)xcb_get_property_value(tp)); } else { - unknown (dpy, tp->encoding, tp->format); + unknown (dpy, tp->type, tp->format); } if (s) putchar ('\n'); } @@ -303,18 +625,22 @@ print_quoted_word(char *s, } static void -unknown(Display *dpy, Atom actual_type, int actual_format) +unknown(xcb_connection_t *dpy, xcb_atom_t actual_type, int actual_format) { - char *s; - printf ("", actual_type, actual_format); } - -- cgit v1.2.3