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