Home | History | Annotate | Download | only in xbonkers
      1 /*
      2  * Add more stress to X server by moving, resizing and activating windows
      3  * Author: Darrick Wong <djwong (at) us.ibm.com>
      4  */
      5 
      6 /*
      7  * Copyright (C) 2003-2006 IBM
      8  *
      9  * This program is free software; you can redistribute it and/or
     10  * modify it under the terms of the GNU General Public License as
     11  * published by the Free Software Foundation; either version 2 of the
     12  * License, or (at your option) any later version.
     13  *
     14  * This program is distributed in the hope that it will be useful, but
     15  * WITHOUT ANY WARRANTY; without even the implied warranty of
     16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     17  * General Public License for more details.
     18  *
     19  * You should have received a copy of the GNU General Public License
     20  * along with this program; if not, write to the Free Software
     21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
     22  * 02111-1307, USA.
     23  */
     24 
     25 #include <stdio.h>
     26 #include <sys/types.h>
     27 #include <sys/stat.h>
     28 #include <fcntl.h>
     29 #include <unistd.h>
     30 #include <stdlib.h>
     31 #include <X11/Xlib.h>
     32 #include <X11/Xatom.h>
     33 #include <string.h>
     34 #include <stdint.h>
     35 #include <limits.h>
     36 
     37 static int enable_fullscreen = 0;
     38 
     39 #define MAX_PROPERTY_VALUE_LEN	4096
     40 #define _NET_WM_STATE_TOGGLE	2
     41 #define SLIDE_THRESHOLD 	32
     42 
     43 /* We assume that the workspace number will either be -1 or some
     44  * huge number for "On All Workspaces" windows.  Presumably there
     45  * aren't 1,000,000 workspaces, so that should be a safe number.
     46  */
     47 #define DESKTOP_MAX		1000000
     48 
     49 #define ACTION_MOVE_WINDOW		0
     50 #define ACTION_ACTIVATE_WINDOW		1
     51 #define ACTION_MAXIMIZE_WINDOW		2
     52 #define ACTION_FULLSCREEN_WINDOW	3
     53 #define ACTION_HIDDEN_WINDOW		4
     54 #define ACTION_SLIDE_WINDOW_0		5
     55 #define ACTION_SLIDE_WINDOW_1		6
     56 #define ACTION_SLIDE_WINDOW_2		7
     57 #define ACTION_SLIDE_WINDOW_3		8
     58 #define ACTION_SLIDE_WINDOW_4		9
     59 #define ACTION_SLIDE_WINDOW_5		10
     60 #define ACTION_MIN		ACTION_MOVE_WINDOW
     61 #define ACTION_MAX		ACTION_SLIDE_WINDOW_5
     62 
     63 /* The goal of this program:
     64  * 0. Seed random number generator
     65  * 1. Grab the list of windows and the desktop size.
     66  * 2. Filter out the panel/desktop/whatever.  We're going to make
     67  *    a cheesy assumption that a window on desktop -1 should be left
     68  *    alone.  (Actually, the -1 denotes "all desktops")
     69  * 3. For each window:
     70  *    a. Figure out what we're going to do--activate, move/resize,
     71  *       or maximize it.
     72  *    b. If we're going to move/resize, grab 4 random numbers.
     73  *    c. Actually perform the action.
     74  * 4. Every so often, jump back to (2) in case there are new windows.
     75  *    Maybe every 10,000 moves or so.
     76  *
     77  * Note that you do NOT want to run this on any X session you care about.
     78  * It shouldn't take down X, but YMMV and in any case mad window resizing
     79  * makes it hard to get work done.
     80  */
     81 static int seed_random(void);
     82 static int get_desktop_size(Display * disp, unsigned long *w, unsigned long *h);
     83 static char *get_property(Display * disp, Window win, Atom xa_prop_type,
     84 			  char *prop_name, unsigned long *size,
     85 			  unsigned long *items);
     86 static void go_bonkers(Display * disp, unsigned long iterations,
     87 		       unsigned long sleep);
     88 static Window *get_interesting_windows(Display * disp,
     89 				       unsigned long *num_windows);
     90 static Window *get_client_list(Display * disp, unsigned long *size,
     91 			       unsigned long *items);
     92 static long get_randnum(long min, long max);
     93 static int send_client_msg(Display * disp, Window win, char *msg,
     94 			   unsigned long data0, unsigned long data1,
     95 			   unsigned long data2, unsigned long data3,
     96 			   unsigned long data4);
     97 static int activate_window(Display * disp, Window * win);
     98 static int wm_supports(Display * disp, const char *prop);
     99 static void move_window(Display * disp, Window * win, unsigned long desk_w,
    100 			unsigned long desk_h);
    101 static int toggle_property(Display * disp, Window * win, const char *property);
    102 static inline unsigned long clamp_value(unsigned long value,
    103 					unsigned long min, unsigned long max);
    104 static int ignore_xlib_error(Display * disp, XErrorEvent * xee);
    105 
    106 /* Actual functions begin here. */
    107 
    108 static int seed_random(void)
    109 {
    110 	int fp;
    111 	long seed;
    112 
    113 	fp = open("/dev/urandom", O_RDONLY);
    114 	if (fp < 0) {
    115 		perror("/dev/urandom");
    116 		return 0;
    117 	}
    118 
    119 	if (read(fp, &seed, sizeof(seed)) != sizeof(seed)) {
    120 		perror("read random seed");
    121 		return 0;
    122 	}
    123 
    124 	close(fp);
    125 	srand(seed);
    126 
    127 	return 1;
    128 }
    129 
    130 static int get_desktop_size(Display * disp, unsigned long *w, unsigned long *h)
    131 {
    132 	*w = DisplayWidth(disp, 0);
    133 	*h = DisplayHeight(disp, 0);
    134 
    135 	return 1;
    136 }
    137 
    138 static char *get_property(Display * disp, Window win, Atom xa_prop_type,
    139 			  char *prop_name, unsigned long *size,
    140 			  unsigned long *items)
    141 {
    142 	Atom xa_prop_name;
    143 	Atom xa_ret_type;
    144 	int ret_format;
    145 	unsigned long ret_nitems;
    146 	unsigned long ret_bytes_after;
    147 	unsigned long tmp_size;
    148 	unsigned char *ret_prop;
    149 	char *ret;
    150 
    151 	xa_prop_name = XInternAtom(disp, prop_name, False);
    152 
    153 	if (XGetWindowProperty
    154 	    (disp, win, xa_prop_name, 0, MAX_PROPERTY_VALUE_LEN / 4, False,
    155 	     xa_prop_type, &xa_ret_type, &ret_format, &ret_nitems,
    156 	     &ret_bytes_after, &ret_prop) != Success) {
    157 		fprintf(stderr, "Cannot get %s property.\n", prop_name);
    158 		return NULL;
    159 	}
    160 
    161 	if (xa_ret_type != xa_prop_type) {
    162 		fprintf(stderr, "Invalid type of %s property.\n", prop_name);
    163 		XFree(ret_prop);
    164 		return NULL;
    165 	}
    166 
    167 	/* XXX: EVIL HACK to get around a bug when sizeof(Window) is 8 yet ret_format
    168 	 * is listed as 32bits and we're trying to get the client list.  Just double
    169 	 * ret_format and proceed. */
    170 	if (ret_format == 32 && strcmp(prop_name, "_NET_CLIENT_LIST") == 0 &&
    171 	    sizeof(Window) == 8) {
    172 		ret_format *= 2;
    173 	}
    174 
    175 	/* null terminate the result to make string handling easier */
    176 	tmp_size = (ret_format / 8) * ret_nitems;
    177 	ret = calloc(tmp_size + 1, 1);
    178 	if (!ret) {
    179 		perror("get_property malloc failed");
    180 		return NULL;
    181 	}
    182 	memcpy(ret, ret_prop, tmp_size);
    183 	ret[tmp_size] = '\0';
    184 
    185 	if (size) {
    186 		*size = ret_format / 8;
    187 	}
    188 	if (items) {
    189 		*items = ret_nitems;
    190 	}
    191 
    192 	XFree(ret_prop);
    193 	return ret;
    194 }
    195 
    196 static long get_randnum(long min, long max)
    197 {
    198 	return min + (long)((float)max * (rand() / (RAND_MAX + 1.0)));
    199 }
    200 
    201 static int wm_supports(Display * disp, const char *prop)
    202 {
    203 	Atom xa_prop = XInternAtom(disp, prop, False);
    204 	Atom *list;
    205 	unsigned long size, items;
    206 	int i;
    207 
    208 	if (!(list = (Atom *) get_property(disp, DefaultRootWindow(disp),
    209 					   XA_ATOM, "_NET_SUPPORTED", &size,
    210 					   &items))) {
    211 		fprintf(stderr, "Cannot get _NET_SUPPORTED property.\n");
    212 		return 0;
    213 	}
    214 
    215 	size *= items;
    216 
    217 	for (i = 0; i < size / sizeof(Atom); i++) {
    218 		if (list[i] == xa_prop) {
    219 			free(list);
    220 			return 1;
    221 		}
    222 	}
    223 
    224 	free(list);
    225 	return 0;
    226 }
    227 
    228 static inline unsigned long clamp_value(unsigned long value,
    229 					unsigned long min, unsigned long max)
    230 {
    231 	return (value < min ? min : (value > max ? max : value));
    232 }
    233 
    234 static int ignore_xlib_error(Display * disp, XErrorEvent * xee)
    235 {
    236 	char errbuf[256];
    237 
    238 	XGetErrorText(disp, xee->error_code, errbuf, 256);
    239 	fprintf(stderr,
    240 		"IGNORING Xlib error %d (%s) on request (%d.%d), sernum = %lu.\n",
    241 		xee->error_code, errbuf, xee->request_code, xee->minor_code,
    242 		xee->serial);
    243 	return 1;
    244 }
    245 
    246 static void slide_window(Display * disp, Window * win, unsigned long desk_w,
    247 			 unsigned long desk_h)
    248 {
    249 	unsigned long x, y;
    250 	unsigned long w, h;
    251 	XWindowAttributes moo;
    252 	Window junk;
    253 
    254 	if (XGetWindowAttributes(disp, *win, &moo) != 1) {
    255 		fprintf(stderr, "Cannot get attributes of window 0x%lx.\n",
    256 			*win);
    257 		return;
    258 	}
    259 
    260 	if (XTranslateCoordinates(disp, *win, moo.root,
    261 				  -moo.border_width, -moo.border_width, &moo.x,
    262 				  &moo.y, &junk) != 1) {
    263 		fprintf(stderr,
    264 			"Cannot translate coordinates of window 0x%lx.\n",
    265 			*win);
    266 		return;
    267 	}
    268 
    269 	x = moo.x + get_randnum(-SLIDE_THRESHOLD, SLIDE_THRESHOLD);
    270 	y = moo.y + get_randnum(-SLIDE_THRESHOLD, SLIDE_THRESHOLD);
    271 	w = moo.width + get_randnum(-SLIDE_THRESHOLD, SLIDE_THRESHOLD);
    272 	h = moo.height + get_randnum(-SLIDE_THRESHOLD, SLIDE_THRESHOLD);
    273 
    274 	x = clamp_value(x, 0, desk_w);
    275 	y = clamp_value(y, 0, desk_h);
    276 	w = clamp_value(w, 0, desk_w);
    277 	h = clamp_value(h, 0, desk_h);
    278 
    279 	if (wm_supports(disp, "_NET_MOVERESIZE_WINDOW")) {
    280 		send_client_msg(disp, *win, "_NET_MOVERESIZE_WINDOW",
    281 				0, x, y, w, h);
    282 	} else {
    283 		XMoveResizeWindow(disp, *win, x, y, w, h);
    284 	}
    285 }
    286 
    287 static void move_window(Display * disp, Window * win, unsigned long desk_w,
    288 			unsigned long desk_h)
    289 {
    290 	unsigned long x, y, w, h;
    291 
    292 	x = get_randnum(0, desk_w);
    293 	y = get_randnum(0, desk_h);
    294 	w = get_randnum(150, desk_w);
    295 	h = get_randnum(150, desk_h);
    296 
    297 	if (wm_supports(disp, "_NET_MOVERESIZE_WINDOW")) {
    298 		send_client_msg(disp, *win, "_NET_MOVERESIZE_WINDOW",
    299 				0, x, y, w, h);
    300 	} else {
    301 		XMoveResizeWindow(disp, *win, x, y, w, h);
    302 	}
    303 }
    304 
    305 static int toggle_property(Display * disp, Window * win, const char *property)
    306 {
    307 	Atom prop;
    308 
    309 	prop = XInternAtom(disp, property, False);
    310 	return send_client_msg(disp, *win, "_NET_WM_STATE",
    311 			       _NET_WM_STATE_TOGGLE, prop, 0, 0, 0);
    312 }
    313 
    314 static void go_bonkers(Display * disp, unsigned long iterations,
    315 		       unsigned long sleep)
    316 {
    317 	unsigned long desk_w, desk_h;
    318 	Window *windows, *window;
    319 	unsigned long windows_length = 0, i;
    320 
    321 	if (!get_desktop_size(disp, &desk_w, &desk_h)) {
    322 		fprintf(stderr, "WARNING: Assuming desktop to be 1024x768!\n");
    323 		desk_w = 1024;
    324 		desk_h = 768;
    325 	}
    326 	printf("Desktop is %lu by %lu.\n", desk_w, desk_h);
    327 
    328 	windows = get_interesting_windows(disp, &windows_length);
    329 	if (!windows) {
    330 		usleep(1000000);
    331 		return;
    332 	}
    333 	printf("There are %lu interesting windows.\n", windows_length);
    334 
    335 	/* Bump up the iteration count so that all windows get
    336 	 * some exercise. */
    337 	iterations += iterations % windows_length;
    338 
    339 	for (i = 0; i < iterations; i++) {
    340 		window = &windows[i % windows_length];
    341 		switch (get_randnum(ACTION_MIN, ACTION_MAX)) {
    342 		case ACTION_MOVE_WINDOW:
    343 			move_window(disp, window, desk_w, desk_h);
    344 			break;
    345 		case ACTION_ACTIVATE_WINDOW:
    346 			activate_window(disp, window);
    347 			break;
    348 		case ACTION_MAXIMIZE_WINDOW:
    349 			toggle_property(disp, window,
    350 					"_NET_WM_STATE_MAXIMIZED_VERT");
    351 			toggle_property(disp, window,
    352 					"_NET_WM_STATE_MAXIMIZED_HORZ");
    353 			break;
    354 		case ACTION_FULLSCREEN_WINDOW:
    355 			if (!enable_fullscreen)
    356 				break;
    357 			toggle_property(disp, window,
    358 					"_NET_WM_STATE_FULLSCREEN");
    359 			break;
    360 		case ACTION_HIDDEN_WINDOW:
    361 			toggle_property(disp, window, "_NET_WM_STATE_HIDDEN");
    362 			break;
    363 		case ACTION_SLIDE_WINDOW_0:
    364 		case ACTION_SLIDE_WINDOW_1:
    365 		case ACTION_SLIDE_WINDOW_2:
    366 		case ACTION_SLIDE_WINDOW_3:
    367 		case ACTION_SLIDE_WINDOW_4:
    368 		case ACTION_SLIDE_WINDOW_5:
    369 			slide_window(disp, window, desk_w, desk_h);
    370 			break;
    371 		}
    372 		usleep(sleep);
    373 	}
    374 
    375 	free(windows);
    376 }
    377 
    378 static int send_client_msg(Display * disp, Window win, char *msg,
    379 			   unsigned long data0, unsigned long data1,
    380 			   unsigned long data2, unsigned long data3,
    381 			   unsigned long data4)
    382 {
    383 	XEvent event;
    384 	long mask = SubstructureRedirectMask | SubstructureNotifyMask;
    385 
    386 	event.xclient.type = ClientMessage;
    387 	event.xclient.serial = 0;
    388 	event.xclient.send_event = True;
    389 	event.xclient.message_type = XInternAtom(disp, msg, False);
    390 	event.xclient.window = win;
    391 	event.xclient.format = 32;
    392 	event.xclient.data.l[0] = data0;
    393 	event.xclient.data.l[1] = data1;
    394 	event.xclient.data.l[2] = data2;
    395 	event.xclient.data.l[3] = data3;
    396 	event.xclient.data.l[4] = data4;
    397 
    398 	if (XSendEvent(disp, DefaultRootWindow(disp), False, mask, &event)) {
    399 		return 1;
    400 	} else {
    401 		fprintf(stderr, "Cannot send %s event.\n", msg);
    402 		return 0;
    403 	}
    404 }
    405 
    406 static int activate_window(Display * disp, Window * win)
    407 {
    408 	int ret;
    409 
    410 	ret = send_client_msg(disp, *win, "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0);
    411 	XMapRaised(disp, *win);
    412 
    413 	return ret;
    414 }
    415 
    416 static Window *get_client_list(Display * disp, unsigned long *size,
    417 			       unsigned long *items)
    418 {
    419 	void *res;
    420 
    421 	if ((res = (Window *) get_property(disp, DefaultRootWindow(disp),
    422 					   XA_WINDOW, "_NET_CLIENT_LIST", size,
    423 					   items)) == NULL) {
    424 		if ((res =
    425 		     (Window *) get_property(disp, DefaultRootWindow(disp),
    426 					     XA_CARDINAL, "_WIN_CLIENT_LIST",
    427 					     size, items)) == NULL) {
    428 			fprintf(stderr,
    429 				"Cannot get client list properties. \n"
    430 				"(_NET_CLIENT_LIST or _WIN_CLIENT_LIST)" "\n");
    431 			return NULL;
    432 		}
    433 	}
    434 
    435 	return (Window *) res;
    436 }
    437 
    438 static Window *get_interesting_windows(Display * disp,
    439 				       unsigned long *num_windows)
    440 {
    441 	Window *client_list, *ret, *tmp;
    442 	unsigned long client_list_size, client_list_items, i;
    443 	long *desktop;
    444 	unsigned long num_needed = 0;
    445 
    446 	if ((client_list = get_client_list(disp, &client_list_size,
    447 					   &client_list_items)) == NULL) {
    448 		return NULL;
    449 	}
    450 
    451 	/* Figure out how many Window structs we'll ultimately need. */
    452 	for (i = 0; i < client_list_items; i++) {
    453 		/* desktop ID */
    454 		if ((desktop = (long *)get_property(disp, client_list[i],
    455 						    XA_CARDINAL,
    456 						    "_NET_WM_DESKTOP", NULL,
    457 						    NULL)) == NULL) {
    458 			desktop =
    459 			    (long *)get_property(disp, client_list[i],
    460 						 XA_CARDINAL, "_WIN_WORKSPACE",
    461 						 NULL, NULL);
    462 		}
    463 
    464 		/* Ignore windows on unknown desktops */
    465 		if (desktop && *desktop >= 0 && *desktop < DESKTOP_MAX) {
    466 			num_needed++;
    467 			free(desktop);
    468 		}
    469 	}
    470 
    471 	ret = calloc(num_needed, sizeof(Window));
    472 	if (!ret) {
    473 		perror("get_interesting_window allocations");
    474 		free(client_list);
    475 		return NULL;
    476 	}
    477 	tmp = ret;
    478 
    479 	/* Now copy all that crud. */
    480 	for (i = 0; i < client_list_items; i++) {
    481 		/* desktop ID */
    482 		if ((desktop = (long *)get_property(disp, client_list[i],
    483 						    XA_CARDINAL,
    484 						    "_NET_WM_DESKTOP", NULL,
    485 						    NULL)) == NULL) {
    486 			desktop =
    487 			    (long *)get_property(disp, client_list[i],
    488 						 XA_CARDINAL, "_WIN_WORKSPACE",
    489 						 NULL, NULL);
    490 		}
    491 
    492 		if (desktop && *desktop >= 0 && *desktop < DESKTOP_MAX) {
    493 			memcpy(tmp, &client_list[i], sizeof(Window));
    494 			tmp++;
    495 			free(desktop);
    496 		}
    497 	}
    498 	free(client_list);
    499 
    500 	*num_windows = num_needed;
    501 	return ret;
    502 }
    503 
    504 int main(int argc, char *argv[])
    505 {
    506 	char *disp_string = NULL;
    507 	unsigned long iterations = 10000, rounds = -1, i;
    508 	unsigned long sleep = 100000;
    509 	int opt;
    510 	Display *disp;
    511 
    512 	while ((opt = getopt(argc, argv, "d:i:r:s:f")) != -1) {
    513 		switch (opt) {
    514 		case 'd':
    515 			disp_string = optarg;
    516 			break;
    517 		case 'i':
    518 			iterations = atoi(optarg);
    519 			break;
    520 		case 'r':
    521 			rounds = atoi(optarg);
    522 			break;
    523 		case 's':
    524 			sleep = atoi(optarg);
    525 			break;
    526 		case 'f':
    527 			enable_fullscreen = 1;
    528 			break;
    529 		default:
    530 			fprintf(stderr,
    531 				"Usage: %s [-d DISPLAY] [-i ITERATIONS] [-r ROUNDS] [-s SLEEP] [-f]\n\
    532 	DISPLAY is an X11 display string.\n\
    533 	ITERATIONS is the approximate number of windows to play with before generating a new window list.\n\
    534 	SLEEP is the amount of time (in usec) to sleep between window tweaks.\n\
    535 	-f enables fullscreen toggling.\n\
    536 	ROUNDS is the number of iterations to run, or -1 to run forever.\n",
    537 				argv[0]);
    538 			return 0;
    539 		}
    540 	}
    541 
    542 	if (!(disp = XOpenDisplay(disp_string))) {
    543 		fprintf(stderr, "Unable to connect to display '%s'.\n",
    544 			(disp_string !=
    545 			 NULL ? disp_string : getenv("DISPLAY")));
    546 		return 1;
    547 	}
    548 
    549 	seed_random();
    550 
    551 	XSetErrorHandler(&ignore_xlib_error);
    552 
    553 	for (i = 0; i < rounds || rounds == -1; i++) {
    554 		go_bonkers(disp, iterations, sleep);
    555 	}
    556 
    557 	printf("Enough of that; I'm done.\n");
    558 
    559 	XCloseDisplay(disp);
    560 
    561 	return 0;
    562 }
    563