http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.14.tar.gz
[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   memcpy (state->opx, state->px, sizeof (*state->opx) * (state->count + 1));
374   memcpy (state->opy, state->py, sizeof (*state->opx) * (state->count + 1));
375
376   return state;
377 }
378
379
380 /* Messes with gravity: permute "down" to be in a random direction.
381  */
382 static void
383 shake (b_state *state)
384 {
385   float a = state->accx;
386   float b = state->accy;
387   int i = random() % 4;
388
389   switch (i)
390     {
391     case 0:
392       state->accx = a;
393       state->accy = b;
394       break;
395     case 1:
396       state->accx = -a;
397       state->accy = -b;
398       break;
399     case 2:
400       state->accx = b;
401       state->accy = a;
402       break;
403     case 3:
404       state->accx = -b;
405       state->accy = -a;
406       break;
407     default:
408       abort();
409       break;
410     }
411
412   state->time_since_shake = 0;
413   recolor (state);
414 }
415
416
417 /* Look at the current time, and update state->time_since_shake.
418    Draw the FPS display if desired.
419  */
420 static void
421 check_wall_clock (b_state *state, float max_d)
422 {
423   static int tick = 0;
424   state->frame_count++;
425   
426   if (tick++ > 20)  /* don't call gettimeofday() too often -- it's slow. */
427     {
428       struct timeval now;
429       static struct timeval last = { 0, 0 };
430 # ifdef GETTIMEOFDAY_TWO_ARGS
431       struct timezone tzp;
432       gettimeofday(&now, &tzp);
433 # else
434       gettimeofday(&now);
435 # endif
436
437       if (last.tv_sec == 0)
438         last = now;
439
440       tick = 0;
441       if (now.tv_sec == last.tv_sec)
442         return;
443
444       state->time_since_shake += (now.tv_sec - last.tv_sec);
445
446       if (state->fps_p) 
447         {
448           float elapsed = ((now.tv_sec  + (now.tv_usec  / 1000000.0)) -
449                            (last.tv_sec + (last.tv_usec / 1000000.0)));
450           float fps = state->frame_count / elapsed;
451           float cps = state->collision_count / elapsed;
452           
453           sprintf (state->fps_str, 
454                    " FPS: %.2f  Collisions: %.3f/frame  Max motion: %.3f",
455                    fps, cps/fps, max_d);
456           
457           draw_fps_string(state);
458         }
459
460       state->frame_count = 0;
461       state->collision_count = 0;
462       last = now;
463     }
464 }
465
466 /* Erases the balls at their previous positions, and draws the new ones.
467  */
468 static void
469 repaint_balls (b_state *state)
470 {
471   int a;
472   int x1a, x2a, y1a, y2a;
473   int x1b, x2b, y1b, y2b;
474   float max_d = 0;
475
476   for (a=1; a <= state->count; a++)
477     {
478       GC gc;
479       x1a = (state->opx[a] - state->r[a] - state->xmin);
480       y1a = (state->opy[a] - state->r[a] - state->ymin);
481       x2a = (state->opx[a] + state->r[a] - state->xmin);
482       y2a = (state->opy[a] + state->r[a] - state->ymin);
483
484       x1b = (state->px[a] - state->r[a] - state->xmin);
485       y1b = (state->py[a] - state->r[a] - state->ymin);
486       x2b = (state->px[a] + state->r[a] - state->xmin);
487       y2b = (state->py[a] + state->r[a] - state->ymin);
488
489       if (!state->dbeclear_p
490 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
491           || !state->backb
492 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
493           )
494         {
495 /*        if (x1a != x1b || y1a != y1b)   -- leaves turds if we optimize this */
496             {
497               gc = state->erase_gc;
498               XFillArc (state->dpy, state->b, gc,
499                         x1a, y1a, x2a-x1a, y2a-y1a,
500                         0, 360*64);
501             }
502         }
503       if (state->mouse_ball == a)
504         gc = state->draw_gc2;
505       else
506         gc = state->draw_gc;
507
508       XFillArc (state->dpy, state->b, gc,
509                 x1b, y1b, x2b-x1b, y2b-y1b,
510                 0, 360*64);
511
512       if (state->shake_p)
513         {
514           /* distance this ball moved this frame */
515           float d = ((state->px[a] - state->opx[a]) *
516                      (state->px[a] - state->opx[a]) +
517                      (state->py[a] - state->opy[a]) *
518                      (state->py[a] - state->opy[a]));
519           if (d > max_d) max_d = d;
520         }
521
522       state->opx[a] = state->px[a];
523       state->opy[a] = state->py[a];
524     }
525
526   if (state->fps_p && state->dbeclear_p) 
527     draw_fps_string(state);
528
529 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
530   if (state->backb)
531     {
532       XdbeSwapInfo info[1];
533       info[0].swap_window = state->window;
534       info[0].swap_action = (state->dbeclear_p ? XdbeBackground : XdbeUndefined);
535       XdbeSwapBuffers (state->dpy, info, 1);
536     }
537   else
538 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
539   if (state->dbuf)
540     {
541       XCopyArea (state->dpy, state->b, state->window, state->erase_gc,
542                  0, 0, state->xgwa.width, state->xgwa.height, 0, 0);
543     }
544
545   if (state->shake_p && state->time_since_shake > 5)
546     {
547       max_d /= state->max_radius;
548       if (max_d < state->shake_threshold ||  /* when its stable */
549           state->time_since_shake > 30)      /* or when 30 secs has passed */
550         {
551           shake (state);
552         }
553     }
554
555   check_wall_clock (state, max_d);
556 }
557
558
559 /* Implements the laws of physics: move balls to their new positions.
560  */
561 static void
562 update_balls (b_state *state)
563 {
564   int a, b;
565   float d, vxa, vya, vxb, vyb, dd, cdx, cdy;
566   float ma, mb, vca, vcb, dva, dvb;
567   float dee2;
568
569   check_window_moved (state);
570
571   /* If we're currently tracking the mouse, update that ball first.
572    */
573   if (state->mouse_ball != 0)
574     {
575       int mouse_x, mouse_y;
576       query_mouse (state, &mouse_x, &mouse_y);
577       state->px[state->mouse_ball] = mouse_x;
578       state->py[state->mouse_ball] = mouse_y;
579       state->vx[state->mouse_ball] =
580         (0.1 *
581          (state->px[state->mouse_ball] - state->opx[state->mouse_ball]) *
582          state->tc);
583       state->vy[state->mouse_ball] =
584         (0.1 *
585          (state->py[state->mouse_ball] - state->opy[state->mouse_ball]) *
586          state->tc);
587     }
588
589   /* For each ball, compute the influence of every other ball. */
590   for (a=1; a <= state->count -  1; a++)
591     for (b=a + 1; b <= state->count; b++)
592       {
593          d = ((state->px[a] - state->px[b]) *
594               (state->px[a] - state->px[b]) +
595               (state->py[a] - state->py[b]) *
596               (state->py[a] - state->py[b]));
597          dee2 = (state->r[a] + state->r[b]) *
598                 (state->r[a] + state->r[b]);
599          if (d < dee2)
600          {
601             state->collision_count++;
602             d = sqrt(d);
603             dd = state->r[a] + state->r[b] - d;
604
605             cdx = (state->px[b] - state->px[a]) / d;
606             cdy = (state->py[b] - state->py[a]) / d;
607
608             /* Move each ball apart from the other by half the
609              * 'collision' distance.
610              */
611             state->px[a] -= 0.5 * dd * cdx;
612             state->py[a] -= 0.5 * dd * cdy;
613             state->px[b] += 0.5 * dd * cdx;
614             state->py[b] += 0.5 * dd * cdy;
615
616             ma = state->m[a];
617             mb = state->m[b];
618
619             vxa = state->vx[a];
620             vya = state->vy[a];
621             vxb = state->vx[b];
622             vyb = state->vy[b];
623
624             vca = vxa * cdx + vya * cdy; /* the component of each velocity */
625             vcb = vxb * cdx + vyb * cdy; /* along the axis of the collision */
626
627             /* elastic collison */
628             dva = (vca * (ma - mb) + vcb * 2 * mb) / (ma + mb) - vca;
629             dvb = (vcb * (mb - ma) + vca * 2 * ma) / (ma + mb) - vcb;
630
631             dva *= state->e; /* some energy lost to inelasticity */
632             dvb *= state->e;
633
634 #if 0
635             dva += (frand (50) - 25) / ma;   /* q: why are elves so chaotic? */
636             dvb += (frand (50) - 25) / mb;   /* a: brownian motion. */
637 #endif
638
639             vxa += dva * cdx;
640             vya += dva * cdy;
641             vxb += dvb * cdx;
642             vyb += dvb * cdy;
643
644             state->vx[a] = vxa;
645             state->vy[a] = vya;
646             state->vx[b] = vxb;
647             state->vy[b] = vyb;
648          }
649       }
650
651    /* Force all balls to be on screen.
652     */
653   for (a=1; a <= state->count; a++)
654     {
655       if (state->px[a] <= (state->xmin + state->r[a]))
656         {
657           state->px[a] = state->xmin + state->r[a];
658           state->vx[a] = -state->vx[a] * state->e;
659         }
660       if (state->px[a] >= (state->xmax - state->r[a]))
661         {
662           state->px[a] = state->xmax - state->r[a];
663           state->vx[a] = -state->vx[a] * state->e;
664         }
665       if (state->py[a] <= (state->ymin + state->r[a]))
666         {
667           state->py[a] = state->ymin + state->r[a];
668           state->vy[a] = -state->vy[a] * state->e;
669         }
670       if (state->py[a] >= (state->ymax - state->r[a]))
671         {
672           state->py[a] = state->ymax - state->r[a];
673           state->vy[a] = -state->vy[a] * state->e;
674         }
675     }
676
677   /* Apply gravity to all balls.
678    */
679   for (a=1; a <= state->count; a++)
680     if (a != state->mouse_ball)
681       {
682         state->vx[a] += state->accx * state->tc;
683         state->vy[a] += state->accy * state->tc;
684         state->px[a] += state->vx[a] * state->tc;
685         state->py[a] += state->vy[a] * state->tc;
686       }
687 }
688
689
690 /* Handle X events, specifically, allow a ball to be picked up with the mouse.
691  */
692 static void
693 handle_events (b_state *state)
694 {
695   XSync (state->dpy, False);
696   while (XPending (state->dpy))
697     {
698       XEvent event;
699       XNextEvent (state->dpy, &event);
700       if (event.xany.type == ButtonPress)
701         {
702           int i;
703           float fmx = event.xbutton.x_root;
704           float fmy = event.xbutton.y_root;
705           if (state->mouse_ball != 0)  /* second down-click?  drop the ball. */
706             {
707               state->mouse_ball = 0;
708               return;
709             }
710           else
711             for (i=1; i <= state->count; i++)
712               {
713                 float d = ((state->px[i] - fmx) * (state->px[i] - fmx) +
714                            (state->py[i] - fmy) * (state->py[i] - fmy));
715                 float r = state->r[i];
716                 if (d < r*r)
717                   {
718                     state->mouse_ball = i;
719                     return;
720                   }
721               }
722         }
723       else if (event.xany.type == ButtonRelease)   /* drop the ball */
724         {
725           state->mouse_ball = 0;
726           return;
727         }
728       else if (event.xany.type == ConfigureNotify)
729         {
730           /* This is redundant, since we poll this every iteration. */
731           check_window_moved (state);
732         }
733
734       screenhack_handle_event (state->dpy, &event);
735     }
736 }
737
738 \f
739 char *progclass = "FluidBalls";
740
741 char *defaults [] = {
742   ".background:         black",
743   ".textColor:          yellow",
744   ".font:               -*-helvetica-*-r-*-*-*-180-*-*-p-*-*-*",
745   "*delay:              10000",
746   "*count:              300",
747   "*size:               25",
748   "*random:             True",
749   "*gravity:            0.01",
750   "*wind:               0.00",
751   "*elasticity:         0.97",
752   "*timeScale:          1.0",
753   "*doFPS:              False",
754   "*shake:              True",
755   "*shakeThreshold:     0.015",
756   "*doubleBuffer:       True",
757 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
758   "*useDBE:             True",
759   "*useDBEClear:        True",
760 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
761   0
762 };
763
764 XrmOptionDescRec options [] = {
765   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
766   { "-count",           ".count",       XrmoptionSepArg, 0 },
767   { "-size",            ".size",        XrmoptionSepArg, 0 },
768   { "-count",           ".count",       XrmoptionSepArg, 0 },
769   { "-gravity",         ".gravity",     XrmoptionSepArg, 0 },
770   { "-wind",            ".wind",        XrmoptionSepArg, 0 },
771   { "-elasticity",      ".elasticity",  XrmoptionSepArg, 0 },
772   { "-fps",             ".doFPS",       XrmoptionNoArg, "True" },
773   { "-no-fps",          ".doFPS",       XrmoptionNoArg, "False" },
774   { "-shake",           ".shake",       XrmoptionNoArg, "True" },
775   { "-no-shake",        ".shake",       XrmoptionNoArg, "False" },
776   { "-random",          ".random",      XrmoptionNoArg, "True" },
777   { "-nonrandom",       ".random",      XrmoptionNoArg, "False" },
778   { "-db",              ".doubleBuffer", XrmoptionNoArg,  "True" },
779   { "-no-db",           ".doubleBuffer", XrmoptionNoArg,  "False" },
780   { 0, 0, 0, 0 }
781 };
782
783 void
784 screenhack (Display *dpy, Window window)
785 {
786   b_state *state = init_balls(dpy, window);
787   int delay = get_integer_resource ("delay", "Integer");
788
789   while (1)
790     {
791       repaint_balls(state);
792       update_balls(state);
793
794       XSync (dpy, False);
795       handle_events (state);
796       if (delay) usleep (delay);
797     }
798 }