http://ftp.ussg.iu.edu/linux/slackware/slackware-9.0/source/xap/xscreensaver/xscreens...
[xscreensaver] / hacks / fluidballs.c
1 /* fluidballs, Copyright (c) 2000 by Peter Birtles <peter@bqdesign.com.au>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or
9  * implied warranty.
10  *
11  * Ported to X11 and xscreensaver by jwz, 27-Feb-2002.
12  *
13  * http://astronomy.swin.edu.au/~pbourke/modelling/fluid/
14  *
15  * Some physics improvements by Steven Barker <steve@blckknght.org>
16  */
17
18 /* Future ideas:
19  * Specifying a distribution in the ball sizes (with a gamma curve, possibly).
20  * Brownian motion, for that extra touch of realism.
21  *
22  * It would be nice to detect when there are more balls than fit in
23  * the window, and scale the number of balls back.  Useful for the
24  * xscreensaver-demo preview, which is often too tight by default.
25  */
26
27 #include <math.h>
28 #include "screenhack.h"
29 #include <stdio.h>
30
31 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
32 #include "xdbe.h"
33 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
34
35 typedef struct {
36   Display *dpy;
37   Window window;
38   XWindowAttributes xgwa;
39
40   Pixmap b, ba; /* double-buffer to reduce flicker */
41 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
42   XdbeBackBuffer backb;
43 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
44
45   GC draw_gc;           /* most of the balls */
46   GC draw_gc2;          /* the ball being dragged with the mouse */
47   GC erase_gc;
48   XColor fg;
49   XColor fg2;
50
51   int count;            /* number of balls */
52   float xmin, ymin;     /* rectangle of window, relative to root */
53   float xmax, ymax;
54
55   int mouse_ball;       /* index of ball being dragged, or 0 if none. */
56
57   float tc;             /* time constant (time-warp multiplier) */
58   float accx;           /* horizontal acceleration (wind) */
59   float accy;           /* vertical acceleration (gravity) */
60
61   float *vx,  *vy;      /* current ball velocities */
62   float *px,  *py;      /* current ball positions */
63   float *opx, *opy;     /* previous ball positions */
64   float *r;             /* ball radiuses */
65
66   float *m;             /* ball mass, precalculated */
67   float e;              /* coeficient of elasticity */
68   float max_radius;     /* largest radius of any ball */
69
70   Bool random_sizes_p;  /* Whether balls should be various sizes up to max. */
71   Bool shake_p;         /* Whether to mess with gravity when things settle. */
72   Bool dbuf;            /* Whether we're using double buffering. */
73   Bool dbeclear_p;      /* ? */
74   float shake_threshold;
75   int time_since_shake;
76
77   Bool fps_p;           /* Whether to draw some text at the bottom. */
78   GC font_gc;
79   int font_height;
80   int font_baseline;
81   int frame_count;
82   int collision_count;
83   char fps_str[1024];
84   
85 } b_state;
86
87
88 /* Draws the frames per second string */
89 static void 
90 draw_fps_string (b_state *state)
91 {  
92   XFillRectangle (state->dpy, state->b, state->erase_gc,
93                   0, state->xgwa.height - state->font_height,
94                   state->xgwa.width, state->font_height);
95   XDrawImageString (state->dpy, state->b, state->font_gc,
96                     0, state->xgwa.height - state->font_baseline,
97                     state->fps_str, strlen(state->fps_str));
98 }
99
100 /* Finds the origin of the window relative to the root window, by
101    walking up the window tree until it reaches the top.
102  */
103 static void
104 window_origin (Display *dpy, Window window, int *x, int *y)
105 {
106   Window root, parent, *kids;
107   unsigned int nkids;
108   XWindowAttributes xgwa;
109   int wx, wy;
110   XGetWindowAttributes (dpy, window, &xgwa);
111
112   wx = xgwa.x;
113   wy = xgwa.y;
114
115   kids = 0;
116   *x = 0;
117   *y = 0;
118
119   if (XQueryTree (dpy, window, &root, &parent, &kids, &nkids))
120     {
121       if (parent && parent != root)
122         {
123           int px, py;
124           window_origin (dpy, parent, &px, &py);
125           wx += px;
126           wy += py;
127         }
128     }
129   if (kids) XFree (kids);
130   *x = wx;
131   *y = wy;
132 }
133
134
135 /* Queries the window position to see if the window has moved or resized.
136    We poll this instead of waiting for ConfigureNotify events, because
137    when the window manager moves the window, only one ConfigureNotify
138    comes in: at the end of the motion.  If we poll, we can react to the
139    new position while the window is still being moved.  (Assuming the WM
140    does OpaqueMove, of course.)
141  */
142 static void
143 check_window_moved (b_state *state)
144 {
145   float oxmin = state->xmin;
146   float oxmax = state->xmax;
147   float oymin = state->ymin;
148   float oymax = state->ymax;
149   int wx, wy;
150   XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
151   window_origin (state->dpy, state->window, &wx, &wy);
152   state->xmin = wx;
153   state->ymin = wy;
154   state->xmax = state->xmin + state->xgwa.width;
155   state->ymax = state->ymin + state->xgwa.height - state->font_height;
156
157   if (state->dbuf && (state->ba))
158     {
159       if (oxmax != state->xmax || oymax != state->ymax)
160         {
161           XFreePixmap (state->dpy, state->ba);
162           state->ba = XCreatePixmap (state->dpy, state->window, 
163                                      state->xgwa.width, state->xgwa.height,
164                                      state->xgwa.depth);
165           XFillRectangle (state->dpy, state->ba, state->erase_gc, 0, 0, 
166                           state->xgwa.width, state->xgwa.height);
167           state->b = state->ba;
168         }
169     }
170   else 
171     {
172       /* Only need to erase the window if the origin moved */
173       if (oxmin != state->xmin || oymin != state->ymin)
174         XClearWindow (state->dpy, state->window);
175       else if (state->fps_p && oymax != state->ymax)
176         XFillRectangle (state->dpy, state->b, state->erase_gc,
177                         0, state->xgwa.height - state->font_height,
178                         state->xgwa.width, state->font_height);
179     }
180 }
181
182
183 /* Returns the position of the mouse relative to the root window.
184  */
185 static void
186 query_mouse (b_state *state, int *x, int *y)
187 {
188   Window root1, child1;
189   int mouse_x, mouse_y, root_x, root_y;
190   unsigned int mask;
191   if (XQueryPointer (state->dpy, state->window, &root1, &child1,
192                      &root_x, &root_y, &mouse_x, &mouse_y, &mask))
193     {
194       *x = root_x;
195       *y = root_y;
196     }
197   else
198     {
199       *x = -9999;
200       *y = -9999;
201     }
202 }
203
204 /* Re-pick the colors of the balls, and the mouse-ball.
205  */
206 static void
207 recolor (b_state *state)
208 {
209   if (state->fg.flags)
210     XFreeColors (state->dpy, state->xgwa.colormap, &state->fg.pixel, 1, 0);
211   if (state->fg2.flags)
212     XFreeColors (state->dpy, state->xgwa.colormap, &state->fg2.pixel, 1, 0);
213
214   state->fg.flags  = DoRed|DoGreen|DoBlue;
215   state->fg.red    = 0x8888 + (random() % 0x8888);
216   state->fg.green  = 0x8888 + (random() % 0x8888);
217   state->fg.blue   = 0x8888 + (random() % 0x8888);
218
219   state->fg2.flags = DoRed|DoGreen|DoBlue;
220   state->fg2.red   = 0x8888 + (random() % 0x8888);
221   state->fg2.green = 0x8888 + (random() % 0x8888);
222   state->fg2.blue  = 0x8888 + (random() % 0x8888);
223
224   if (XAllocColor (state->dpy, state->xgwa.colormap, &state->fg))
225     XSetForeground (state->dpy, state->draw_gc,  state->fg.pixel);
226
227   if (XAllocColor (state->dpy, state->xgwa.colormap, &state->fg2))
228     XSetForeground (state->dpy, state->draw_gc2, state->fg2.pixel);
229 }
230
231 /* Initialize the state structure and various X data.
232  */
233 static b_state *
234 init_balls (Display *dpy, Window window)
235 {
236   int i;
237   float extx, exty;
238   b_state *state = (b_state *) calloc (1, sizeof(*state));
239   XGCValues gcv;
240
241   state->dpy = dpy;
242
243   state->window = window;
244
245   check_window_moved (state);
246
247   state->dbuf = get_boolean_resource ("doubleBuffer", "Boolean");
248   state->dbeclear_p = get_boolean_resource ("useDBEClear", "Boolean");
249
250   if (state->dbuf)
251     {
252 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
253       if (state->dbeclear_p)
254         state->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
255       else
256         state->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
257       state->backb = state->b;
258 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
259
260       if (!state->b)
261         {
262           state->ba = XCreatePixmap (state->dpy, state->window, 
263                                      state->xgwa.width, state->xgwa.height,
264                                      state->xgwa.depth);
265           state->b = state->ba;
266         }
267     }
268   else
269     {
270       state->b = state->window;
271     }
272
273   /* Select ButtonRelease events on the external window, if no other app has
274      already selected it (only one app can select it at a time: BadAccess. */
275   if (! (state->xgwa.all_event_masks & ButtonReleaseMask))
276     XSelectInput (state->dpy, state->window,
277                   state->xgwa.your_event_mask | ButtonReleaseMask);
278
279   gcv.foreground = get_pixel_resource("foreground", "Foreground",
280                                       state->dpy, state->xgwa.colormap);
281   gcv.background = get_pixel_resource("background", "Background",
282                                       state->dpy, state->xgwa.colormap);
283   state->draw_gc = XCreateGC (state->dpy, state->b,
284                               GCForeground|GCBackground, &gcv);
285
286   gcv.foreground = get_pixel_resource("mouseForeground", "MouseForeground",
287                                       state->dpy, state->xgwa.colormap);
288   state->draw_gc2 = XCreateGC (state->dpy, state->b,
289                                GCForeground|GCBackground, &gcv);
290
291   gcv.foreground = gcv.background;
292   state->erase_gc = XCreateGC (state->dpy, state->b,
293                                GCForeground|GCBackground, &gcv);
294
295
296   if (state->ba) 
297     XFillRectangle (state->dpy, state->ba, state->erase_gc, 0, 0, 
298                     state->xgwa.width, state->xgwa.height);
299
300   recolor (state);
301
302   extx = state->xmax - state->xmin;
303   exty = state->ymax - state->ymin;
304
305   state->count = get_integer_resource ("count", "Count");
306   if (state->count < 1) state->count = 20;
307
308   state->max_radius = get_float_resource ("size", "Size") / 2;
309   if (state->max_radius < 1.0) state->max_radius = 1.0;
310
311   state->random_sizes_p = get_boolean_resource ("random", "Random");
312
313   state->accx = get_float_resource ("wind", "Wind");
314   if (state->accx < -1.0 || state->accx > 1.0) state->accx = 0;
315
316   state->accy = get_float_resource ("gravity", "Gravity");
317   if (state->accy < -1.0 || state->accy > 1.0) state->accy = 0.01;
318
319   state->e = get_float_resource ("elasticity", "Elacitcity");
320   if (state->e < 0.2 || state->e > 1.0) state->e = 0.97;
321
322   state->tc = get_float_resource ("timeScale", "TimeScale");
323   if (state->tc <= 0 || state->tc > 10) state->tc = 1.0;
324
325   state->shake_p = get_boolean_resource ("shake", "Shake");
326   state->shake_threshold = get_float_resource ("shakeThreshold",
327                                                "ShakeThreshold");
328
329   state->fps_p = get_boolean_resource ("doFPS", "DoFPS");
330   if (state->fps_p)
331     {
332       XFontStruct *font;
333       char *fontname = get_string_resource ("font", "Font");
334       const char *def_font = "fixed";
335       if (!fontname || !*fontname) fontname = (char *)def_font;
336       font = XLoadQueryFont (dpy, fontname);
337       if (!font) font = XLoadQueryFont (dpy, def_font);
338       if (!font) exit(-1);
339       gcv.font = font->fid;
340       gcv.foreground = get_pixel_resource("textColor", "Foreground",
341                                           state->dpy, state->xgwa.colormap);
342       state->font_gc = XCreateGC(dpy, state->b,
343                                  GCFont|GCForeground|GCBackground, &gcv);
344       state->font_height = font->ascent + font->descent;
345       state->font_baseline = font->descent;
346     }
347
348   state->m   = (float *) malloc (sizeof (*state->m)   * (state->count + 1));
349   state->r   = (float *) malloc (sizeof (*state->r)   * (state->count + 1));
350   state->vx  = (float *) malloc (sizeof (*state->vx)  * (state->count + 1));
351   state->vy  = (float *) malloc (sizeof (*state->vy)  * (state->count + 1));
352   state->px  = (float *) malloc (sizeof (*state->px)  * (state->count + 1));
353   state->py  = (float *) malloc (sizeof (*state->py)  * (state->count + 1));
354   state->opx = (float *) malloc (sizeof (*state->opx) * (state->count + 1));
355   state->opy = (float *) malloc (sizeof (*state->opy) * (state->count + 1));
356
357   for (i=1; i<=state->count; i++)
358     {
359       state->px[i] = frand(extx) + state->xmin;
360       state->py[i] = frand(exty) + state->ymin;
361       state->vx[i] = frand(0.2) - 0.1;
362       state->vy[i] = frand(0.2) - 0.1;
363
364       state->r[i] = (state->random_sizes_p
365                      ? ((0.2 + frand(0.8)) * state->max_radius)
366                      : state->max_radius);
367       /*state->r[i] = pow(frand(1.0), state->sizegamma) * state->max_radius;*/
368
369       /* state->m[i] = pow(state->r[i],2) * M_PI; */
370       state->m[i] = pow(state->r[i],3) * M_PI * 1.3333;
371     }
372
373   return state;
374 }
375
376
377 /* Messes with gravity: permute "down" to be in a random direction.
378  */
379 static void
380 shake (b_state *state)
381 {
382   float a = state->accx;
383   float b = state->accy;
384   int i = random() % 4;
385
386   switch (i)
387     {
388     case 0:
389       state->accx = a;
390       state->accy = b;
391       break;
392     case 1:
393       state->accx = -a;
394       state->accy = -b;
395       break;
396     case 2:
397       state->accx = b;
398       state->accy = a;
399       break;
400     case 3:
401       state->accx = -b;
402       state->accy = -a;
403       break;
404     default:
405       abort();
406       break;
407     }
408
409   state->time_since_shake = 0;
410   recolor (state);
411 }
412
413
414 /* Look at the current time, and update state->time_since_shake.
415    Draw the FPS display if desired.
416  */
417 static void
418 check_wall_clock (b_state *state, float max_d)
419 {
420   static int tick = 0;
421   state->frame_count++;
422   
423   if (tick++ > 20)  /* don't call gettimeofday() too often -- it's slow. */
424     {
425       struct timeval now;
426       static struct timeval last = {0, };
427 # ifdef GETTIMEOFDAY_TWO_ARGS
428       struct timezone tzp;
429       gettimeofday(&now, &tzp);
430 # else
431       gettimeofday(&now);
432 # endif
433
434       if (last.tv_sec == 0)
435         last = now;
436
437       tick = 0;
438       if (now.tv_sec == last.tv_sec)
439         return;
440
441       state->time_since_shake += (now.tv_sec - last.tv_sec);
442
443       if (state->fps_p) 
444         {
445           float elapsed = ((now.tv_sec  + (now.tv_usec  / 1000000.0)) -
446                            (last.tv_sec + (last.tv_usec / 1000000.0)));
447           float fps = state->frame_count / elapsed;
448           float cps = state->collision_count / elapsed;
449           
450           sprintf (state->fps_str, 
451                    " FPS: %.2f  Collisions: %.3f/frame  Max motion: %.3f",
452                    fps, cps/fps, max_d);
453           
454           draw_fps_string(state);
455         }
456
457       state->frame_count = 0;
458       state->collision_count = 0;
459       last = now;
460     }
461 }
462
463 /* Erases the balls at their previous positions, and draws the new ones.
464  */
465 static void
466 repaint_balls (b_state *state)
467 {
468   int a;
469   int x1a, x2a, y1a, y2a;
470   int x1b, x2b, y1b, y2b;
471   float max_d = 0;
472
473   for (a=1; a <= state->count; a++)
474     {
475       GC gc;
476       x1a = (state->opx[a] - state->r[a] - state->xmin);
477       y1a = (state->opy[a] - state->r[a] - state->ymin);
478       x2a = (state->opx[a] + state->r[a] - state->xmin);
479       y2a = (state->opy[a] + state->r[a] - state->ymin);
480
481       x1b = (state->px[a] - state->r[a] - state->xmin);
482       y1b = (state->py[a] - state->r[a] - state->ymin);
483       x2b = (state->px[a] + state->r[a] - state->xmin);
484       y2b = (state->py[a] + state->r[a] - state->ymin);
485
486       if (!state->dbeclear_p ||
487 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
488           !state->backb
489 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
490           )
491         {
492 /*        if (x1a != x1b || y1a != y1b)   -- leaves turds if we optimize this */
493             {
494               gc = state->erase_gc;
495               XFillArc (state->dpy, state->b, gc,
496                         x1a, y1a, x2a-x1a, y2a-y1a,
497                         0, 360*64);
498             }
499         }
500       if (state->mouse_ball == a)
501         gc = state->draw_gc2;
502       else
503         gc = state->draw_gc;
504
505       XFillArc (state->dpy, state->b, gc,
506                 x1b, y1b, x2b-x1b, y2b-y1b,
507                 0, 360*64);
508
509       if (state->shake_p)
510         {
511           /* distance this ball moved this frame */
512           float d = ((state->px[a] - state->opx[a]) *
513                      (state->px[a] - state->opx[a]) +
514                      (state->py[a] - state->opy[a]) *
515                      (state->py[a] - state->opy[a]));
516           if (d > max_d) max_d = d;
517         }
518
519       state->opx[a] = state->px[a];
520       state->opy[a] = state->py[a];
521     }
522
523   if (state->fps_p && state->dbeclear_p) 
524     draw_fps_string(state);
525
526 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
527   if (state->backb)
528     {
529       XdbeSwapInfo info[1];
530       info[0].swap_window = state->window;
531       info[0].swap_action = (state->dbeclear_p ? XdbeBackground : XdbeUndefined);
532       XdbeSwapBuffers (state->dpy, info, 1);
533     }
534   else
535 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
536   if (state->dbuf)
537     {
538       XCopyArea (state->dpy, state->b, state->window, state->erase_gc,
539                  0, 0, state->xgwa.width, state->xgwa.height, 0, 0);
540     }
541
542   if (state->shake_p && state->time_since_shake > 5)
543     {
544       max_d /= state->max_radius;
545       if (max_d < state->shake_threshold ||  /* when its stable */
546           state->time_since_shake > 30)      /* or when 30 secs has passed */
547         {
548           shake (state);
549         }
550     }
551
552   check_wall_clock (state, max_d);
553 }
554
555
556 /* Implements the laws of physics: move balls to their new positions.
557  */
558 static void
559 update_balls (b_state *state)
560 {
561   int a, b;
562   float d, vxa, vya, vxb, vyb, dd, cdx, cdy;
563   float ma, mb, vca, vcb, dva, dvb;
564   float dee2;
565
566   check_window_moved (state);
567
568   /* If we're currently tracking the mouse, update that ball first.
569    */
570   if (state->mouse_ball != 0)
571     {
572       int mouse_x, mouse_y;
573       query_mouse (state, &mouse_x, &mouse_y);
574       state->px[state->mouse_ball] = mouse_x;
575       state->py[state->mouse_ball] = mouse_y;
576       state->vx[state->mouse_ball] =
577         (0.1 *
578          (state->px[state->mouse_ball] - state->opx[state->mouse_ball]) *
579          state->tc);
580       state->vy[state->mouse_ball] =
581         (0.1 *
582          (state->py[state->mouse_ball] - state->opy[state->mouse_ball]) *
583          state->tc);
584     }
585
586   /* For each ball, compute the influence of every other ball. */
587   for (a=1; a <= state->count -  1; a++)
588     for (b=a + 1; b <= state->count; b++)
589       {
590          d = ((state->px[a] - state->px[b]) *
591               (state->px[a] - state->px[b]) +
592               (state->py[a] - state->py[b]) *
593               (state->py[a] - state->py[b]));
594          dee2 = (state->r[a] + state->r[b]) *
595                 (state->r[a] + state->r[b]);
596          if (d < dee2)
597          {
598             state->collision_count++;
599             d = sqrt(d);
600             dd = state->r[a] + state->r[b] - d;
601
602             cdx = (state->px[b] - state->px[a]) / d;
603             cdy = (state->py[b] - state->py[a]) / d;
604
605             /* Move each ball apart from the other by half the
606              * 'collision' distance.
607              */
608             state->px[a] -= 0.5 * dd * cdx;
609             state->py[a] -= 0.5 * dd * cdy;
610             state->px[b] += 0.5 * dd * cdx;
611             state->py[b] += 0.5 * dd * cdy;
612
613             ma = state->m[a];
614             mb = state->m[b];
615
616             vxa = state->vx[a];
617             vya = state->vy[a];
618             vxb = state->vx[b];
619             vyb = state->vy[b];
620
621             vca = vxa * cdx + vya * cdy; /* the component of each velocity */
622             vcb = vxb * cdx + vyb * cdy; /* along the axis of the collision */
623
624             /* elastic collison */
625             dva = (vca * (ma - mb) + vcb * 2 * mb) / (ma + mb) - vca;
626             dvb = (vcb * (mb - ma) + vca * 2 * ma) / (ma + mb) - vcb;
627
628             dva *= state->e; /* some energy lost to inelasticity */
629             dvb *= state->e;
630
631 #if 0
632             dva += (frand (50) - 25) / ma;   /* q: why are elves so chaotic? */
633             dvb += (frand (50) - 25) / mb;   /* a: brownian motion. */
634 #endif
635
636             vxa += dva * cdx;
637             vya += dva * cdy;
638             vxb += dvb * cdx;
639             vyb += dvb * cdy;
640
641             state->vx[a] = vxa;
642             state->vy[a] = vya;
643             state->vx[b] = vxb;
644             state->vy[b] = vyb;
645          }
646       }
647
648    /* Force all balls to be on screen.
649     */
650   for (a=1; a <= state->count; a++)
651     {
652       if (state->px[a] <= (state->xmin + state->r[a]))
653         {
654           state->px[a] = state->xmin + state->r[a];
655           state->vx[a] = -state->vx[a] * state->e;
656         }
657       if (state->px[a] >= (state->xmax - state->r[a]))
658         {
659           state->px[a] = state->xmax - state->r[a];
660           state->vx[a] = -state->vx[a] * state->e;
661         }
662       if (state->py[a] <= (state->ymin + state->r[a]))
663         {
664           state->py[a] = state->ymin + state->r[a];
665           state->vy[a] = -state->vy[a] * state->e;
666         }
667       if (state->py[a] >= (state->ymax - state->r[a]))
668         {
669           state->py[a] = state->ymax - state->r[a];
670           state->vy[a] = -state->vy[a] * state->e;
671         }
672     }
673
674   /* Apply gravity to all balls.
675    */
676   for (a=1; a <= state->count; a++)
677     if (a != state->mouse_ball)
678       {
679         state->vx[a] += state->accx * state->tc;
680         state->vy[a] += state->accy * state->tc;
681         state->px[a] += state->vx[a] * state->tc;
682         state->py[a] += state->vy[a] * state->tc;
683       }
684 }
685
686
687 /* Handle X events, specifically, allow a ball to be picked up with the mouse.
688  */
689 static void
690 handle_events (b_state *state)
691 {
692   XSync (state->dpy, False);
693   while (XPending (state->dpy))
694     {
695       XEvent event;
696       XNextEvent (state->dpy, &event);
697       if (event.xany.type == ButtonPress)
698         {
699           int i;
700           float fmx = event.xbutton.x_root;
701           float fmy = event.xbutton.y_root;
702           if (state->mouse_ball != 0)  /* second down-click?  drop the ball. */
703             {
704               state->mouse_ball = 0;
705               return;
706             }
707           else
708             for (i=1; i <= state->count; i++)
709               {
710                 float d = ((state->px[i] - fmx) * (state->px[i] - fmx) +
711                            (state->py[i] - fmy) * (state->py[i] - fmy));
712                 float r = state->r[i];
713                 if (d < r*r)
714                   {
715                     state->mouse_ball = i;
716                     return;
717                   }
718               }
719         }
720       else if (event.xany.type == ButtonRelease)   /* drop the ball */
721         {
722           state->mouse_ball = 0;
723           return;
724         }
725       else if (event.xany.type == ConfigureNotify)
726         {
727           /* This is redundant, since we poll this every iteration. */
728           check_window_moved (state);
729         }
730
731       screenhack_handle_event (state->dpy, &event);
732     }
733 }
734
735 \f
736 char *progclass = "FluidBalls";
737
738 char *defaults [] = {
739   ".background:         black",
740   ".textColor:          yellow",
741   ".font:               -*-helvetica-*-r-*-*-*-180-*-*-p-*-*-*",
742   "*delay:              10000",
743   "*count:              300",
744   "*size:               25",
745   "*random:             True",
746   "*gravity:            0.01",
747   "*wind:               0.00",
748   "*elasticity:         0.97",
749   "*timeScale:          1.0",
750   "*doFPS:              False",
751   "*shake:              True",
752   "*shakeThreshold:     0.015",
753   "*doubleBuffer:       True",
754 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
755   "*useDBE:             True",
756   "*useDBEClear:        True",
757 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
758   0
759 };
760
761 XrmOptionDescRec options [] = {
762   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
763   { "-count",           ".count",       XrmoptionSepArg, 0 },
764   { "-size",            ".size",        XrmoptionSepArg, 0 },
765   { "-count",           ".count",       XrmoptionSepArg, 0 },
766   { "-gravity",         ".gravity",     XrmoptionSepArg, 0 },
767   { "-wind",            ".wind",        XrmoptionSepArg, 0 },
768   { "-elasticity",      ".elasticity",  XrmoptionSepArg, 0 },
769   { "-fps",             ".doFPS",       XrmoptionNoArg, "True" },
770   { "-no-fps",          ".doFPS",       XrmoptionNoArg, "False" },
771   { "-shake",           ".shake",       XrmoptionNoArg, "True" },
772   { "-no-shake",        ".shake",       XrmoptionNoArg, "False" },
773   { "-random",          ".random",      XrmoptionNoArg, "True" },
774   { "-nonrandom",       ".random",      XrmoptionNoArg, "False" },
775   { "-db",              ".doubleBuffer", XrmoptionNoArg,  "True" },
776   { "-no-db",           ".doubleBuffer", XrmoptionNoArg,  "False" },
777   { 0, 0, 0, 0 }
778 };
779
780 void
781 screenhack (Display *dpy, Window window)
782 {
783   b_state *state = init_balls(dpy, window);
784   int delay = get_integer_resource ("delay", "Integer");
785
786   while (1)
787     {
788       repaint_balls(state);
789       update_balls(state);
790
791       XSync (dpy, False);
792       handle_events (state);
793       if (delay) usleep (delay);
794     }
795 }