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