From http://www.jwz.org/xscreensaver/xscreensaver-5.16.tar.gz
[xscreensaver] / hacks / glx / sonar.c
1 /* sonar, Copyright (c) 1998-2012 Jamie Zawinski and Stephen Martin
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* Created in Apr 1998 by Stephen Martin <smartin@vanderfleet-martin.net>
13  * for the  RedHat Screensaver Contest
14  * Heavily hacked by jwz ever since.
15  * Rewritten in OpenGL by jwz, Aug 2008.
16  *
17  * This is an implementation of a general purpose reporting tool in the
18  * format of a Sonar display. It is designed such that a sensor is read
19  * on every movement of a sweep arm and the results of that sensor are
20  * displayed on the screen. The location of the display points (targets) on the
21  * screen are determined by the current localtion of the sweep and a distance
22  * value associated with the target. 
23  *
24  * Currently the only two sensors that are implemented are the simulator
25  * (the default) and the ping sensor. The simulator randomly creates a set
26  * of bogies that move around on the scope while the ping sensor can be
27  * used to display hosts on your network.
28  *
29  * The ping code is only compiled in if you define HAVE_ICMP or HAVE_ICMPHDR,
30  * because, unfortunately, different systems have different ways of creating
31  * these sorts of packets.
32  *
33  * In order to use the ping sensor on most systems, this program must be
34  * installed as setuid root, so that it can create an ICMP RAW socket.  Root
35  * privileges are disavowed shortly after startup (just after connecting to
36  * the X server and reading the resource database) so this is *believed* to
37  * be a safe thing to do, but it is usually recommended that you have as few
38  * setuid programs around as possible, on general principles.
39  *
40  * It is not necessary to make it setuid on MacOS systems, because on those
41  * systems, unprivileged programs can ping by using ICMP DGRAM sockets
42  * instead of ICMP RAW.
43  *
44  * It should be easy to extend this code to support other sorts of sensors.
45  * Some ideas:
46  *   - search the output of "netstat" for the list of hosts to ping;
47  *   - plot the contents of /proc/interrupts;
48  *   - plot the process table, by process size, cpu usage, or total time;
49  *   - plot the logged on users by idle time or cpu usage.
50  *
51  */
52
53 #define DEF_FONT "-*-lucidatypewriter-bold-r-normal-*-*-480-*-*-*-*-iso8859-1"
54 #define DEF_SPEED        "1.0"
55 #define DEF_SWEEP_SIZE   "0.3"
56 #define DEF_FONT_SIZE    "12"
57 #define DEF_TEAM_A_NAME  "F18"
58 #define DEF_TEAM_B_NAME  "MIG"
59 #define DEF_TEAM_A_COUNT "4"
60 #define DEF_TEAM_B_COUNT "4"
61 #define DEF_PING         "default"
62 #define DEF_PING_TIMEOUT "3000"
63 #define DEF_RESOLVE      "True"
64 #define DEF_TIMES        "True"
65 #define DEF_WOBBLE       "True"
66 #define DEF_DEBUG        "False"
67
68 #define DEFAULTS        "*delay:        30000       \n" \
69                         "*font:       " DEF_FONT   "\n" \
70                         "*showFPS:      False       \n" \
71                         "*wireframe:    False       \n" \
72
73
74 # define refresh_sonar 0
75 #undef countof
76 #define countof(x) (sizeof((x))/sizeof((*x)))
77
78 #ifdef HAVE_UNISTD_H
79 # include <unistd.h>   /* for setuid() */
80 #endif
81
82 #include "xlockmore.h"
83 #include "sonar.h"
84 #include "gltrackball.h"
85 #include "rotator.h"
86 #include "texfont.h"
87 #include <ctype.h>
88
89 #ifdef USE_GL /* whole file */
90
91 typedef struct {
92   double x,y,z;
93 } XYZ;
94
95 typedef struct {
96   GLXContext *glx_context;
97   trackball_state *trackball;
98   rotator *rot;
99   Bool button_down_p;
100
101   double start_time;
102   GLfloat sweep_offset;
103
104   GLuint screen_list, grid_list, sweep_list, table_list;
105   int screen_polys, grid_polys, sweep_polys, table_polys;
106   GLfloat sweep_th;
107   GLfloat line_thickness;
108
109   texture_font_data *texfont;
110
111   sonar_sensor_data *ssd;
112   char *error;
113
114   sonar_bogie *displayed;       /* on screen and fading */
115   sonar_bogie *pending;         /* returned by sensor, not yet on screen */
116
117 } sonar_configuration;
118
119 static sonar_configuration *sps = NULL;
120
121 static GLfloat speed;
122 static GLfloat sweep_size;
123 static GLfloat font_size;
124 static Bool resolve_p;
125 static Bool times_p;
126 static Bool wobble_p;
127 static Bool debug_p;
128
129 static char *team_a_name;
130 static char *team_b_name;
131 static int team_a_count;
132 static int team_b_count;
133 static int ping_timeout;
134 static char *ping_arg;
135
136 static XrmOptionDescRec opts[] = {
137   { "-speed",        ".speed",       XrmoptionSepArg, 0 },
138   { "-sweep-size",   ".sweepSize",   XrmoptionSepArg, 0 },
139   { "-font-size",    ".fontSize",    XrmoptionSepArg, 0 },
140   { "-team-a-name",  ".teamAName",   XrmoptionSepArg, 0 },
141   { "-team-b-name",  ".teamBName",   XrmoptionSepArg, 0 },
142   { "-team-a-count", ".teamACount",  XrmoptionSepArg, 0 },
143   { "-team-b-count", ".teamBCount",  XrmoptionSepArg, 0 },
144   { "-ping",         ".ping",        XrmoptionSepArg, 0 },
145   { "-ping-timeout", ".pingTimeout", XrmoptionSepArg, 0 },
146   { "-dns",          ".resolve",     XrmoptionNoArg, "True" },
147   { "+dns",          ".resolve",     XrmoptionNoArg, "False" },
148   { "-times",        ".times",       XrmoptionNoArg, "True" },
149   { "+times",        ".times",       XrmoptionNoArg, "False" },
150   { "-wobble",       ".wobble",      XrmoptionNoArg, "True" },
151   { "+wobble",       ".wobble",      XrmoptionNoArg, "False" },
152   { "-debug",        ".debug",       XrmoptionNoArg, "True" },
153 };
154
155 static argtype vars[] = {
156   {&speed,        "speed",       "Speed",       DEF_SPEED,        t_Float},
157   {&sweep_size,   "sweepSize",   "SweepSize",   DEF_SWEEP_SIZE,   t_Float},
158   {&font_size,    "fontSize",    "FontSize",    DEF_FONT_SIZE,    t_Float},
159   {&team_a_name,  "teamAName",   "TeamName",    DEF_TEAM_A_NAME,  t_String},
160   {&team_b_name,  "teamBName",   "TeamName",    DEF_TEAM_B_NAME,  t_String},
161   {&team_a_count, "teamACount",  "TeamCount",   DEF_TEAM_A_COUNT, t_Int},
162   {&team_b_count, "teamBCount",  "TeamCount",   DEF_TEAM_A_COUNT, t_Int},
163   {&ping_arg,     "ping",        "Ping",        DEF_PING,         t_String},
164   {&ping_timeout, "pingTimeout", "PingTimeout", DEF_PING_TIMEOUT, t_Int},
165   {&resolve_p,    "resolve",     "Resolve",     DEF_RESOLVE,      t_Bool},
166   {&times_p,      "times",       "Times",       DEF_TIMES,        t_Bool},
167   {&wobble_p,     "wobble",      "Wobble",      DEF_WOBBLE,       t_Bool},
168   {&debug_p,      "debug",       "Debug",       DEF_DEBUG,        t_Bool},
169 };
170
171 ENTRYPOINT ModeSpecOpt sonar_opts = {countof(opts), opts, countof(vars), vars, NULL};
172
173
174 static int
175 draw_screen (ModeInfo *mi, Bool mesh_p, Bool sweep_p)
176 {
177   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
178   int wire = MI_IS_WIREFRAME(mi);
179   int polys = 0;
180   int i;
181   int th_steps, r_steps, r_skip, th_skip, th_skip2, outer_r;
182   GLfloat curvature = M_PI * 0.4;
183   GLfloat r0, r1, z0, z1, zoff;
184   XYZ *ring;
185
186   static const GLfloat glass[4]  = {0.0, 0.4, 0.0, 0.5};
187   static const GLfloat lines[4]  = {0.0, 0.7, 0.0, 0.5};
188   static const GLfloat sweepc[4] = {0.2, 1.0, 0.2, 0.5};
189   static const GLfloat spec[4]   = {1.0, 1.0, 1.0, 1.0};
190   static const GLfloat shiny     = 20.0;
191
192   if (wire && !(mesh_p || sweep_p)) return 0;
193
194   glDisable (GL_TEXTURE_2D);
195
196   glFrontFace (GL_CCW);
197   th_steps = 36 * 4;    /* must be a multiple of th_skip2 divisor */
198   r_steps = 40;
199   r_skip = 1;
200   th_skip = 1;
201   th_skip2 = 1;
202   outer_r = 0;
203
204   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
205   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
206   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mesh_p ? lines : glass);
207   if (wire) glColor3fv (lines);
208
209   if (mesh_p) 
210     {
211       th_skip  = th_steps / 12;
212       th_skip2 = th_steps / 36;
213       r_skip = r_steps / 3;
214       outer_r = r_steps * 0.93;
215
216       if (! wire)
217         glLineWidth (sp->line_thickness);
218     }
219
220   ring = (XYZ *) calloc (th_steps, sizeof(*ring));
221
222   for (i = 0; i < th_steps; i++)
223     {
224       double a = M_PI * 2 * i / th_steps;
225       ring[i].x = cos(a);
226       ring[i].y = sin(a);
227     }
228
229   /* place the bottom of the disc on the xy plane. */
230   zoff = cos (curvature/2 * (M_PI/2)) / 2;
231
232   for (i = r_steps; i > 0; i--)
233     {
234       int j0, j1;
235
236       r0 = i     / (GLfloat) r_steps;
237       r1 = (i+1) / (GLfloat) r_steps;
238
239       if (r1 > 1) r1 = 1; /* avoid asin lossage */
240
241       z0 = cos (curvature/2 * asin (r0)) / 2 - zoff;
242       z1 = cos (curvature/2 * asin (r1)) / 2 - zoff;
243
244       glBegin(wire || mesh_p ? GL_LINES : GL_QUAD_STRIP);
245       for (j0 = 0; j0 <= th_steps; j0++)
246         {
247           if (mesh_p && 
248               (i < outer_r
249                ? (j0 % th_skip != 0)
250                : (j0 % th_skip2 != 0)))
251             continue;
252
253           if (sweep_p)
254             {
255               GLfloat color[4];
256               GLfloat r = 1 - (j0 / (GLfloat) (th_steps * sweep_size));
257 #if 0
258               color[0] = glass[0] + (sweepc[0] - glass[0]) * r;
259               color[1] = glass[1] + (sweepc[1] - glass[1]) * r;
260               color[2] = glass[2] + (sweepc[2] - glass[2]) * r;
261               color[3] = glass[3];
262 #else
263               color[0] = sweepc[0];
264               color[1] = sweepc[1];
265               color[2] = sweepc[2];
266               color[3] = r;
267 #endif
268               glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
269             }
270
271           j1 = j0 % th_steps;
272           glNormal3f (r0 * ring[j1].x, r0 * ring[j1].y, z0);
273           glVertex3f (r0 * ring[j1].x, r0 * ring[j1].y, z0);
274           glNormal3f (r1 * ring[j1].x, r1 * ring[j1].y, z1);
275           glVertex3f (r1 * ring[j1].x, r1 * ring[j1].y, z1);
276           polys++;
277
278           if (sweep_p && j0 >= th_steps * sweep_size)
279             break;
280           if (sweep_p && wire)
281             break;
282         }
283       glEnd();
284
285       if (mesh_p &&
286           (i == outer_r ||
287            i == r_steps ||
288            (i % r_skip == 0 &&
289             i < r_steps - r_skip)))
290         {
291           glBegin(GL_LINE_LOOP);
292           for (j0 = 0; j0 < th_steps; j0++)
293             {
294               glNormal3f (r0 * ring[j0].x, r0 * ring[j0].y, z0);
295               glVertex3f (r0 * ring[j0].x, r0 * ring[j0].y, z0);
296               polys++;
297             }
298           glEnd();
299         }
300     }
301
302   /* one more polygon for the middle */
303   if (!wire && !sweep_p)
304     {
305       glBegin(wire || mesh_p ? GL_LINE_LOOP : GL_POLYGON);
306       glNormal3f (0, 0, 1);
307       for (i = 0; i < th_steps; i++)
308         {
309           glNormal3f (r0 * ring[i].x, r0 * ring[i].y, z0);
310           glVertex3f (r0 * ring[i].x, r0 * ring[i].y, z0);
311         }
312       polys++;
313       glEnd();
314     }
315
316   free (ring);
317
318   return polys;
319 }
320
321
322 static int
323 draw_text (ModeInfo *mi, const char *string, GLfloat r, GLfloat th, 
324            GLfloat ttl, GLfloat size)
325 {
326   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
327   int wire = MI_IS_WIREFRAME(mi);
328   int polys = 0;
329   GLfloat font_scale = 0.001 * (size > 0 ? size : font_size) / 14.0;
330   int lines = 0, max_w = 0, lh = 0;
331   char *string2 = strdup (string);
332   char *token = string2;
333   char *line;
334   GLfloat color[4];
335
336   if (size <= 0)   /* if size not specified, draw in yellow with alpha */
337     {
338       color[0] = 1;
339       color[1] = 1;
340       color[2] = 0;
341       color[3] = (ttl / (M_PI * 2)) * 1.2;
342       if (color[3] > 1) color[3] = 1;
343
344       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
345       if (wire)
346         glColor3f (color[0]*color[3], color[1]*color[3], color[2]*color[3]);
347     }
348
349   while ((line = strtok (token, "\r\n")))
350     {
351       int w = texture_string_width (sp->texfont, line, &lh);
352       if (w > max_w) max_w = w;
353       lines++;
354       token = 0;
355     }
356
357   glPushMatrix();
358   glTranslatef (r * cos (th), r * sin(th), 0);
359   glScalef (font_scale, font_scale, font_scale);
360
361   if (size <= 0)                /* Draw the dot */
362     {
363       GLfloat s = font_size * 1.7;
364       glDisable (GL_TEXTURE_2D);
365       glFrontFace (GL_CW);
366       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
367       glVertex3f (0, s, 0);
368       glVertex3f (s, s, 0);
369       glVertex3f (s, 0, 0);
370       glVertex3f (0, 0, 0);
371       glEnd();
372       glTranslatef (-max_w/2, -lh, 0);
373     }
374   else
375     glTranslatef (-max_w/2, -lh/2, 0);
376
377   /* draw each line, centered */
378   if (! wire) glEnable (GL_TEXTURE_2D);
379   free (string2);
380   string2 = strdup (string);
381   token = string2;
382   while ((line = strtok (token, "\r\n")))
383     {
384       int w = texture_string_width (sp->texfont, line, 0);
385       glPushMatrix();
386       glTranslatef ((max_w-w)/2, 0, 0);
387
388       if (wire)
389         {
390           glBegin (GL_LINE_LOOP);
391           glVertex3f (0, 0, 0);
392           glVertex3f (w, 0, 0);
393           glVertex3f (w, lh, 0);
394           glVertex3f (0, lh, 0);
395           glEnd();
396         }
397       else
398         {
399           glFrontFace (GL_CW);
400           print_texture_string (sp->texfont, line);
401         }
402       glPopMatrix();
403       glTranslatef (0, -lh, 0);
404       polys++;
405       token = 0;
406     }
407   glPopMatrix();
408
409   free (string2);
410
411   if (! wire) glEnable (GL_DEPTH_TEST);
412
413   return polys;
414 }
415
416
417 /* There's a disc with a hole in it around the screen, to act as a mask
418    preventing slightly off-screen bogies from showing up.  This clips 'em.
419  */
420 static int
421 draw_table (ModeInfo *mi)
422 {
423   /*sonar_configuration *sp = &sps[MI_SCREEN(mi)];*/
424   int wire = MI_IS_WIREFRAME(mi);
425   int polys = 0;
426   int i;
427   int th_steps = 36 * 4;    /* same as in draw_screen */
428
429   static const GLfloat color[4]  = {0.0, 0.0, 0.0, 1.0};
430   static const GLfloat text[4]   = {0.15, 0.15, 0.15, 1.0};
431   static const GLfloat spec[4]   = {0.0, 0.0, 0.0, 1.0};
432   static const GLfloat shiny     = 0.0;
433
434   if (wire) return 0;
435
436   glDisable (GL_TEXTURE_2D);
437
438   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
439   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
440   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
441
442   glFrontFace (GL_CCW);
443   glBegin(wire ? GL_LINES : GL_QUAD_STRIP);
444   glNormal3f (0, 0, 1);
445   for (i = 0; i <= th_steps; i++)
446     {
447       double a = M_PI * 2 * i / th_steps;
448       double x = cos(a);
449       double y = sin(a);
450       glVertex3f (x, y, 0);
451       glVertex3f (x*10, y*10, 0);
452       polys++;
453     }
454   glEnd();
455
456   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, text);
457   glTranslatef (0, 0, 0.01);
458   for (i = 0; i < 360; i += 10)
459     {
460       char buf[10];
461       GLfloat a = M_PI/2 - (i / 180.0 * M_PI);
462       sprintf (buf, "%d", i);
463       polys += draw_text (mi, buf, 1.07, a, 0, 10.0);
464     }
465
466   return polys;
467 }
468
469
470 static int
471 draw_bogies (ModeInfo *mi)
472 {
473   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
474   int polys = 0;
475   sonar_bogie *b;
476
477   for (b = sp->displayed; b; b = b->next)
478     {
479       char *s = (char *) 
480         malloc (strlen (b->name) + (b->desc ? strlen(b->desc) : 0) + 3);
481       strcpy (s, b->name);
482       if (b->desc)
483         {
484           strcat (s, "\n");
485           strcat (s, b->desc);
486         }
487       polys += draw_text (mi, s, b->r, b->th, b->ttl, -1);
488       free (s);
489
490       /* Move *very slightly* forward so that the text is not all in the
491          same plane: this prevents flickering with overlapping text as
492          the textures fight for priority. */
493       glTranslatef(0, 0, 0.00002);
494     }
495
496   return polys;
497 }
498
499
500 /* called from sonar-sim.c and sonar-icmp.c */
501 sonar_bogie *
502 copy_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
503 {
504   sonar_bogie *b2 = (sonar_bogie *) calloc (1, sizeof(*b2));
505   b2->name = strdup (b->name);
506   b2->desc = b->desc ? strdup (b->desc) : 0;
507   b2->r    = b->r;
508   b2->th   = b->th;
509   b2->ttl  = b->ttl;
510   /* does not copy b->closure */
511
512   /* Take this opportunity to normalize 'th' to the range [0-2pi). */
513   while (b2->th < 0)       b2->th += M_PI*2;
514   while (b2->th >= M_PI*2) b2->th -= M_PI*2;
515
516   return b2;
517 }
518
519
520 /* called from sonar-icmp.c */
521 void
522 free_bogie (sonar_sensor_data *ssd, sonar_bogie *b)
523 {
524   if (b->closure)
525     ssd->free_bogie_cb (ssd, b->closure);
526   free (b->name);
527   if (b->desc) free (b->desc);
528   free (b);
529 }
530
531 /* removes it from the list and frees it
532  */
533 static void
534 delete_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
535               sonar_bogie **from_list)
536 {
537   sonar_bogie *ob, *prev;
538   for (prev = 0, ob = *from_list; ob; prev = ob, ob = ob->next)
539     if (ob == b)
540       {
541         if (prev)
542           prev->next = b->next;
543         else
544           (*from_list) = b->next;
545         free_bogie (ssd, b);
546         break;
547       }
548 }
549
550
551 /* copies the bogie and adds it to the list.
552    if there's another bogie there with the same name, frees that one.
553  */
554 static void
555 copy_and_insert_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
556                        sonar_bogie **to_list)
557 {
558   sonar_bogie *ob, *next;
559   if (!b) abort();
560   for (ob = *to_list, next = ob ? ob->next : 0; 
561        ob; 
562        ob = next, next = ob ? ob->next : 0)
563     {
564       if (ob == b) abort();   /* this will end badly */
565       if (!strcmp (ob->name, b->name))  /* match! */
566         {
567           delete_bogie (ssd, ob, to_list);
568           break;
569         }
570     }
571
572   b = copy_bogie (ssd, b);
573   b->next = *to_list;
574   *to_list = b;
575 }
576
577
578 static void
579 update_sensor_data (sonar_configuration *sp)
580 {
581   sonar_bogie *new_list = sp->ssd->scan_cb (sp->ssd);
582   sonar_bogie *b2;
583
584   /* If a bogie exists in 'new_list' but not 'pending', add it.
585      If a bogie exists in both, update it in 'pending'.
586    */
587   for (b2 = new_list; b2; b2 = b2->next)
588     {
589       if (debug_p > 2)
590         fprintf (stderr, "%s:   updated: %s (%5.2f %5.2f %5.2f)\n", 
591                  progname, b2->name, b2->r, b2->th, b2->ttl);
592       copy_and_insert_bogie (sp->ssd, b2, &sp->pending);
593     }
594   if (debug_p > 2) fprintf (stderr, "\n");
595 }
596
597
598 /* Returns whether the given angle lies between two other angles.
599    When those angles cross 0, it assumes the wedge is the smaller one.
600    That is: 5 lies between 10 and 350 degrees (a 20 degree wedge).
601  */
602 static Bool
603 point_in_wedge (GLfloat th, GLfloat low, GLfloat high)
604 {
605   if (low < high)
606     return (th > low && th <= high);
607   else
608     return (th <= high || th > low);
609 }
610
611
612 /* Returns the current time in seconds as a double.
613  */
614 static double
615 double_time (void)
616 {
617   struct timeval now;
618 # ifdef GETTIMEOFDAY_TWO_ARGS
619   struct timezone tzp;
620   gettimeofday(&now, &tzp);
621 # else
622   gettimeofday(&now);
623 # endif
624
625   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
626 }
627
628
629 static void
630 sweep (sonar_configuration *sp)
631 {
632   sonar_bogie *b;
633
634   /* Move the sweep forward (clockwise).
635    */
636   GLfloat prev_sweep, this_sweep, tick;
637   GLfloat cycle_secs = 30 / speed;  /* default to one cycle every N seconds */
638   this_sweep = ((cycle_secs - fmod (double_time() - sp->start_time +
639                                     sp->sweep_offset,
640                                     cycle_secs))
641                 / cycle_secs
642                 * M_PI * 2);
643   prev_sweep = sp->sweep_th;
644   tick = prev_sweep - this_sweep;
645   while (tick < 0) tick += M_PI*2;
646
647   sp->sweep_th = this_sweep;
648
649   if (this_sweep < 0 || this_sweep >= M_PI*2) abort();
650   if (prev_sweep < 0)  /* skip first time */
651     return;
652
653   if (tick < 0 || tick >= M_PI*2) abort();
654
655
656   /* Go through the 'pending' sensor data, find those bogies who are
657      just now being swept, and move them from 'pending' to 'displayed'.
658      (Leave bogies that have not yet been swept alone: we'll get to
659      them when the sweep moves forward.)
660    */
661   b = sp->pending;
662   while (b)
663     {
664       sonar_bogie *next = b->next;
665       if (point_in_wedge (b->th, this_sweep, prev_sweep))
666         {
667           if (debug_p > 1) {
668             time_t t = time((time_t *) 0);
669             fprintf (stderr,
670                      "%s: sweep hit: %02d:%02d: %s: (%5.2f %5.2f %5.2f;"
671                      " th=[%.2f < %.2f <= %.2f])\n", 
672                      progname,
673                      (int) (t / 60) % 60, (int) t % 60,
674                      b->name, b->r, b->th, b->ttl,
675                      this_sweep, b->th, prev_sweep);
676           }
677           b->ttl = M_PI * 2.1;
678           copy_and_insert_bogie (sp->ssd, b, &sp->displayed);
679           delete_bogie (sp->ssd, b, &sp->pending);
680         }
681       b = next;
682     }
683
684
685   /* Update TTL on all currently-displayed bogies; delete the dead.
686
687      Request sensor updates on the ones just now being swept.
688
689      Any updates go into 'pending' and might not show up until
690      the next time the sweep comes around.  This is to prevent
691      already-drawn bogies from jumping to a new position without
692      having faded out first.
693   */
694   b = sp->displayed;
695   while (b)
696     {
697       sonar_bogie *next = b->next;
698       b->ttl -= tick;
699
700       if (b->ttl <= 0)
701         {
702           if (debug_p > 1)
703             fprintf (stderr, "%s: TTL expired: %s (%5.2f %5.2f %5.2f)\n",
704                      progname, b->name, b->r, b->th, b->ttl);
705           delete_bogie (sp->ssd, b, &sp->displayed);
706         }
707       b = next;
708     }
709
710   update_sensor_data (sp);
711 }
712
713
714 static void
715 draw_startup_blurb (ModeInfo *mi)
716 {
717   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
718   const char *msg = (sp->error ? sp->error : "Resolving hosts...");
719   static const GLfloat color[4] = {0, 1, 0, 1};
720
721   if (!sp->error && ping_arg && !strcmp (ping_arg, "simulation"))
722     return;  /* don't bother */
723
724   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
725   glTranslatef (0, 0, 0.3);
726   draw_text (mi, msg, 0, 0, 0, 30.0);
727
728   /* only leave error message up for N seconds */
729   if (sp->error &&
730       sp->start_time + 4 < double_time())
731     {
732       free (sp->error);
733       sp->error = 0;
734     }
735 }
736
737
738 /* Window management, etc
739  */
740 ENTRYPOINT void
741 reshape_sonar (ModeInfo *mi, int width, int height)
742 {
743   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
744   GLfloat h = (GLfloat) height / (GLfloat) width;
745
746   glViewport (0, 0, (GLint) width, (GLint) height);
747
748   glMatrixMode(GL_PROJECTION);
749   glLoadIdentity();
750   gluPerspective (30.0, 1/h, 1.0, 100.0);
751
752   glMatrixMode(GL_MODELVIEW);
753   glLoadIdentity();
754   gluLookAt( 0.0, 0.0, 30.0,
755              0.0, 0.0, 0.0,
756              0.0, 1.0, 0.0);
757
758   glClear(GL_COLOR_BUFFER_BIT);
759
760   sp->line_thickness = (MI_IS_WIREFRAME (mi) ? 1 : MAX (1, height / 300.0));
761 }
762
763
764 ENTRYPOINT Bool
765 sonar_handle_event (ModeInfo *mi, XEvent *event)
766 {
767   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
768
769   if (event->xany.type == ButtonPress &&
770       event->xbutton.button == Button1)
771     {
772       sp->button_down_p = True;
773       gltrackball_start (sp->trackball,
774                          event->xbutton.x, event->xbutton.y,
775                          MI_WIDTH (mi), MI_HEIGHT (mi));
776       return True;
777     }
778   else if (event->xany.type == ButtonRelease &&
779            event->xbutton.button == Button1)
780     {
781       sp->button_down_p = False;
782       return True;
783     }
784   else if (event->xany.type == ButtonPress &&
785            (event->xbutton.button == Button4 ||
786             event->xbutton.button == Button5 ||
787             event->xbutton.button == Button6 ||
788             event->xbutton.button == Button7))
789     {
790       gltrackball_mousewheel (sp->trackball, event->xbutton.button, 10,
791                               !!event->xbutton.state);
792       return True;
793     }
794   else if (event->xany.type == MotionNotify &&
795            sp->button_down_p)
796     {
797       gltrackball_track (sp->trackball,
798                          event->xmotion.x, event->xmotion.y,
799                          MI_WIDTH (mi), MI_HEIGHT (mi));
800       return True;
801     }
802
803   return False;
804 }
805
806
807 ENTRYPOINT void 
808 init_sonar (ModeInfo *mi)
809 {
810   sonar_configuration *sp;
811
812   if (!sps) {
813     sps = (sonar_configuration *)
814       calloc (MI_NUM_SCREENS(mi), sizeof (sonar_configuration));
815     if (!sps) {
816       fprintf(stderr, "%s: out of memory\n", progname);
817       exit(1);
818     }
819   }
820   sp = &sps[MI_SCREEN(mi)];
821   sp->glx_context = init_GL(mi);
822
823   reshape_sonar (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
824   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
825
826   sp->trackball = gltrackball_init ();
827   sp->rot = make_rotator (0, 0, 0, 0, speed * 0.003, True);
828
829   sp->texfont = load_texture_font (MI_DISPLAY(mi), "font");
830   check_gl_error ("loading font");
831
832   sp->table_list = glGenLists (1);
833   glNewList (sp->table_list, GL_COMPILE);
834   sp->table_polys = draw_table (mi);
835   glEndList ();
836
837   sp->screen_list = glGenLists (1);
838   glNewList (sp->screen_list, GL_COMPILE);
839   sp->screen_polys = draw_screen (mi, False, False);
840   glEndList ();
841
842   sp->grid_list = glGenLists (1);
843   glNewList (sp->grid_list, GL_COMPILE);
844   sp->grid_polys = draw_screen (mi, True,  False);
845   glEndList ();
846
847   sp->sweep_list = glGenLists (1);
848   glNewList (sp->sweep_list, GL_COMPILE);
849   sp->sweep_polys = draw_screen (mi, False, True);
850   glEndList ();
851
852   sp->start_time = double_time ();
853   sp->sweep_offset = random() % 60;
854   sp->sweep_th = -1;
855 }
856
857
858 static void
859 init_sensor (ModeInfo *mi)
860 {
861   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
862
863   if (sp->ssd) abort();
864
865   if (!ping_arg || !*ping_arg ||
866       !strcmp(ping_arg, "default") ||
867       !!strcmp (ping_arg, "simulation"))
868     sp->ssd = init_ping (MI_DISPLAY (mi), &sp->error, ping_arg,
869                          ping_timeout, resolve_p, times_p, debug_p);
870
871   /* Disavow privs.  This was already done in init_ping(), but
872      we might not have called that at all, so do it again. */
873   setuid(getuid());
874
875   if (!sp->ssd)
876     sp->ssd = init_simulation (MI_DISPLAY (mi), &sp->error,
877                                team_a_name, team_b_name,
878                                team_a_count, team_b_count,
879                                debug_p);
880   if (!sp->ssd)
881     abort();
882 }
883
884
885 ENTRYPOINT void
886 draw_sonar (ModeInfo *mi)
887 {
888   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
889   Display *dpy = MI_DISPLAY(mi);
890   Window window = MI_WINDOW(mi);
891   int wire = MI_IS_WIREFRAME(mi);
892
893   if (!sp->glx_context)
894     return;
895
896   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
897
898   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
899
900   if (!wire)
901     {
902       GLfloat pos[4] = {0.05, 0.07, 1.00, 0.0};
903       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
904       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
905       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
906
907       glEnable(GL_TEXTURE_2D);
908       glEnable(GL_LIGHTING);
909       glEnable(GL_LIGHT0);
910       glEnable(GL_CULL_FACE);
911       glEnable(GL_DEPTH_TEST);
912       glEnable(GL_NORMALIZE);
913       glEnable(GL_LINE_SMOOTH);
914       glEnable(GL_BLEND);
915       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
916       glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
917       glShadeModel(GL_SMOOTH);
918
919       glLightfv(GL_LIGHT0, GL_POSITION, pos);
920       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
921       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
922       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
923     }
924
925   glPushMatrix ();
926   {
927     GLfloat s = 7;
928     if (MI_WIDTH(mi) < MI_HEIGHT(mi))
929       s *= (MI_WIDTH(mi) / (float) MI_HEIGHT(mi));
930     glScalef (s,s,s);
931   }
932
933   gltrackball_rotate (sp->trackball);
934   glRotatef(current_device_rotation(), 0, 0, 1);
935
936   if (wobble_p)
937     {
938       double x, y, z;
939       double max = 40;
940       get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
941       glRotatef (max/2 - x*max, 1, 0, 0);
942       glRotatef (max/2 - z*max, 0, 1, 0);
943     }
944
945   mi->polygon_count = 0;
946
947   glPushMatrix();                                       /* table */
948   glCallList (sp->table_list);
949   mi->polygon_count += sp->table_polys;
950   glPopMatrix();
951
952   glPushMatrix();                                       /* text */
953   glTranslatef (0, 0, -0.01);
954   mi->polygon_count += draw_bogies (mi);
955   glPopMatrix();
956
957   glCallList (sp->screen_list);                         /* glass */
958   mi->polygon_count += sp->screen_polys;
959
960   glTranslatef (0, 0, 0.004);                           /* sweep */
961   glPushMatrix();
962   glRotatef ((sp->sweep_th * 180 / M_PI), 0, 0, 1);
963   if (sp->sweep_th >= 0)
964     glCallList (sp->sweep_list);
965   mi->polygon_count += sp->sweep_polys;
966   glPopMatrix();
967
968   glTranslatef (0, 0, 0.004);                           /* grid */
969   glCallList (sp->grid_list);
970   mi->polygon_count += sp->screen_polys;
971
972   if (! sp->ssd || sp->error)
973     draw_startup_blurb(mi);
974
975   glPopMatrix ();
976
977   if (mi->fps_p) do_fps (mi);
978   glFinish();
979
980   glXSwapBuffers(dpy, window);
981
982   if (! sp->ssd)
983     /* Just starting up.  "Resolving hosts" text printed.  Go stall. */
984     init_sensor (mi);
985   else
986     sweep (sp);
987 }
988
989 ENTRYPOINT void
990 release_sonar (ModeInfo *mi)
991 {
992 #if 0
993   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
994   sonar_bogie *b = sp->displayed;
995   while (b)
996     {
997       sonar_bogie *next = b->next;
998       free_bogie (sp->ssd, b);
999       b = next;
1000     }
1001   sp->displayed = 0;
1002
1003   b = sp->pending;
1004   while (b)
1005     {
1006       sonar_bogie *next = b->next;
1007       free_bogie (sp->ssd, b);
1008       b = next;
1009     }
1010   sp->pending = 0;
1011
1012   sp->ssd->free_data_cb (sp->ssd, sp->ssd->closure);
1013   free (sp->ssd);
1014   sp->ssd = 0;
1015 #endif
1016 }
1017
1018 XSCREENSAVER_MODULE ("Sonar", sonar)
1019
1020 #endif /* USE_GL */