ftp://ftp.uni-heidelberg.de/pub/X11/contrib/applications/xscreensaver-1.27.tar.Z
[xscreensaver] / hacks / bubbles.c
1 /* bubbles.c - frying pan / soft drink in a glass simulation */
2
3 /*$Id: bubbles.c,v 1.1 1996/09/08 01:35:40 jwz Exp $*/
4
5 /*
6  *  Copyright (C) 1995-1996 James Macnicol
7  *
8  *   This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by the
10  * Free Software Foundation; either version 2, or (at your option) any later
11  * version.
12  *
13  *   This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
15  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16  * for more details. 
17  */
18
19 /*
20  * I got my original inspiration for this by looking at the bottom of a 
21  * frying pan while something was cooking and watching the little bubbles
22  * coming off the bottom of the pan as the oil was boiling joining together
23  * to form bigger bubbles and finally to *pop* and disappear.  I had some
24  * time on my hands so I wrote this little xscreensaver module to imitate
25  * it.  Now that it's done it reminds me more of the bubbles you get in
26  * a glass of fizzy soft drink.....
27  *
28  * The problem seemed to be that the position/size etc. of all the bubbles
29  * on the screen had to be remembered and searched through to find when
30  * bubbles hit each other and combined.  To do this more efficiently, the
31  * window/screen is divided up into a square mesh of side length mesh_length
32  * and separate lists of bubbles contained in each cell of the mesh are
33  * kept.  Only the cells in the immediate vicinity of the bubble in question
34  * are searched.  This should make things more efficient although the whole
35  * thing seems to use up too much CPU, but then I'm using an ancient PC so
36  * perhaps it's not surprising .
37  * (Six months after I wrote the above I now have a Pentium with PCI graphics 
38  * and things are _much_ nicer.)
39  *
40  * Author:           James Macnicol 
41  * Internet E-mail : J.Macnicol@student.anu.edu.au
42  */
43
44 #include "bubbles.h"
45
46 #ifdef BUBBLES_IO
47 #include <dirent.h>
48 #include <fcntl.h>
49 #include <sys/types.h>
50 #endif /* BUBBLES_IO */
51
52 #include <limits.h>
53 #include <math.h>
54 #include <signal.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <sys/wait.h>
59 #include <unistd.h>
60 #include "screenhack.h"
61 #include "../utils/yarandom.h"
62
63 #ifdef HAVE_XPM
64 #include <X11/xpm.h>
65 #endif
66
67 /* 
68  * Public variables 
69  */
70
71 #ifndef NO_DEFAULT_BUBBLE
72 extern int num_default_bubbles;
73 extern char **default_bubbles[];
74 #endif /* NO_DEFAULT_BUBBLE */
75
76 char *progclass = "Bubbles";
77
78 char *defaults [] = {
79   "*background: black",
80   "*foreground: white",
81   "*simple:     false",
82   "*broken:     false",
83   "*delay:      2000",
84 #ifdef BUBBLES_IO
85   "*file:       (default)",
86   "*directory:  (default)",
87 #endif /* BUBBLES_IO */
88   "*quiet:      false", 
89   "*nodelay:    false",
90   "*3D:     false",
91   "*geometry:   400x300",
92   0
93 };
94
95 XrmOptionDescRec options [] = {
96   { "-simple",          ".simple",      XrmoptionNoArg, "true" },
97 #ifdef HAVE_XPM
98   { "-broken",          ".broken",      XrmoptionNoArg, "true" },
99 #endif /* HAVE_XPM */
100   { "-quiet",           ".quiet",       XrmoptionNoArg, "true" },
101   { "-nodelay",         ".nodelay",     XrmoptionNoArg, "true" },
102   { "-3D",          ".3D",      XrmoptionNoArg, "true" },
103 #ifdef BUBBLES_IO
104   { "-file",            ".file",        XrmoptionSepArg, 0 },
105   { "-directory",       ".directory",   XrmoptionSepArg, 0 },
106 #endif /* BUBBLES_IO */
107   { "-delay",           ".delay",       XrmoptionSepArg, 0 }
108 };
109 int options_size = (sizeof (options) / sizeof (options[0]));
110
111 /* 
112  * Private variables 
113  */
114
115 static Bubble **mesh;
116 static int mesh_length;
117 static int mesh_width;
118 static int mesh_height;
119 static int mesh_cells;
120
121 static int **adjacent_list;
122
123 static int screen_width;
124 static int screen_height;
125 static int screen_depth;
126 static unsigned int default_fg_pixel, default_bg_pixel;
127 /* 
128  * I know it's not elegant to save this stuff in global variables
129  * but we need it for the signal handler.
130  */
131 static Display *defdsp;
132 static Window defwin;
133 static Colormap defcmap;
134
135 /* For simple mode only */
136 static int bubble_min_radius;
137 static int bubble_max_radius;
138 static long *bubble_areas;
139 static GC draw_gc, erase_gc;
140
141 #ifdef HAVE_XPM
142 static int num_bubble_pixmaps;
143 static Bubble_Step **step_pixmaps;
144 #ifdef BUBBLES_IO
145 static char *pixmap_file;
146 #endif /* BUBBLES_IO */
147 static int use_default_bubble;
148 #endif /* HAVE_XPM */
149
150 /* Options stuff */
151 #ifdef HAVE_XPM
152 static Bool simple = False;
153 #else
154 static Bool simple = True;
155 #endif
156 static Bool broken = False;
157 static Bool quiet = False;
158 static Bool threed = False;
159 static int delay;
160
161 /* 
162  * To prevent forward references, some stuff is up here 
163  */
164
165 static long
166 calc_bubble_area(r)
167      int r;
168 /* Calculate the area of a bubble of radius r */
169 {
170 #ifdef DEBUG
171   printf("%d %g\n", r,
172          10.0 * PI * (double)r * (double)r * (double)r);
173 #endif /* DEBUG */
174   if (threed)
175     return (long)(10.0 * PI * (double)r * (double)r * (double)r);
176   else
177     return (long)(10.0 * PI * (double)r * (double)r);
178 }
179
180 static void *
181 xmalloc(size)
182      size_t size;
183 /* Safe malloc */
184 {
185   void *ret;
186
187   if ((ret = malloc(size)) == NULL) {
188     fprintf(stderr, "%s: out of memory\n", progname);
189     exit(1);
190   }
191   return ret;
192 }
193
194 #ifdef DEBUG
195 static void 
196 die_bad_bubble(bb)
197      Bubble *bb;
198 /* This is for use with GDB */
199 {
200   fprintf(stderr, "Bad bubble detected at 0x%x!\n", (int)bb);
201   exit(1);
202 }
203 #endif
204
205 static int
206 null_bubble(bb)
207      Bubble *bb;
208 /* Returns true if the pointer passed is NULL.  If not then this checks to
209 see if the bubble is valid (i.e. the (x,y) position is valid and the magic
210 number is set correctly.  This only a sanity check for debugging and is
211 turned off if DEBUG isn't set. */
212 {
213   if (bb == (Bubble *)NULL)
214     return 1;
215 #ifdef DEBUG
216   if ((bb->cell_index < 0) || (bb->cell_index > mesh_cells)) {
217     fprintf(stderr, "cell_index = %d\n", bb->cell_index);
218     die_bad_bubble(bb);
219   }
220   if (bb->magic != BUBBLE_MAGIC)  {
221     fprintf(stderr, "Magic = %d\n", bb->magic);
222     die_bad_bubble(bb);
223   }
224   if (simple) {
225     if ((bb->x < 0) || (bb->x > screen_width) ||
226         (bb->y < 0) || (bb->y > screen_height) ||
227         (bb->radius < bubble_min_radius) || (bb->radius >
228                                              bubble_max_radius)) {
229       fprintf(stderr,
230               "radius = %d, x = %d, y = %d, magic = %d, \
231 cell index = %d\n", bb->radius, bb->x, bb->y, bb->magic, bb->cell_index);
232       die_bad_bubble(bb);  
233     }
234 #ifdef HAVE_XPM
235   } else {
236     if ((bb->x < 0) || (bb->x > screen_width) ||
237         (bb->y < 0) || (bb->y > screen_height) ||
238         (bb->radius < step_pixmaps[0]->radius) || 
239         (bb->radius > step_pixmaps[num_bubble_pixmaps-1]->radius)) {
240       fprintf(stderr,
241               "radius = %d, x = %d, y = %d, magic = %d, \
242 cell index = %d\n", bb->radius, bb->x, bb->y, bb->magic, bb->cell_index);
243       die_bad_bubble(bb);  
244     }
245 #endif /* HAVE_XPM */
246   }
247 #endif /* DEBUG */
248   return 0;
249 }
250
251 #ifdef DEBUG
252 static void 
253 print_bubble_list(bb)
254      Bubble *bb;
255 /* Print list of where all the bubbles are.  For debugging purposes only. */
256 {
257   if (! null_bubble(bb)) {
258     printf("  (%d, %d) %d\n", bb->x, bb->y, bb->radius);
259     print_bubble_list(bb->next);
260   }
261 }
262 #endif /* DEBUG */
263
264 static void 
265 add_bubble_to_list(list, bb)
266      Bubble **list;
267      Bubble *bb;
268 /* Take a pointer to a list of bubbles and stick bb at the head of the
269  list. */
270 {
271   Bubble *head = *list;
272
273   if (null_bubble(head)) {
274     bb->prev = (Bubble *)NULL;
275     bb->next = (Bubble *)NULL;
276   } else {
277     bb->next = head;
278     bb->prev = (Bubble *)NULL;
279     head->prev = bb;
280   }
281   *list = bb;
282 }
283
284
285 /* 
286  * Mesh stuff 
287  */
288
289
290 static void 
291 init_mesh()
292 /* Setup the mesh of bubbles */
293 {
294   int i;
295
296   mesh = (Bubble **)xmalloc(mesh_cells * sizeof(Bubble *));
297   for (i = 0; i < mesh_cells; i++)
298     mesh[i] = (Bubble *)NULL;
299 }
300
301 static int
302 cell_to_mesh(x, y)
303      int x;
304      int y;
305 /* convert cell coordinates to mesh index */
306 {
307 #ifdef DEBUG
308   if ((x < 0) || (y < 0)) {
309     fprintf(stderr, "cell_to_mesh: x = %d, y = %d\n", x, y);
310     exit(1);
311   }
312 #endif
313   return ((mesh_width * y) + x);
314 }
315
316 static void 
317 mesh_to_cell(mi, cx, cy)
318      int mi;
319      int *cx;
320      int *cy;
321 /* convert mesh index into cell coordinates */
322 {
323   *cx = mi % mesh_width;
324   *cy = mi / mesh_width;
325 }
326
327 static int
328 pixel_to_mesh(x, y)
329      int x;
330      int y;
331 /* convert screen coordinates into mesh index */
332 {
333   return cell_to_mesh((x / mesh_length), (y / mesh_length));
334 }
335
336 static int
337 verify_mesh_index(x, y)
338      int x;
339      int y;
340 /* check to see if (x,y) is in the mesh */
341 {
342   if ((x < 0) || (y < 0) || (x >= mesh_width) || (y >= mesh_height))
343     return (-1);
344   return (cell_to_mesh(x, y));
345 }
346
347 #ifdef DEBUG
348 static void 
349 print_adjacents(adj)
350     int *adj;
351 /* Print a list of the cells calculated above.  For debugging only. */
352 {
353   int i;
354
355   printf("(");
356   for (i = 0; i < 8; i++)
357     printf("%d ", adj[i]);
358   printf(")\n");
359 }
360 #endif /* DEBUG */
361
362 static void 
363 add_to_mesh(bb)
364      Bubble *bb;
365 /* Add the given bubble to the mesh by sticking it on the front of the
366 list.  bb is already allocated so no need to malloc() anything, just
367 adjust pointers. */
368 {
369 #ifdef DEBUG
370   if (null_bubble(bb)) {
371     fprintf(stderr, "Bad bubble passed to add_to_mesh()!\n");
372     exit(1);
373   }
374 #endif /* DEBUG */
375
376   add_bubble_to_list(&mesh[bb->cell_index], bb);
377 }
378
379 #ifdef DEBUG
380 static void 
381 print_mesh()
382 /* Print the contents of the mesh */
383 {
384   int i;
385
386   for (i = 0; i < mesh_cells; i++) {
387     if (! null_bubble(mesh[i])) {
388       printf("Mesh cell %d\n", i);
389       print_bubble_list(mesh[i]);
390     }
391   }
392 }
393
394 static void 
395 valid_mesh()
396 /* Check to see if the mesh is Okay.  For debugging only. */
397 {
398   int i;
399   Bubble *b;
400
401   for (i = 0; i < mesh_cells; i++) {
402     b = mesh[i];
403     while (! null_bubble(b))
404       b = b->next;
405   }
406 }
407
408 static int
409 total_bubbles()
410 /* Count how many bubbles there are in total.  For debugging only. */
411 {
412   int rv = 0;
413   int i;
414   Bubble *b;
415
416   for (i = 0; i < mesh_cells; i++) {
417     b = mesh[i];
418     while (! null_bubble(b)) {
419       rv++;
420       b = b->next;
421     } 
422   }
423
424   return rv;
425 }
426 #endif /* DEBUG */
427
428 static void 
429 calculate_adjacent_list()
430 /* Calculate the list of cells adjacent to a particular cell for use
431    later. */
432 {
433   int i; 
434   int ix, iy;
435
436   adjacent_list = (int **)xmalloc(mesh_cells * sizeof(int *));
437   for (i = 0; i < mesh_cells; i++) {
438     adjacent_list[i] = (int *)xmalloc(9 * sizeof(int));
439     mesh_to_cell(i, &ix, &iy);
440     adjacent_list[i][0] = verify_mesh_index(--ix, --iy);
441     adjacent_list[i][1] = verify_mesh_index(++ix, iy);
442     adjacent_list[i][2] = verify_mesh_index(++ix, iy);
443     adjacent_list[i][3] = verify_mesh_index(ix, ++iy);
444     adjacent_list[i][4] = verify_mesh_index(ix, ++iy);
445     adjacent_list[i][5] = verify_mesh_index(--ix, iy);
446     adjacent_list[i][6] = verify_mesh_index(--ix, iy);
447     adjacent_list[i][7] = verify_mesh_index(ix, --iy);
448     adjacent_list[i][8] = i;
449   }
450 }
451
452 static void
453 adjust_areas()
454 /* Adjust areas of bubbles so we don't get overflow in weighted_mean() */
455 {
456   double maxvalue;
457   long maxarea;
458   long factor;
459   int i;
460
461 #ifdef HAVE_XPM
462   if (simple)
463     maxarea = bubble_areas[bubble_max_radius+1];
464   else
465     maxarea = step_pixmaps[num_bubble_pixmaps]->area;
466 #else
467   maxarea = bubble_areas[bubble_max_radius+1];
468 #endif /* HAVE_XPM */
469   maxvalue = (double)screen_width * 2.0 * (double)maxarea;
470   factor = (long)ceil(maxvalue / (double)LONG_MAX);
471   if (factor > 1) {
472     /* Overflow will occur in weighted_mean().  We must divide areas
473        each by factor so it will never do so. */
474 #ifdef HAVE_XPM
475     if (simple) {
476       for (i = bubble_min_radius; i <= bubble_max_radius+1; i++) {
477         bubble_areas[i] /= factor;
478         if (bubble_areas[i] == 0)
479           bubble_areas[i] = 1;
480       }
481     } else {
482       for (i = 0; i <= num_bubble_pixmaps; i++) {
483 #ifdef DEBUG
484         printf("area = %ld", step_pixmaps[i]->area);
485 #endif /* DEBUG */
486         step_pixmaps[i]->area /= factor;
487         if (step_pixmaps[i]->area == 0)
488           step_pixmaps[i]->area = 1;
489 #ifdef DEBUG
490         printf("-> %ld\n", step_pixmaps[i]->area);
491 #endif /* DEBUG */
492       }
493     }
494 #else
495     for (i = bubble_min_radius; i <= bubble_max_radius+1; i++) {
496       bubble_areas[i] /= factor;
497       if (bubble_areas[i] == 0)
498         bubble_areas[i] = 1;
499     }
500 #endif /* HAVE_XPM */
501   }
502 #ifdef DEBUG
503   printf("maxarea = %ld\n", maxarea);
504   printf("maxvalue = %g\n", maxvalue);
505   printf("LONG_MAX = %ld\n", LONG_MAX);
506   printf("factor = %ld\n", factor);
507 #endif /* DEBUG */
508 }
509
510 /* 
511  * Bubbles stuff 
512  */
513
514 static Bubble *
515 new_bubble()
516 /* Add a new bubble at some random position on the screen of the smallest
517 size. */
518 {
519   Bubble *rv = (Bubble *)xmalloc(sizeof(Bubble));
520
521   /* Can't use null_bubble() here since magic number hasn't been set */
522   if (rv == (Bubble *)NULL) {
523     fprintf(stderr, "Ran out of memory!\n");
524     exit(1);
525   }
526
527   if (simple) {
528     rv->radius = bubble_min_radius;
529     rv->area = bubble_areas[bubble_min_radius];
530 #ifdef HAVE_XPM
531   } else {
532     rv->step = 0;
533     rv->radius = step_pixmaps[0]->radius;
534     rv->area = step_pixmaps[0]->area;
535 #endif /* HAVE_XPM */
536   }
537   rv->visible = 0;
538   rv->magic = BUBBLE_MAGIC;
539   rv->x = ya_random() % screen_width;
540   rv->y = ya_random() % screen_height;
541   rv->cell_index = pixel_to_mesh(rv->x, rv->y);
542
543   return rv;
544 }
545
546 static void 
547 show_bubble(bb)
548      Bubble *bb;
549 /* paint the bubble on the screen */
550 {
551   if (null_bubble(bb)) {
552     fprintf(stderr, "NULL bubble passed to show_bubble\n");
553     exit(1);
554   }
555
556   if (! bb->visible) {
557     bb->visible = 1;
558
559     if (simple) {
560       XDrawArc(defdsp, defwin, draw_gc, (bb->x - bb->radius),
561                (bb->y - bb->radius), bb->radius*2, bb->radius*2, 0,
562                360*64);  
563     } else {
564 #ifdef HAVE_XPM
565       XSetClipOrigin(defdsp, step_pixmaps[bb->step]->draw_gc, 
566                      (bb->x - bb->radius),
567                      (bb->y - bb->radius));
568       
569       XCopyArea(defdsp, step_pixmaps[bb->step]->ball, defwin, 
570                 step_pixmaps[bb->step]->draw_gc,
571                 0, 0, (bb->radius * 2), 
572                 (bb->radius * 2),  
573                 (bb->x - bb->radius),
574                 (bb->y - bb->radius));
575 #endif /* HAVE_XPM */
576     }
577   }
578 }
579
580 static void 
581 hide_bubble(bb)
582      Bubble *bb;
583 /* erase the bubble */
584 {
585   if (null_bubble(bb)) {
586     fprintf(stderr, "NULL bubble passed to hide_bubble\n");
587     exit(1);
588   }
589
590   if (bb->visible) {
591     bb->visible = 0;
592
593     if (simple) {
594       XDrawArc(defdsp, defwin, erase_gc, (bb->x - bb->radius),
595                (bb->y - bb->radius), bb->radius*2, bb->radius*2, 0,
596                360*64);
597     } else {
598 #ifdef HAVE_XPM
599       if (! broken) {
600         XSetClipOrigin(defdsp, step_pixmaps[bb->step]->erase_gc, 
601                        (bb->x - bb->radius), (bb->y - bb->radius));
602         
603         XFillRectangle(defdsp, defwin, step_pixmaps[bb->step]->erase_gc,
604                        (bb->x - bb->radius),
605                        (bb->y - bb->radius),
606                        (bb->radius * 2),
607                        (bb->radius * 2));
608       }
609 #endif /* HAVE_XPM */
610     }
611   }
612 }
613
614 static void 
615 delete_bubble_in_mesh(bb, keep_bubble)
616      Bubble *bb;
617      int keep_bubble;
618 /* Delete an individual bubble, adjusting list of bubbles around it.
619    If keep_bubble is true then the bubble isn't actually deleted.  We
620    use this to allow bubbles to change mesh cells without reallocating,
621    (it needs this when two bubbles collide and the centre position is
622    recalculated, and this may stray over a mesh boundary). */
623 {
624   if ((!null_bubble(bb->prev)) && (!null_bubble(bb->next))) {
625     bb->prev->next = bb->next;
626     bb->next->prev = bb->prev;
627   } else if ((!null_bubble(bb->prev)) &&
628              (null_bubble(bb->next))) {
629     bb->prev->next = (Bubble *)NULL;
630     bb->next = mesh[bb->cell_index];
631   } else if ((null_bubble(bb->prev)) &&
632              (!null_bubble(bb->next))) {
633     bb->next->prev = (Bubble *)NULL;
634     mesh[bb->cell_index] = bb->next;
635     bb->next = mesh[bb->cell_index];
636   } else {
637     /* Only item on list */
638     mesh[bb->cell_index] = (Bubble *)NULL;
639   }              
640   if (! keep_bubble)
641     free(bb);
642 }
643
644 static unsigned long 
645 ulongsqrint(x)
646      int x;
647 /* Saves ugly inline code */
648 {
649   return ((unsigned long)x * (unsigned long)x);
650 }
651
652 static Bubble *
653 get_closest_bubble(bb)
654      Bubble *bb;
655 /* Find the closest bubble touching the this bubble, NULL if none are
656    touching. */
657 {
658   Bubble *rv = (Bubble *)NULL;
659   Bubble *tmp;
660   unsigned long separation2, touchdist2;
661   int dx, dy;
662   unsigned long closest2 = ULONG_MAX;
663   int i;
664
665 #ifdef DEBUG 
666   if (null_bubble(bb)) {
667     fprintf(stderr, "NULL pointer 0x%x passed to get_closest_bubble()!", 
668             (int)bb);
669     exit(1);
670   }
671 #endif /* DEBUG */
672
673   for (i = 0; i < 9; i++) {
674     /* There is a bug here where bb->cell_index is negaitve.. */
675 #ifdef DEBUG
676     if ((bb->cell_index < 0) || (bb->cell_index >= mesh_cells)) {
677       fprintf(stderr, "bb->cell_index = %d\n", bb->cell_index);
678       exit(1);
679     }
680 #endif /* DEBUG */
681 /*    printf("%d,", bb->cell_index); */
682     if (adjacent_list[bb->cell_index][i] != -1) {
683       tmp = mesh[adjacent_list[bb->cell_index][i]];
684       while (! null_bubble(tmp)) {
685         if (tmp != bb) {
686           dx = tmp->x - bb->x;
687           dy = tmp->y - bb->y;
688           separation2 = ulongsqrint(dx) + ulongsqrint(dy);
689           /* Add extra leeway so circles _never_ overlap */
690           touchdist2 = ulongsqrint(tmp->radius + bb->radius + 2);
691           if ((separation2 <= touchdist2) && (separation2 <
692                                               closest2)) {
693             rv = tmp;
694             closest2 = separation2;
695           }
696         }
697         tmp = tmp->next;
698       }
699     }
700   }
701
702   return rv;
703 }
704
705 #ifdef DEBUG
706 static void
707 ldr_barf()
708 {
709 }
710 #endif /* DEBUG */
711
712 static long
713 long_div_round(num, dem)
714      long num;
715      long dem;
716 {
717   long divvie, moddo;
718
719 #ifdef DEBUG
720   if ((num < 0) || (dem < 0)) {
721     fprintf(stderr, "long_div_round: %ld, %ld\n", num, dem);
722     ldr_barf();
723     exit(1);
724   }
725 #endif /* DEBUG */
726
727   divvie = num / dem;
728   moddo = num % dem;
729   if (moddo > (dem / 2))
730     ++divvie;
731
732 #ifdef DEBUG
733   if ((divvie < 0) || (moddo < 0)) {
734     fprintf(stderr, "long_div_round: %ld, %ld\n", divvie, moddo);
735     ldr_barf();
736     exit(1);
737   }
738 #endif /* DEBUG */
739
740   return divvie;
741 }
742
743 static int
744 weighted_mean(n1, n2, w1, w2)
745      int n1;
746      int n2;
747      long w1;
748      long w2;
749 /* Mean of n1 and n2 respectively weighted by weights w1 and w2. */
750 {
751 #ifdef DEBUG
752   if ((w1 <= 0) || (w2 <= 0)) {
753     fprintf(stderr, "Bad weights passed to weighted_mean() - \
754 (%d, %d, %ld, %ld)!\n", n1, n2, w1, w2);
755     exit(1);
756   }
757 #endif /* DEBUG */
758   return ((int)long_div_round((long)n1 * w1 + (long)n2 * w2,
759                            w1 + w2));
760 }
761
762 static int
763 bubble_eat(diner, food)
764      Bubble *diner;
765      Bubble *food;
766 /* The diner eats the food.  Returns true (1) if the diner still exists */
767
768   int i;
769   int newmi;
770
771 #ifdef DEBUG
772   if ((null_bubble(diner)) || (null_bubble(food))) {
773     fprintf(stderr, "Bad bubbles passed to bubble_eat()!\n");
774     exit(1);
775   }
776 #endif /* DEBUG */
777
778   /* We hide the diner even in the case that it doesn't grow so that
779      if the food overlaps its boundary it is replaced. This could
780      probably be solved by letting bubbles eat others which are close
781      but not quite touching.  It's probably worth it, too, since we
782      would then not have to redraw bubbles which don't change in
783      size. */
784
785   hide_bubble(diner);
786   hide_bubble(food);
787   diner->x = weighted_mean(diner->x, food->x, diner->area, food->area);
788   diner->y = weighted_mean(diner->y, food->y, diner->area, food->area);
789   newmi = pixel_to_mesh(diner->x, diner->y);
790   diner->area += food->area;
791   delete_bubble_in_mesh(food, DELETE_BUBBLE);
792
793   if ((simple) && (diner->area > bubble_areas[bubble_max_radius])) {
794     delete_bubble_in_mesh(diner, DELETE_BUBBLE);
795     return 0;
796   }
797 #ifdef HAVE_XPM
798   if ((! simple) && (diner->area > 
799                      step_pixmaps[num_bubble_pixmaps]->area)) {
800     delete_bubble_in_mesh(diner, DELETE_BUBBLE);
801     return 0;
802   }
803 #endif /* HAVE_XPM */
804
805   if (simple) {
806     if (diner->area > bubble_areas[diner->radius + 1]) {
807       /* Move the bubble to a new radius */
808       i = diner->radius;
809       while (diner->area > bubble_areas[i+1])
810         ++i;
811       diner->radius = i;
812     }
813     show_bubble(diner);
814 #ifdef HAVE_XPM
815   } else {
816     if (diner->area > step_pixmaps[diner->step+1]->area) {
817       i = diner->step;
818       while (diner->area > step_pixmaps[i+1]->area)
819         ++i;
820       diner->step = i;
821       diner->radius = step_pixmaps[diner->step]->radius;
822     }
823     show_bubble(diner);
824 #endif /* HAVE_XPM */
825   }
826
827   /* Now adjust locations and cells if need be */
828   if (newmi != diner->cell_index) {
829     delete_bubble_in_mesh(diner, KEEP_BUBBLE);
830     diner->cell_index = newmi;
831     add_to_mesh(diner);
832   }
833
834   return 1;
835 }
836
837 static int
838 merge_bubbles(b1, b2)
839      Bubble *b1;
840      Bubble *b2;
841 /* These two bubbles merge into one.  If the first one wins out return
842 1 else return 2.  If there is no winner (it explodes) then return 0 */
843 {
844   int b1size, b2size;
845
846   b1size = b1->area;
847   b2size = b2->area;
848
849 #ifdef DEBUG
850   if ((null_bubble(b1) || null_bubble(b2))) {
851     fprintf(stderr, "NULL bubble passed to merge_bubbles()!\n");
852     exit(1);
853   }
854 #endif /* DEBUG */
855
856   if (b1 == b2) {
857     hide_bubble(b1);
858     delete_bubble_in_mesh(b1, DELETE_BUBBLE);
859     return 0;
860   }
861
862   if (b1size > b2size) {
863     switch (bubble_eat(b1, b2)) {
864     case 0:
865       return 0;
866       break;
867     case 1:
868       return 1;
869       break;
870     default:
871       break;
872     }
873   } else if (b1size < b2size) {
874     switch (bubble_eat(b2, b1)) {
875     case 0:
876       return 0;
877       break;
878     case 1:
879       return 2;
880       break;
881     default:
882       break;
883     }
884   } else {
885     if ((ya_random() % 2) == 0) {
886       switch (bubble_eat(b1, b2)) {
887       case 0:
888         return 0;
889         break;
890       case 1:
891         return 1;
892         break;
893       default:
894         break;
895       }
896     } else {
897       switch (bubble_eat(b2, b1)) {
898       case 0:
899         return 0;
900         break;
901       case 1:
902         return 2;
903         break;
904       default:
905         break;
906       }
907     }
908   }
909   fprintf(stderr, "An error occurred in merge_bubbles()\n");
910   exit(1);
911 }
912
913 static void 
914 insert_new_bubble(tmp)
915      Bubble *tmp;
916 /* Calculates which bubbles are eaten when a new bubble tmp is
917    inserted.  This is called recursively in case when a bubble grows
918    it eats others.  Careful to pick out disappearing bubbles. */
919 {
920   Bubble *nextbub;
921   Bubble *touch;
922
923 #ifdef DEBUG
924   if (null_bubble(tmp)) {
925     fprintf(stderr, "Bad bubble passed to insert_new_bubble()!\n");
926     exit(1);
927   }
928 #endif /* DEBUG */
929   
930   nextbub = tmp;
931   touch = get_closest_bubble(nextbub);
932   while (! null_bubble(touch)) {
933     switch (merge_bubbles(nextbub, touch)) {
934     case 2:
935       /* touch ate nextbub and survived */
936       nextbub = touch;
937       break;
938     case 1:
939       /* nextbub ate touch and survived */
940       break;
941     case 0:
942       /* somebody ate someone else but they exploded */
943       nextbub = (Bubble *)NULL;
944       break;
945     default:
946       /* something went wrong */
947       fprintf(stderr, "Error occurred in insert_new_bubble()\n");
948       exit(1);
949     }
950     /* Check to see if there are any other bubbles still in the area
951        and if we need to do this all over again for them. */
952     if (! null_bubble(nextbub))
953       touch = get_closest_bubble(nextbub);
954     else
955       touch = (Bubble *)NULL;
956   }
957 }
958
959 #ifdef DEBUG
960 static int
961 get_length_of_bubble_list(bb)
962      Bubble *bb;
963 {
964   Bubble *tmp = bb;
965   int rv = 0;
966
967   while (! null_bubble(tmp)) {
968     rv++;
969     tmp = tmp->next;
970   }
971
972   return rv;
973 }
974 #endif /* DEBUG */
975
976 /*
977  * Pixmap stuff used regardless of whether file I/O is available.  Must
978  * still check for XPM, though!
979  */
980
981 #ifdef HAVE_XPM
982
983 static void 
984 free_pixmaps()
985 /* Free resources associated with XPM */
986 {
987   int i;
988
989 #ifdef DEBUG
990   if (simple) {
991     fprintf(stderr, "free_pixmaps() called in simple mode\n");
992     exit(1);
993   }
994   printf("free_pixmaps()\n");
995 #endif /* DEBUG */
996
997   for(i = 0; i < (num_bubble_pixmaps - 1); i++) {
998     XFreePixmap(defdsp, step_pixmaps[i]->ball);
999     XFreePixmap(defdsp, step_pixmaps[i]->shape_mask);
1000     XFreeGC(defdsp, step_pixmaps[i]->draw_gc);
1001     XFreeGC(defdsp, step_pixmaps[i]->erase_gc);
1002     XFreeColors(defdsp, defcmap, step_pixmaps[i]->xpmattrs.pixels, 
1003                 step_pixmaps[i]->xpmattrs.npixels, 0);
1004     XpmFreeAttributes(&step_pixmaps[i]->xpmattrs);
1005   }
1006 }
1007
1008 static void 
1009 onintr(a)
1010      int a;
1011 /* This gets called when SIGINT or SIGTERM is received */
1012 {
1013   free_pixmaps();
1014   exit(0);
1015 }
1016
1017 #ifdef DEBUG
1018 static void
1019 onsegv(a)
1020      int a;
1021 /* Called when SEGV detected.   Hmmmmm.... */
1022 {
1023   fflush(stdout);
1024   fprintf(stderr, "SEGV detected! : %d\n", a);
1025   exit(1);
1026 }
1027 #endif /* DEBUG */
1028
1029
1030 /*
1031  * Pixmaps without file I/O (but do have XPM)
1032  */
1033
1034 static void 
1035 pixmap_sort(head, numelems)
1036      Bubble_Step **head;
1037      int numelems;
1038 /* Couldn't get qsort to work right with this so I wrote my own.  This puts
1039 the numelems length array with first element at head into order of radius.
1040 */
1041 {
1042   Bubble_Step tmp;
1043   Bubble_Step *least = 0;
1044   int minradius = INT_MAX;
1045   int i;
1046
1047   for (i = 0; i < numelems; i++) {
1048     if (head[i]->radius < minradius) {
1049       least = head[i];
1050       minradius = head[i]->radius;
1051     }
1052   }
1053   if (*head != least) {
1054     memcpy(&tmp, least, sizeof(Bubble_Step));
1055     memcpy(least, *head, sizeof(Bubble_Step));
1056     memcpy(*head, &tmp, sizeof(Bubble_Step));
1057   }
1058
1059   if (numelems > 2)
1060     pixmap_sort(&head[1], numelems-1);
1061 }
1062
1063 static int
1064 extrapolate(i1, i2)
1065      int i1;
1066      int i2;
1067 {
1068   return (i2 + (i2 - i1));
1069 }
1070
1071 static void 
1072 make_pixmap_array(list)
1073      Bubble_Step *list;
1074 /* From a linked list of bubbles construct the array step_pixmaps */
1075 {
1076   Bubble_Step *tmp = list;
1077   int ind;
1078 #ifdef DEBUG
1079   int prevrad = -1;
1080 #endif
1081   
1082   if (list == (Bubble_Step *)NULL) {
1083     fprintf(stderr, "NULL list passed to make_pixmap_array\n");
1084     exit(1);
1085   }
1086
1087   num_bubble_pixmaps = 1;
1088   while(tmp->next != (Bubble_Step *)NULL) {
1089     tmp = tmp->next;
1090     ++num_bubble_pixmaps;
1091   }
1092
1093   if (num_bubble_pixmaps < 2) {
1094     fprintf(stderr, "Must be at least two bubbles in file\n");
1095     exit(1);
1096   }
1097
1098   step_pixmaps = (Bubble_Step **)xmalloc((num_bubble_pixmaps + 1) * 
1099                                          sizeof(Bubble_Step *));
1100
1101   /* Copy them blindly into the array for sorting. */
1102   ind = 0;
1103   tmp = list;
1104   do {
1105     step_pixmaps[ind++] = tmp;
1106     tmp = tmp->next;
1107   } while(tmp != (Bubble_Step *)NULL);
1108
1109   /* We make another bubble beyond the ones with pixmaps so that the final
1110      bubble hangs around and doesn't pop immediately.  It's radius and area
1111      are found by extrapolating from the largest two bubbles with pixmaps. */
1112
1113   step_pixmaps[num_bubble_pixmaps] = 
1114     (Bubble_Step *)xmalloc(sizeof(Bubble_Step));
1115   step_pixmaps[num_bubble_pixmaps]->radius = INT_MAX;
1116
1117   pixmap_sort(step_pixmaps, (num_bubble_pixmaps + 1));
1118
1119 #ifdef DEBUG
1120   if (step_pixmaps[num_bubble_pixmaps]->radius != INT_MAX) {
1121     fprintf(stderr, "pixmap_sort() screwed up make_pixmap_array\n");
1122   }
1123 #endif /* DEBUG */
1124
1125   step_pixmaps[num_bubble_pixmaps]->radius = 
1126     extrapolate(step_pixmaps[num_bubble_pixmaps-2]->radius,
1127                 step_pixmaps[num_bubble_pixmaps-1]->radius);
1128   step_pixmaps[num_bubble_pixmaps]->area = 
1129     calc_bubble_area(step_pixmaps[num_bubble_pixmaps]->radius);
1130   
1131
1132 #ifdef DEBUG
1133   /* Now check for correct order */
1134   for (ind = 0; ind < num_bubble_pixmaps; ind++) {
1135     if (prevrad > 0) {
1136       if (step_pixmaps[ind]->radius < prevrad) {
1137         fprintf(stderr, "Pixmaps not in ascending order of radius\n");
1138         exit(1);
1139       }
1140     }
1141     prevrad = step_pixmaps[ind]->radius;
1142   }
1143 #endif /* DEBUG */
1144 }
1145
1146 #ifndef NO_DEFAULT_BUBBLE
1147 static void
1148 make_pixmap_from_default(pixmap_data, bl)
1149      char **pixmap_data;
1150      Bubble_Step *bl;
1151 /* Read pixmap data which has been compiled into the program and a pointer
1152  to which has been passed. 
1153
1154  This is virtually copied verbatim from make_pixmap_from_file() above and
1155 changes made to either should be propagated onwards! */
1156 {
1157   int result;
1158   XGCValues gcv;
1159
1160 #ifdef DEBUG
1161   if (pixmap_data == (char **)0) {
1162     fprintf(stderr, "make_pixmap_from_default(): NULL passed\n");
1163     exit(1);
1164   }
1165 #endif
1166
1167   if (bl == (Bubble_Step *)NULL) {
1168     fprintf(stderr, "NULL pointer passed to make_pixmap()\n");
1169     exit(1);
1170   }
1171
1172   bl->xpmattrs.closeness = 40000;
1173   bl->xpmattrs.valuemask = XpmColormap | XpmCloseness;
1174   bl->xpmattrs.colormap = defcmap;
1175
1176   /* This is the only line which is different from make_pixmap_from_file() */
1177   result = XpmCreatePixmapFromData(defdsp, defwin, pixmap_data, &bl->ball, 
1178                                    &bl->shape_mask, &bl->xpmattrs);
1179
1180   switch(result) {
1181   case XpmColorError:
1182     fprintf(stderr, "xpm: color substitution performed\n");
1183     /* fall through */
1184   case XpmSuccess:
1185     bl->radius = MAX(bl->xpmattrs.width, bl->xpmattrs.height) / 2;
1186     bl->area = calc_bubble_area(bl->radius);
1187     break;
1188   case XpmColorFailed:
1189     fprintf(stderr, "xpm: color allocation failed\n");
1190     exit(1);
1191   case XpmNoMemory:
1192     fprintf(stderr, "xpm: out of memory\n");
1193     exit(1);
1194   default:
1195     fprintf(stderr, "xpm: unknown error code %d\n", result);
1196     exit(1);
1197   }
1198   
1199   gcv.plane_mask = AllPlanes;
1200   gcv.foreground = default_fg_pixel;
1201   gcv.function = GXcopy;
1202   bl->draw_gc = XCreateGC (defdsp, defwin, GCForeground, &gcv);
1203   XSetClipMask(defdsp, bl->draw_gc, bl->shape_mask);
1204   
1205   gcv.foreground = default_bg_pixel;
1206   gcv.function = GXcopy;
1207   bl->erase_gc = XCreateGC (defdsp, defwin, GCForeground, &gcv);
1208   XSetClipMask(defdsp, bl->erase_gc, bl->shape_mask);  
1209 }
1210
1211 static void 
1212 default_to_pixmaps(void)
1213 /* Make pixmaps out of default ball data stored in bubbles_default.c */
1214 {
1215   int i;
1216   Bubble_Step *pixmap_list = (Bubble_Step *)NULL;
1217   Bubble_Step *newpix, *tmppix;
1218   char **pixpt;
1219
1220   /* Make sure pixmaps are freed when program is terminated */
1221   /* This is when I hit ^C */
1222   if (signal(SIGINT, SIG_IGN) != SIG_IGN)
1223     signal(SIGINT, onintr);
1224   /* xscreensaver sends SIGTERM */
1225   if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
1226     signal(SIGTERM, onintr);
1227 #ifdef DEBUG
1228   if (signal(SIGSEGV, SIG_IGN) != SIG_IGN) {
1229     printf("Setting signal handler for SIGSEGV\n");
1230     signal(SIGSEGV, onsegv);
1231   } else {
1232     printf("Didn't set signal hanlder for SIGSEGV\n");
1233   }
1234 #endif /* DEBUG */
1235
1236   for (i = 0; i < num_default_bubbles; i++) {
1237     pixpt = default_bubbles[i];
1238     newpix = (Bubble_Step *)xmalloc(sizeof(Bubble_Step));
1239     make_pixmap_from_default(pixpt, newpix);
1240     /* Now add to list */
1241     if (pixmap_list == (Bubble_Step *)NULL) {
1242       pixmap_list = newpix;
1243     } else {
1244       tmppix = pixmap_list;
1245       while (tmppix->next != (Bubble_Step *)NULL)
1246         tmppix = tmppix->next;
1247       tmppix->next = newpix;
1248     }
1249     newpix->next = (Bubble_Step *)NULL;
1250   }
1251   
1252   /* Finally construct step_pixmaps[] */
1253   make_pixmap_array(pixmap_list);
1254 }
1255
1256 #endif /* NO_DEFAULT_BUBBLE */
1257
1258 #endif /* HAVE_XPM */
1259
1260 /* 
1261  * File I/O stuff
1262  */
1263
1264 #ifdef BUBBLES_IO
1265
1266 static DIR *
1267 my_opendir(name)
1268      char *name;
1269 /* Like opendir() but checks for things so we don't have to do it multiple
1270 times in the code. */
1271 {
1272   DIR *rv;
1273
1274   if (name == (char *)NULL) {
1275     fprintf(stderr, "NULL directory name\n");
1276     return (DIR *)NULL;
1277   }
1278   
1279   if ((rv = opendir(name)) == NULL) {
1280     perror(name);
1281     return (DIR *)NULL;
1282   }
1283
1284   return rv;
1285 }
1286
1287 static int
1288 regular_file(name)
1289      char *name;
1290 /* Check to see if we can use the named file.  This was broken under Linux
1291 1.3.45 but seems to be okay under 1.3.54.  The parameter "name" was being
1292 trashed if the file didn't exist.  Yeah, I know 1.3.x are development
1293 kernels....
1294 */
1295 {
1296   int fd;
1297
1298   if ((fd = open(name, O_RDONLY)) == -1) {
1299     perror(name);
1300     return 0;
1301   } else {
1302     close(fd);
1303     return 1;
1304   }
1305 }
1306
1307 static char *
1308 get_random_name(dir)
1309      char *dir;
1310 /* Pick an appropriate file at random out of the files in the directory dir */
1311 {
1312   STRUCT_DIRENT *dp;
1313   DIR *dfd;
1314   int numentries = 0;
1315   int entnum;
1316   int x;
1317   char buf[PATH_BUF_SIZE];
1318   char *rv;
1319
1320   if ((dfd = my_opendir(dir)) == (DIR *)NULL)
1321     return (char *)NULL;
1322
1323   while ((dp = readdir(dfd)) != NULL) {
1324     if ((strcmp(DIRENT_NAME, ".") == 0) || (strcmp(DIRENT_NAME, "..") == 0))
1325       continue;
1326     if ((strlen(dir)+strlen(DIRENT_NAME)+2) > 1024) {
1327       fprintf(stderr, "name %s/%s too long\n", dir, DIRENT_NAME);
1328       continue;
1329     }
1330     if (sprintf(buf, "%s/%s", dir, DIRENT_NAME) > (PATH_BUF_SIZE-1)) {
1331       fprintf(stderr, "path buffer overflowed in get_random_name()\n");
1332       continue;
1333     }
1334     if (regular_file(buf))
1335       ++numentries;
1336   }
1337   closedir(dfd);
1338   if (numentries == 0) {
1339     fprintf(stderr, "No suitable files found in %s\n", dir);
1340     return (char *)NULL;
1341   }
1342   entnum = ya_random() % numentries;
1343   x = 0;
1344
1345   if ((dfd = my_opendir(dir)) == (DIR *)NULL)
1346     return (char *)NULL;
1347   while ((dp = readdir(dfd)) != NULL) {
1348     if ((strcmp(DIRENT_NAME, ".") == 0) || (strcmp(DIRENT_NAME, "..") == 0))
1349       continue;
1350     if ((strlen(dir)+strlen(DIRENT_NAME)+2) > 1024) {
1351       /* We warned about this previously */
1352       continue;
1353     }
1354     if (sprintf(buf, "%s/%s", dir, DIRENT_NAME) > (PATH_BUF_SIZE-1)) {
1355       fprintf(stderr, "path buffer overflowed in get_random_name()\n");
1356       continue;
1357     }
1358     if (regular_file(buf)) {
1359       if (x == entnum) {
1360         rv = (char *)xmalloc(1024 * sizeof(char));
1361         strcpy(rv, buf);
1362         closedir(dfd);
1363         return rv;
1364       }
1365       ++x;
1366     }
1367   }
1368   /* We've screwed up if we reach here - someone must have deleted all the
1369      files while we were counting them... */
1370   fprintf(stderr, "get_random_name(): Oops!\n");
1371   exit(1);
1372 }
1373
1374 static int
1375 read_line(fd, buf, bufsize)
1376      int fd;
1377      char **buf;
1378      int bufsize;
1379 /* A line is read from fd until a '\n' is found or EOF is reached.  (*buf)
1380 is initially of length bufsize and is extended by bufsize chars if need
1381 be (for as many times as it takes). */
1382 {
1383   char x;
1384   int pos = 0;
1385   int size = bufsize;
1386   int rv;
1387   char *newbuf;
1388
1389   while (1) {
1390     rv = read(fd, &x, 1);
1391     if (rv == -1) {
1392       perror("read_line(): ");
1393       return IO_ERROR;
1394     } else if (rv == 0) {
1395       (*buf)[pos] = '\0';
1396       return EOF_REACHED;
1397     } else if (x == '\n') {
1398       (*buf)[pos] = '\0';
1399       return LINE_READ;
1400     } else {
1401       (*buf)[pos++] = x;
1402       if (pos == (size - 1)) {
1403         /* We've come to the end of the space */
1404         newbuf = (char *)xmalloc((size+bufsize) * sizeof(char));
1405         strncpy(newbuf, *buf, (size - 1));
1406         free(*buf);
1407         *buf = newbuf;
1408         size += bufsize;
1409       }
1410     }
1411   }
1412 }
1413
1414 static int
1415 create_temp_file(name)
1416      char **name;
1417 /* Create a temporary file in /tmp and return a filedescriptor to it */
1418 {
1419   int rv;
1420  
1421   if (*name != (char *)NULL)
1422     free(*name);
1423
1424   if ((*name = tempnam("/tmp", "abxdfes")) == (char *)NULL) {
1425     fprintf(stderr, "Couldn't make new temporary file\n");
1426     exit(1);
1427   }
1428 /*   printf("Temp file created : %s\n", *name); */
1429   if ((rv = creat(*name, 0644)) == -1) {
1430     fprintf(stderr, "Couldn't open temporary file\n");
1431     exit(1);
1432   }
1433   
1434   return rv;
1435 }
1436
1437
1438 #ifdef BUBBLES_IO
1439 static void 
1440 make_pixmap_from_file(fname, bl)
1441      char *fname;
1442      Bubble_Step *bl;
1443 /* Read the pixmap in file fname into structure bl which must already
1444  be allocated. */
1445 {
1446   int result;
1447   XGCValues gcv;
1448
1449   if (bl == (Bubble_Step *)NULL) {
1450     fprintf(stderr, "NULL pointer passed to make_pixmap()\n");
1451     exit(1);
1452   }
1453
1454   bl->xpmattrs.closeness = 40000;
1455   bl->xpmattrs.valuemask = XpmColormap | XpmCloseness;
1456   bl->xpmattrs.colormap = defcmap;
1457
1458   result = XpmReadFileToPixmap(defdsp, defwin, fname, &bl->ball, 
1459                                &bl->shape_mask, &bl->xpmattrs);
1460
1461   switch(result) {
1462   case XpmColorError:
1463     fprintf(stderr, "xpm: color substitution performed\n");
1464     /* fall through */
1465   case XpmSuccess:
1466     bl->radius = MAX(bl->xpmattrs.width, bl->xpmattrs.height) / 2;
1467     bl->area = calc_bubble_area(bl->radius);
1468     break;
1469   case XpmColorFailed:
1470     fprintf(stderr, "xpm: color allocation failed\n");
1471     exit(1);
1472   case XpmNoMemory:
1473     fprintf(stderr, "xpm: out of memory\n");
1474     exit(1);
1475   default:
1476     fprintf(stderr, "xpm: unknown error code %d\n", result);
1477     exit(1);
1478   }
1479   
1480   gcv.plane_mask = AllPlanes;
1481   gcv.foreground = default_fg_pixel;
1482   gcv.function = GXcopy;
1483   bl->draw_gc = XCreateGC (defdsp, defwin, GCForeground, &gcv);
1484   XSetClipMask(defdsp, bl->draw_gc, bl->shape_mask);
1485   
1486   gcv.foreground = default_bg_pixel;
1487   gcv.function = GXcopy;
1488   bl->erase_gc = XCreateGC (defdsp, defwin, GCForeground, &gcv);
1489   XSetClipMask(defdsp, bl->erase_gc, bl->shape_mask);  
1490 }
1491 #endif /* BUBBLES_IO */
1492
1493 static void 
1494 read_file_to_pixmaps(fname)
1495      char *fname;
1496 /* Read the pixmaps contained in the file fname into memory.  THESE SHOULD
1497 BE UNCOMPRESSED AND READY TO GO! */
1498 {
1499   int fd, tmpfd=0, rv;
1500   int inxpm = 0;
1501   int xpmseen = 0;
1502   char *buf = (char *)NULL;
1503   char *tmpname = (char *)NULL;
1504   Bubble_Step *pixmap_list = (Bubble_Step *)NULL;
1505   Bubble_Step *newpix, *tmppix;
1506
1507   /* We first create a linked list of pixmaps before allocating
1508      memory for the array */
1509
1510   if ((fd = open(fname, O_RDONLY)) == -1) {
1511     fprintf(stderr, "Couldn't open %s\n", fname);
1512     exit(1);
1513   }
1514
1515   /* Make sure pixmaps are freed when program is terminated */
1516   /* This is when I hit ^C */
1517   if (signal(SIGINT, SIG_IGN) != SIG_IGN)
1518     signal(SIGINT, onintr);
1519   /* xscreensaver sends SIGTERM */
1520   if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
1521     signal(SIGTERM, onintr);
1522 #ifdef DEBUG
1523   if (signal(SIGSEGV, SIGN_IGN) != SIG_IGN)
1524     signal(SIGSEGV, onsegv);
1525 #endif /* DEBUG */
1526   
1527   while (1) {
1528     if (inxpm == 2)
1529       break;
1530
1531     buf = (char *)malloc(READ_LINE_BUF_SIZE * sizeof(char));
1532
1533     switch ((rv = read_line(fd, &buf, READ_LINE_BUF_SIZE))) {
1534     case IO_ERROR:
1535       fprintf(stderr, "An I/O error occurred\n");
1536       exit(1);
1537     case EOF_REACHED:
1538       if (inxpm) {
1539         fprintf(stderr, "EOF occurred inside an XPM block\n");
1540         exit(1);
1541       } else
1542         inxpm = 2;
1543       break;
1544     case LINE_READ:
1545       if (inxpm) {
1546         if (strncmp("};", buf, 2) == 0) {
1547           inxpm = 0;
1548           write(tmpfd, buf, strlen(buf));
1549           write(tmpfd, "\n", 1);
1550           close(tmpfd);
1551           /* Now process the tmpfile */
1552           newpix = (Bubble_Step *)xmalloc(sizeof(Bubble_Step));
1553           make_pixmap_from_file(tmpname, newpix);
1554           /* Now add to list */
1555           if (pixmap_list == (Bubble_Step *)NULL) {
1556             pixmap_list = newpix;
1557           } else {
1558             tmppix = pixmap_list;
1559             while (tmppix->next != (Bubble_Step *)NULL)
1560               tmppix = tmppix->next;
1561             tmppix->next = newpix;
1562           }
1563           newpix->next = (Bubble_Step *)NULL;
1564           unlink(tmpname);
1565         } else {
1566           write(tmpfd, buf, strlen(buf));
1567           write(tmpfd, "\n", 1);
1568         }
1569       } else {
1570         if (strncmp("/* XPM */", buf, 9) == 0) {
1571           tmpfd = create_temp_file(&tmpname);
1572 /* This proves XPM's performance is kinda pathetic */
1573 #ifdef DEBUG
1574           printf("New XPM detected : %s, fd=%d\n", tmpname, tmpfd); 
1575 #endif /* DEBUG */
1576           inxpm = 1;
1577           xpmseen = 1;
1578         }
1579         write(tmpfd, buf, strlen(buf));
1580         write(tmpfd, "\n", 1);
1581       }
1582       break;
1583     default:
1584       fprintf(stderr, "read_line returned unknown code %d\n", rv);
1585       exit(1);
1586     }
1587
1588     free(buf);
1589   }
1590
1591   close(fd);
1592   if (buf != (char *)NULL)
1593     free(buf);
1594   if (tmpname != (char *)NULL)
1595     free(tmpname);
1596
1597   if (! xpmseen) {
1598     fprintf(stderr, "There was no XPM data in the file %s\n", fname);
1599     exit(1);
1600   }
1601
1602   /* Finally construct step_pixmaps[] */
1603   make_pixmap_array(pixmap_list);
1604 }
1605
1606 static void 
1607 shell_exec(command)
1608      char *command;
1609 /* Forks a shell to execute "command" then waits for command to finish */
1610 {
1611   int pid, status, wval;
1612
1613   switch(pid=fork()) {
1614   case 0:
1615     if (execlp(BOURNESH, BOURNESH, "-c", command, (char *)NULL) == -1) {
1616       fprintf(stderr, "Couldn't exec shell %s\n", BOURNESH);
1617       exit(1);
1618     }
1619     /* fall through if execlp() fails */
1620   case -1:
1621     /* Couldn't fork */
1622     perror(progname);
1623     exit(1);
1624   default:
1625     while ((wval = wait(&status)) != pid)
1626       if (wval == -1) {
1627         perror(progname);
1628         exit(1);
1629       }    
1630   }
1631 }
1632
1633 static void 
1634 uncompress_file(current, namebuf)
1635      char *current;
1636      char *namebuf;
1637 /* If the file current is compressed (i.e. its name ends in .gz or .Z,
1638 no check is made to see if it is actually a compressed file...) then a
1639 new temporary file is created for it and it is decompressed into there,
1640 returning the name of the file to namebuf, else current is returned in
1641 namebuf */
1642 {
1643   int fd;
1644   char *tname = (char *)NULL;
1645   char argbuf[COMMAND_BUF_SIZE];
1646
1647   if (((strlen(current) >=4) && 
1648        (strncmp(&current[strlen(current)-3], ".gz", 3) == 0)) || 
1649       ((strlen(current) >=3) && 
1650        (strncmp(&current[strlen(current)-2], ".Z", 2) == 0))) {
1651     fd = create_temp_file(&tname);
1652     /* close immediately but don't unlink so we should have a zero length
1653        file in /tmp which we can append to */
1654     close(fd);
1655     if (sprintf(argbuf, "%s -dc %s > %s", GZIP, current, tname) > 
1656         (COMMAND_BUF_SIZE-1)) {
1657       fprintf(stderr, "command buffer overflowed in uncompress_file()\n");
1658       exit(1);
1659     }
1660     shell_exec(argbuf);
1661     strcpy(namebuf, tname);
1662   } else {
1663     strcpy(namebuf, current);
1664   }
1665   return;
1666 }
1667
1668 #endif /* BUBBLES_IO */
1669
1670 /* 
1671  * Main stuff 
1672  */
1673
1674
1675 static void 
1676 get_resources(dpy)
1677      Display *dpy;
1678 /* Get the appropriate X resources and warn about any inconsistencies. */
1679 {
1680   Bool nodelay;
1681 #ifdef BUBBLES_IO
1682 #ifdef HAVE_XPM
1683   char *dirname;
1684 #else
1685   char *foo, *bar;
1686 #endif /* HAVE_XPM */
1687 #endif /* BUBBLES_IO */
1688
1689   threed = get_boolean_resource("3D", "Boolean");
1690   quiet = get_boolean_resource("quiet", "Boolean");
1691   simple = get_boolean_resource("simple", "Boolean");
1692   /* Forbid rendered bubbles on monochrome displays */
1693   if ((mono_p) && (! simple)) {
1694     if (! quiet)
1695       fprintf(stderr, "Rendered bubbles not supported on monochrome \
1696 displays\n");
1697     simple = True;
1698   }
1699   delay = get_integer_resource("delay", "Integer");
1700   nodelay = get_boolean_resource("nodelay", "Boolean");
1701   if (nodelay)
1702     delay = 0;
1703   if (delay < 0)
1704     delay = 0;
1705
1706   default_fg_pixel = get_pixel_resource ("foreground", "Foreground", dpy,
1707                                          DefaultColormap(dpy, 
1708                                                          DefaultScreen(dpy)));
1709   default_bg_pixel = get_pixel_resource ("background", "Background", dpy,
1710                                          DefaultColormap(dpy, 
1711                                                          DefaultScreen(dpy)));
1712
1713   if (simple) {
1714     /* This is easy */
1715     broken = get_boolean_resource("broken", "Boolean");
1716     if (broken)
1717       if (! quiet)
1718         fprintf(stderr, "-broken not available in simple mode\n");
1719   } else {
1720 #ifndef HAVE_XPM
1721     simple = 1;
1722 #else
1723     broken = get_boolean_resource("broken", "Boolean");
1724 #ifdef BUBBLES_IO
1725     pixmap_file = get_string_resource("file", "File");
1726     dirname = get_string_resource("directory", "Directory");    
1727 #ifdef NO_DEFAULT_BUBBLE
1728     /* Must specify -file or -directory if no default bubble compiled in */
1729     if (strcmp(pixmap_file, "(default)") != 0) {
1730     } else if (strcmp(dirname, "(default)") != 0) {
1731       if ((pixmap_file = get_random_name(dirname)) == (char *)NULL) {
1732         /* Die if we can't open directory - make it consistent with -file
1733            when it fails, rather than falling back to default. */
1734         exit(1);
1735       }
1736     } else {
1737       fprintf(stderr, "No default bubble compiled in - use -file or \
1738 -directory\n");
1739       exit(1);
1740     }
1741 #else
1742     if (strcmp(pixmap_file, "(default)") != 0) {
1743     } else if (strcmp(dirname, "(default)") != 0) {
1744       if ((pixmap_file = get_random_name(dirname)) == (char *)NULL) {
1745         exit(1);
1746       }
1747     } else {
1748       /* Use default bubble */
1749       use_default_bubble = 1;
1750     }
1751 #endif /* NO_DEFAULT_BUBBLE */
1752 #else 
1753     use_default_bubble = 1;
1754 #endif /* BUBBLES_IO */
1755 #endif /* HAVE_XPM */
1756   }
1757 }
1758
1759 static void
1760 init_bubbles (dpy, window)
1761      Display *dpy;
1762      Window window;
1763 {
1764   XGCValues gcv;
1765   XWindowAttributes xgwa;
1766   int i;
1767 #ifdef BUBBLES_IO
1768   char uncompressed[1024];
1769 #endif /* BUBBLES_IO */
1770
1771   defdsp = dpy;
1772   defwin = window;
1773
1774   ya_rand_init(0);
1775
1776   get_resources(dpy);
1777
1778   XGetWindowAttributes (dpy, window, &xgwa);
1779
1780 #ifdef DEBUG
1781   printf("sizof(int) on this platform is %d\n", sizeof(int));
1782   printf("sizof(long) on this platform is %d\n", sizeof(long));
1783 #endif /* DEBUG */
1784
1785   screen_width = xgwa.width;
1786   screen_height = xgwa.height;
1787   screen_depth = xgwa.depth;
1788   defcmap = xgwa.colormap;
1789
1790   if (simple) {
1791     /* These are pretty much plucked out of the air */
1792     bubble_min_radius = (int)(0.006*(double)(MIN(screen_width, 
1793                                                  screen_height)));
1794     bubble_max_radius = (int)(0.045*(double)(MIN(screen_width,
1795                                                  screen_height)));
1796     /* Some trivial values */
1797     if (bubble_min_radius < 1)
1798       bubble_min_radius = 1;
1799     if (bubble_max_radius <= bubble_min_radius)
1800       bubble_max_radius = bubble_min_radius + 1;
1801
1802     mesh_length = (2 * bubble_max_radius) + 3;
1803
1804     /* store area of each bubble of certain radius as number of 1/10s of
1805        a pixel area.  PI is defined in <math.h> */
1806     bubble_areas = (long *)xmalloc((bubble_max_radius + 2) * sizeof(int));
1807     for (i = 0; i < bubble_min_radius; i++)
1808       bubble_areas[i] = 0;
1809     for (i = bubble_min_radius; i <= (bubble_max_radius+1); i++)
1810       bubble_areas[i] = calc_bubble_area(i);
1811
1812     mesh_length = (2 * bubble_max_radius) + 3;
1813   } else {
1814 #ifndef HAVE_XPM
1815     fprintf(stderr, "Bug: simple mode code not set but HAVE_XPM not \
1816 defined\n");
1817     exit(1);
1818 #else
1819     /* Make sure all #ifdef sort of things have been taken care of in
1820        get_resources(). */
1821     if (use_default_bubble) {
1822 #ifdef NO_DEFAULT_BUBBLE
1823       fprintf(stderr, "Bug: use_default_bubble and NO_DEFAULT_BUBBLE both \
1824 defined\n");
1825       exit(1);
1826 #else
1827       default_to_pixmaps();
1828 #endif /* NO_DEFAULT_BUBBLE */
1829
1830       /* Set mesh length */
1831       mesh_length = (2 * step_pixmaps[num_bubble_pixmaps-1]->radius) + 3;
1832     } else {
1833 #ifdef BUBBLES_IO
1834       if (! regular_file(pixmap_file)) {
1835         /* perror() in regular_file printed error message */
1836         exit(1);
1837       }
1838       uncompress_file(pixmap_file, uncompressed);
1839       read_file_to_pixmaps(uncompressed);
1840       if (strcmp(pixmap_file, uncompressed))
1841         unlink(uncompressed);
1842
1843       mesh_length = (2 * step_pixmaps[num_bubble_pixmaps-1]->radius) + 3;
1844 #else
1845       fprintf(stderr, "Bug: use_default_bubble is not defined yet I/O is not \
1846 compiled in\n");
1847       exit(1);
1848 #endif /* BUBBLES_IO */
1849     }
1850 #endif /* HAVE_XPM */
1851
1852     /* Am I missing something in here??? */
1853   }
1854
1855   mesh_width = (screen_width / mesh_length) + 1;
1856   mesh_height = (screen_height / mesh_length) + 1;
1857   mesh_cells = mesh_width * mesh_height;
1858   init_mesh();
1859
1860   calculate_adjacent_list();
1861
1862   adjust_areas();
1863
1864   /* Graphics contexts for simple mode */
1865   if (simple) {
1866     gcv.foreground = default_fg_pixel;
1867     draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
1868     gcv.foreground = default_bg_pixel;
1869     erase_gc = XCreateGC (dpy, window, GCForeground, &gcv);
1870   }
1871
1872   XClearWindow (dpy, window);
1873 }
1874
1875 static void 
1876 bubbles (dpy, window)
1877      Display *dpy;
1878      Window window;
1879 {
1880   Bubble *tmp;
1881
1882   tmp = new_bubble();
1883   add_to_mesh(tmp);
1884   insert_new_bubble(tmp);
1885
1886   XSync (dpy, True);
1887 }
1888
1889
1890 void 
1891 screenhack (dpy, window)
1892      Display *dpy;
1893      Window window;
1894 {
1895   init_bubbles (dpy, window);
1896   while (1) {
1897     bubbles (dpy, window);
1898     if (delay)
1899       usleep(delay);
1900   }
1901 }
1902