1/*
2 * Add more stress to X server by moving, resizing and activating windows
3 * Author: Darrick Wong <djwong@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
37static 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 */
81static int seed_random(void);
82static int get_desktop_size(Display * disp, unsigned long *w, unsigned long *h);
83static char *get_property(Display * disp, Window win, Atom xa_prop_type,
84			  char *prop_name, unsigned long *size,
85			  unsigned long *items);
86static void go_bonkers(Display * disp, unsigned long iterations,
87		       unsigned long sleep);
88static Window *get_interesting_windows(Display * disp,
89				       unsigned long *num_windows);
90static Window *get_client_list(Display * disp, unsigned long *size,
91			       unsigned long *items);
92static long get_randnum(long min, long max);
93static 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);
97static int activate_window(Display * disp, Window * win);
98static int wm_supports(Display * disp, const char *prop);
99static void move_window(Display * disp, Window * win, unsigned long desk_w,
100			unsigned long desk_h);
101static int toggle_property(Display * disp, Window * win, const char *property);
102static inline unsigned long clamp_value(unsigned long value,
103					unsigned long min, unsigned long max);
104static int ignore_xlib_error(Display * disp, XErrorEvent * xee);
105
106/* Actual functions begin here. */
107
108static 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
130static 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
138static 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
196static long get_randnum(long min, long max)
197{
198	return min + (long)((float)max * (rand() / (RAND_MAX + 1.0)));
199}
200
201static 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
228static 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
234static 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
246static 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
287static 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
305static 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
314static 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
378static 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
406static 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
416static 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
438static 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
504int 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