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