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