http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / hacks / glx / sonar.c
1 /* sonar, Copyright (c) 1998-2011 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   glPushAttrib (GL_ENABLE_BIT);
195   glDisable (GL_TEXTURE_2D);
196
197   glFrontFace (GL_CCW);
198   th_steps = 36 * 4;    /* must be a multiple of th_skip2 divisor */
199   r_steps = 40;
200   r_skip = 1;
201   th_skip = 1;
202   th_skip2 = 1;
203   outer_r = 0;
204
205   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
206   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
207   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mesh_p ? lines : glass);
208   if (wire) glColor3fv (lines);
209
210   if (mesh_p) 
211     {
212       th_skip  = th_steps / 12;
213       th_skip2 = th_steps / 36;
214       r_skip = r_steps / 3;
215       outer_r = r_steps * 0.93;
216
217       if (! wire)
218         glLineWidth (sp->line_thickness);
219     }
220
221   ring = (XYZ *) calloc (th_steps, sizeof(*ring));
222
223   for (i = 0; i < th_steps; i++)
224     {
225       double a = M_PI * 2 * i / th_steps;
226       ring[i].x = cos(a);
227       ring[i].y = sin(a);
228     }
229
230   /* place the bottom of the disc on the xy plane. */
231   zoff = cos (curvature/2 * (M_PI/2)) / 2;
232
233   for (i = r_steps; i > 0; i--)
234     {
235       int j0, j1;
236
237       r0 = i     / (GLfloat) r_steps;
238       r1 = (i+1) / (GLfloat) r_steps;
239
240       if (r1 > 1) r1 = 1; /* avoid asin lossage */
241
242       z0 = cos (curvature/2 * asin (r0)) / 2 - zoff;
243       z1 = cos (curvature/2 * asin (r1)) / 2 - zoff;
244
245       glBegin(wire || mesh_p ? GL_LINES : GL_QUAD_STRIP);
246       for (j0 = 0; j0 <= th_steps; j0++)
247         {
248           if (mesh_p && 
249               (i < outer_r
250                ? (j0 % th_skip != 0)
251                : (j0 % th_skip2 != 0)))
252             continue;
253
254           if (sweep_p)
255             {
256               GLfloat color[4];
257               GLfloat r = 1 - (j0 / (GLfloat) (th_steps * sweep_size));
258 #if 0
259               color[0] = glass[0] + (sweepc[0] - glass[0]) * r;
260               color[1] = glass[1] + (sweepc[1] - glass[1]) * r;
261               color[2] = glass[2] + (sweepc[2] - glass[2]) * r;
262               color[3] = glass[3];
263 #else
264               color[0] = sweepc[0];
265               color[1] = sweepc[1];
266               color[2] = sweepc[2];
267               color[3] = r;
268 #endif
269               glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
270             }
271
272           j1 = j0 % th_steps;
273           glNormal3f (r0 * ring[j1].x, r0 * ring[j1].y, z0);
274           glVertex3f (r0 * ring[j1].x, r0 * ring[j1].y, z0);
275           glNormal3f (r1 * ring[j1].x, r1 * ring[j1].y, z1);
276           glVertex3f (r1 * ring[j1].x, r1 * ring[j1].y, z1);
277           polys++;
278
279           if (sweep_p && j0 >= th_steps * sweep_size)
280             break;
281           if (sweep_p && wire)
282             break;
283         }
284       glEnd();
285
286       if (mesh_p &&
287           (i == outer_r ||
288            i == r_steps ||
289            (i % r_skip == 0 &&
290             i < r_steps - r_skip)))
291         {
292           glBegin(GL_LINE_LOOP);
293           for (j0 = 0; j0 < th_steps; j0++)
294             {
295               glNormal3f (r0 * ring[j0].x, r0 * ring[j0].y, z0);
296               glVertex3f (r0 * ring[j0].x, r0 * ring[j0].y, z0);
297               polys++;
298             }
299           glEnd();
300         }
301     }
302
303   /* one more polygon for the middle */
304   if (!wire && !sweep_p)
305     {
306       glBegin(wire || mesh_p ? GL_LINE_LOOP : GL_POLYGON);
307       glNormal3f (0, 0, 1);
308       for (i = 0; i < th_steps; i++)
309         {
310           glNormal3f (r0 * ring[i].x, r0 * ring[i].y, z0);
311           glVertex3f (r0 * ring[i].x, r0 * ring[i].y, z0);
312         }
313       polys++;
314       glEnd();
315     }
316
317   glPopAttrib();
318   free (ring);
319
320   return polys;
321 }
322
323
324 static int
325 draw_text (ModeInfo *mi, const char *string, GLfloat r, GLfloat th, 
326            GLfloat ttl, GLfloat size)
327 {
328   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
329   int wire = MI_IS_WIREFRAME(mi);
330   int polys = 0;
331   GLfloat font_scale = 0.001 * (size > 0 ? size : font_size) / 14.0;
332   int lines = 0, max_w = 0, lh = 0;
333   char *string2 = strdup (string);
334   char *token = string2;
335   char *line;
336   GLfloat color[4];
337
338   if (size <= 0)   /* if size not specified, draw in yellow with alpha */
339     {
340       color[0] = 1;
341       color[1] = 1;
342       color[2] = 0;
343       color[3] = (ttl / (M_PI * 2)) * 1.2;
344       if (color[3] > 1) color[3] = 1;
345
346       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
347       if (wire)
348         glColor3f (color[0]*color[3], color[1]*color[3], color[2]*color[3]);
349     }
350
351   while ((line = strtok (token, "\r\n")))
352     {
353       int w = texture_string_width (sp->texfont, line, &lh);
354       if (w > max_w) max_w = w;
355       lines++;
356       token = 0;
357     }
358
359   glPushMatrix();
360   glTranslatef (r * cos (th), r * sin(th), 0);
361   glScalef (font_scale, font_scale, font_scale);
362
363   if (size <= 0)                /* Draw the dot */
364     {
365       GLfloat s = font_size * 1.7;
366       glDisable (GL_TEXTURE_2D);
367       glFrontFace (GL_CW);
368       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
369       glVertex3f (0, s, 0);
370       glVertex3f (s, s, 0);
371       glVertex3f (s, 0, 0);
372       glVertex3f (0, 0, 0);
373       glEnd();
374       glTranslatef (-max_w/2, -lh, 0);
375     }
376   else
377     glTranslatef (-max_w/2, -lh/2, 0);
378
379   /* draw each line, centered */
380   if (! wire) glEnable (GL_TEXTURE_2D);
381   free (string2);
382   string2 = strdup (string);
383   token = string2;
384   while ((line = strtok (token, "\r\n")))
385     {
386       int w = texture_string_width (sp->texfont, line, 0);
387       glPushMatrix();
388       glTranslatef ((max_w-w)/2, 0, 0);
389
390       if (wire)
391         {
392           glBegin (GL_LINE_LOOP);
393           glVertex3f (0, 0, 0);
394           glVertex3f (w, 0, 0);
395           glVertex3f (w, lh, 0);
396           glVertex3f (0, lh, 0);
397           glEnd();
398         }
399       else
400         {
401           glFrontFace (GL_CW);
402           print_texture_string (sp->texfont, line);
403         }
404       glPopMatrix();
405       glTranslatef (0, -lh, 0);
406       polys++;
407       token = 0;
408     }
409   glPopMatrix();
410
411   free (string2);
412
413   if (! wire) glEnable (GL_DEPTH_TEST);
414
415   return polys;
416 }
417
418
419 /* There's a disc with a hole in it around the screen, to act as a mask
420    preventing slightly off-screen bogies from showing up.  This clips 'em.
421  */
422 static int
423 draw_table (ModeInfo *mi)
424 {
425   /*sonar_configuration *sp = &sps[MI_SCREEN(mi)];*/
426   int wire = MI_IS_WIREFRAME(mi);
427   int polys = 0;
428   int i;
429   int th_steps = 36 * 4;    /* same as in draw_screen */
430
431   static const GLfloat color[4]  = {0.0, 0.0, 0.0, 1.0};
432   static const GLfloat text[4]   = {0.15, 0.15, 0.15, 1.0};
433   static const GLfloat spec[4]   = {0.0, 0.0, 0.0, 1.0};
434   static const GLfloat shiny     = 0.0;
435
436   if (wire) return 0;
437
438   glPushAttrib (GL_ENABLE_BIT);
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   glPopAttrib();
459
460   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, text);
461   glTranslatef (0, 0, 0.01);
462   for (i = 0; i < 360; i += 10)
463     {
464       char buf[10];
465       GLfloat a = M_PI/2 - (i / 180.0 * M_PI);
466       sprintf (buf, "%d", i);
467       polys += draw_text (mi, buf, 1.07, a, 0, 10.0);
468     }
469
470   return polys;
471 }
472
473
474 static int
475 draw_bogies (ModeInfo *mi)
476 {
477   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
478   int polys = 0;
479   sonar_bogie *b;
480
481   for (b = sp->displayed; b; b = b->next)
482     {
483       char *s = (char *) 
484         malloc (strlen (b->name) + (b->desc ? strlen(b->desc) : 0) + 3);
485       strcpy (s, b->name);
486       if (b->desc)
487         {
488           strcat (s, "\n");
489           strcat (s, b->desc);
490         }
491       polys += draw_text (mi, s, b->r, b->th, b->ttl, -1);
492       free (s);
493
494       /* Move *very slightly* forward so that the text is not all in the
495          same plane: this prevents flickering with overlapping text as
496          the textures fight for priority. */
497       glTranslatef(0, 0, 0.00002);
498     }
499
500   return polys;
501 }
502
503
504 /* called from sonar-sim.c and sonar-icmp.c */
505 sonar_bogie *
506 copy_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
507 {
508   sonar_bogie *b2 = (sonar_bogie *) calloc (1, sizeof(*b2));
509   b2->name = strdup (b->name);
510   b2->desc = b->desc ? strdup (b->desc) : 0;
511   b2->r    = b->r;
512   b2->th   = b->th;
513   b2->ttl  = b->ttl;
514   /* does not copy b->closure */
515
516   /* Take this opportunity to normalize 'th' to the range [0-2pi). */
517   while (b2->th < 0)       b2->th += M_PI*2;
518   while (b2->th >= M_PI*2) b2->th -= M_PI*2;
519
520   return b2;
521 }
522
523
524 /* called from sonar-icmp.c */
525 void
526 free_bogie (sonar_sensor_data *ssd, sonar_bogie *b)
527 {
528   if (b->closure)
529     ssd->free_bogie_cb (ssd, b->closure);
530   free (b->name);
531   if (b->desc) free (b->desc);
532   free (b);
533 }
534
535 /* removes it from the list and frees it
536  */
537 static void
538 delete_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
539               sonar_bogie **from_list)
540 {
541   sonar_bogie *ob, *prev;
542   for (prev = 0, ob = *from_list; ob; prev = ob, ob = ob->next)
543     if (ob == b)
544       {
545         if (prev)
546           prev->next = b->next;
547         else
548           (*from_list) = b->next;
549         free_bogie (ssd, b);
550         break;
551       }
552 }
553
554
555 /* copies the bogie and adds it to the list.
556    if there's another bogie there with the same name, frees that one.
557  */
558 static void
559 copy_and_insert_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
560                        sonar_bogie **to_list)
561 {
562   sonar_bogie *ob, *next;
563   if (!b) abort();
564   for (ob = *to_list, next = ob ? ob->next : 0; 
565        ob; 
566        ob = next, next = ob ? ob->next : 0)
567     {
568       if (ob == b) abort();   /* this will end badly */
569       if (!strcmp (ob->name, b->name))  /* match! */
570         {
571           delete_bogie (ssd, ob, to_list);
572           break;
573         }
574     }
575
576   b = copy_bogie (ssd, b);
577   b->next = *to_list;
578   *to_list = b;
579 }
580
581
582 static void
583 update_sensor_data (sonar_configuration *sp)
584 {
585   sonar_bogie *new_list = sp->ssd->scan_cb (sp->ssd);
586   sonar_bogie *b2;
587
588   /* If a bogie exists in 'new_list' but not 'pending', add it.
589      If a bogie exists in both, update it in 'pending'.
590    */
591   for (b2 = new_list; b2; b2 = b2->next)
592     {
593       if (debug_p > 2)
594         fprintf (stderr, "%s:   updated: %s (%5.2f %5.2f %5.2f)\n", 
595                  progname, b2->name, b2->r, b2->th, b2->ttl);
596       copy_and_insert_bogie (sp->ssd, b2, &sp->pending);
597     }
598   if (debug_p > 2) fprintf (stderr, "\n");
599 }
600
601
602 /* Returns whether the given angle lies between two other angles.
603    When those angles cross 0, it assumes the wedge is the smaller one.
604    That is: 5 lies between 10 and 350 degrees (a 20 degree wedge).
605  */
606 static Bool
607 point_in_wedge (GLfloat th, GLfloat low, GLfloat high)
608 {
609   if (low < high)
610     return (th > low && th <= high);
611   else
612     return (th <= high || th > low);
613 }
614
615
616 /* Returns the current time in seconds as a double.
617  */
618 static double
619 double_time (void)
620 {
621   struct timeval now;
622 # ifdef GETTIMEOFDAY_TWO_ARGS
623   struct timezone tzp;
624   gettimeofday(&now, &tzp);
625 # else
626   gettimeofday(&now);
627 # endif
628
629   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
630 }
631
632
633 static void
634 sweep (sonar_configuration *sp)
635 {
636   sonar_bogie *b;
637
638   /* Move the sweep forward (clockwise).
639    */
640   GLfloat prev_sweep, this_sweep, tick;
641   GLfloat cycle_secs = 30 / speed;  /* default to one cycle every N seconds */
642   this_sweep = ((cycle_secs - fmod (double_time() - sp->start_time +
643                                     sp->sweep_offset,
644                                     cycle_secs))
645                 / cycle_secs
646                 * M_PI * 2);
647   prev_sweep = sp->sweep_th;
648   tick = prev_sweep - this_sweep;
649   while (tick < 0) tick += M_PI*2;
650
651   sp->sweep_th = this_sweep;
652
653   if (this_sweep < 0 || this_sweep >= M_PI*2) abort();
654   if (prev_sweep < 0)  /* skip first time */
655     return;
656
657   if (tick < 0 || tick >= M_PI*2) abort();
658
659
660   /* Go through the 'pending' sensor data, find those bogies who are
661      just now being swept, and move them from 'pending' to 'displayed'.
662      (Leave bogies that have not yet been swept alone: we'll get to
663      them when the sweep moves forward.)
664    */
665   b = sp->pending;
666   while (b)
667     {
668       sonar_bogie *next = b->next;
669       if (point_in_wedge (b->th, this_sweep, prev_sweep))
670         {
671           if (debug_p > 1) {
672             time_t t = time((time_t *) 0);
673             fprintf (stderr,
674                      "%s: sweep hit: %02d:%02d: %s: (%5.2f %5.2f %5.2f;"
675                      " th=[%.2f < %.2f <= %.2f])\n", 
676                      progname,
677                      (int) (t / 60) % 60, (int) t % 60,
678                      b->name, b->r, b->th, b->ttl,
679                      this_sweep, b->th, prev_sweep);
680           }
681           b->ttl = M_PI * 2.1;
682           copy_and_insert_bogie (sp->ssd, b, &sp->displayed);
683           delete_bogie (sp->ssd, b, &sp->pending);
684         }
685       b = next;
686     }
687
688
689   /* Update TTL on all currently-displayed bogies; delete the dead.
690
691      Request sensor updates on the ones just now being swept.
692
693      Any updates go into 'pending' and might not show up until
694      the next time the sweep comes around.  This is to prevent
695      already-drawn bogies from jumping to a new position without
696      having faded out first.
697   */
698   b = sp->displayed;
699   while (b)
700     {
701       sonar_bogie *next = b->next;
702       b->ttl -= tick;
703
704       if (b->ttl <= 0)
705         {
706           if (debug_p > 1)
707             fprintf (stderr, "%s: TTL expired: %s (%5.2f %5.2f %5.2f)\n",
708                      progname, b->name, b->r, b->th, b->ttl);
709           delete_bogie (sp->ssd, b, &sp->displayed);
710         }
711       b = next;
712     }
713
714   update_sensor_data (sp);
715 }
716
717
718 static void
719 draw_startup_blurb (ModeInfo *mi)
720 {
721   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
722   const char *msg = (sp->error ? sp->error : "Resolving hosts...");
723   static const GLfloat color[4] = {0, 1, 0, 1};
724
725   if (!sp->error && ping_arg && !strcmp (ping_arg, "simulation"))
726     return;  /* don't bother */
727
728   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
729   glTranslatef (0, 0, 0.3);
730   draw_text (mi, msg, 0, 0, 0, 30.0);
731
732   /* only leave error message up for N seconds */
733   if (sp->error &&
734       sp->start_time + 4 < double_time())
735     {
736       free (sp->error);
737       sp->error = 0;
738     }
739 }
740
741
742 /* Window management, etc
743  */
744 ENTRYPOINT void
745 reshape_sonar (ModeInfo *mi, int width, int height)
746 {
747   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
748   GLfloat h = (GLfloat) height / (GLfloat) width;
749
750   glViewport (0, 0, (GLint) width, (GLint) height);
751
752   glMatrixMode(GL_PROJECTION);
753   glLoadIdentity();
754   gluPerspective (30.0, 1/h, 1.0, 100.0);
755
756   glMatrixMode(GL_MODELVIEW);
757   glLoadIdentity();
758   gluLookAt( 0.0, 0.0, 30.0,
759              0.0, 0.0, 0.0,
760              0.0, 1.0, 0.0);
761
762   glClear(GL_COLOR_BUFFER_BIT);
763
764   sp->line_thickness = (MI_IS_WIREFRAME (mi) ? 1 : MAX (1, height / 300.0));
765 }
766
767
768 ENTRYPOINT Bool
769 sonar_handle_event (ModeInfo *mi, XEvent *event)
770 {
771   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
772
773   if (event->xany.type == ButtonPress &&
774       event->xbutton.button == Button1)
775     {
776       sp->button_down_p = True;
777       gltrackball_start (sp->trackball,
778                          event->xbutton.x, event->xbutton.y,
779                          MI_WIDTH (mi), MI_HEIGHT (mi));
780       return True;
781     }
782   else if (event->xany.type == ButtonRelease &&
783            event->xbutton.button == Button1)
784     {
785       sp->button_down_p = False;
786       return True;
787     }
788   else if (event->xany.type == ButtonPress &&
789            (event->xbutton.button == Button4 ||
790             event->xbutton.button == Button5 ||
791             event->xbutton.button == Button6 ||
792             event->xbutton.button == Button7))
793     {
794       gltrackball_mousewheel (sp->trackball, event->xbutton.button, 10,
795                               !!event->xbutton.state);
796       return True;
797     }
798   else if (event->xany.type == MotionNotify &&
799            sp->button_down_p)
800     {
801       gltrackball_track (sp->trackball,
802                          event->xmotion.x, event->xmotion.y,
803                          MI_WIDTH (mi), MI_HEIGHT (mi));
804       return True;
805     }
806
807   return False;
808 }
809
810
811 ENTRYPOINT void 
812 init_sonar (ModeInfo *mi)
813 {
814   sonar_configuration *sp;
815   int wire = MI_IS_WIREFRAME(mi);
816
817   if (!sps) {
818     sps = (sonar_configuration *)
819       calloc (MI_NUM_SCREENS(mi), sizeof (sonar_configuration));
820     if (!sps) {
821       fprintf(stderr, "%s: out of memory\n", progname);
822       exit(1);
823     }
824   }
825   sp = &sps[MI_SCREEN(mi)];
826   sp->glx_context = init_GL(mi);
827
828   reshape_sonar (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
829   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
830
831   if (!wire)
832     {
833       GLfloat pos[4] = {0.05, 0.07, 1.00, 0.0};
834       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
835       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
836       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
837
838       glEnable(GL_TEXTURE_2D);
839       glEnable(GL_LIGHTING);
840       glEnable(GL_LIGHT0);
841       glEnable(GL_CULL_FACE);
842       glEnable(GL_DEPTH_TEST);
843       glEnable(GL_NORMALIZE);
844       glEnable(GL_LINE_SMOOTH);
845       glEnable(GL_BLEND);
846       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
847       glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
848       glShadeModel(GL_SMOOTH);
849
850       glLightfv(GL_LIGHT0, GL_POSITION, pos);
851       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
852       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
853       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
854     }
855
856   sp->trackball = gltrackball_init ();
857   sp->rot = make_rotator (0, 0, 0, 0, speed * 0.003, True);
858
859   sp->texfont = load_texture_font (MI_DISPLAY(mi), "font");
860   check_gl_error ("loading font");
861
862   sp->table_list = glGenLists (1);
863   glNewList (sp->table_list, GL_COMPILE);
864   sp->table_polys = draw_table (mi);
865   glEndList ();
866
867   sp->screen_list = glGenLists (1);
868   glNewList (sp->screen_list, GL_COMPILE);
869   sp->screen_polys = draw_screen (mi, False, False);
870   glEndList ();
871
872   sp->grid_list = glGenLists (1);
873   glNewList (sp->grid_list, GL_COMPILE);
874   sp->grid_polys = draw_screen (mi, True,  False);
875   glEndList ();
876
877   sp->sweep_list = glGenLists (1);
878   glNewList (sp->sweep_list, GL_COMPILE);
879   sp->sweep_polys = draw_screen (mi, False, True);
880   glEndList ();
881
882   sp->start_time = double_time ();
883   sp->sweep_offset = random() % 60;
884   sp->sweep_th = -1;
885 }
886
887
888 static void
889 init_sensor (ModeInfo *mi)
890 {
891   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
892
893   if (sp->ssd) abort();
894
895   if (!ping_arg || !*ping_arg ||
896       !strcmp(ping_arg, "default") ||
897       !!strcmp (ping_arg, "simulation"))
898     sp->ssd = init_ping (MI_DISPLAY (mi), &sp->error, ping_arg,
899                          ping_timeout, resolve_p, times_p, debug_p);
900
901   /* Disavow privs.  This was already done in init_ping(), but
902      we might not have called that at all, so do it again. */
903   setuid(getuid());
904
905   if (!sp->ssd)
906     sp->ssd = init_simulation (MI_DISPLAY (mi), &sp->error,
907                                team_a_name, team_b_name,
908                                team_a_count, team_b_count,
909                                debug_p);
910   if (!sp->ssd)
911     abort();
912 }
913
914
915 ENTRYPOINT void
916 draw_sonar (ModeInfo *mi)
917 {
918   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
919   Display *dpy = MI_DISPLAY(mi);
920   Window window = MI_WINDOW(mi);
921
922   if (!sp->glx_context)
923     return;
924
925   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
926
927   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
928
929   glPushMatrix ();
930   { GLfloat s = 7; glScalef (s,s,s); }
931
932   gltrackball_rotate (sp->trackball);
933
934   if (wobble_p)
935     {
936       double x, y, z;
937       double max = 40;
938       get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
939       glRotatef (max/2 - x*max, 1, 0, 0);
940       glRotatef (max/2 - z*max, 0, 1, 0);
941     }
942
943   mi->polygon_count = 0;
944
945   glPushMatrix();                                       /* table */
946   glCallList (sp->table_list);
947   mi->polygon_count += sp->table_polys;
948   glPopMatrix();
949
950   glPushMatrix();                                       /* text */
951   glTranslatef (0, 0, -0.01);
952   mi->polygon_count += draw_bogies (mi);
953   glPopMatrix();
954
955   glCallList (sp->screen_list);                         /* glass */
956   mi->polygon_count += sp->screen_polys;
957
958   glTranslatef (0, 0, 0.004);                           /* sweep */
959   glPushMatrix();
960   glRotatef ((sp->sweep_th * 180 / M_PI), 0, 0, 1);
961   if (sp->sweep_th >= 0)
962     glCallList (sp->sweep_list);
963   mi->polygon_count += sp->sweep_polys;
964   glPopMatrix();
965
966   glTranslatef (0, 0, 0.004);                           /* grid */
967   glCallList (sp->grid_list);
968   mi->polygon_count += sp->screen_polys;
969
970   if (! sp->ssd || sp->error)
971     draw_startup_blurb(mi);
972
973   glPopMatrix ();
974
975   if (mi->fps_p) do_fps (mi);
976   glFinish();
977
978   glXSwapBuffers(dpy, window);
979
980   if (! sp->ssd)
981     /* Just starting up.  "Resolving hosts" text printed.  Go stall. */
982     init_sensor (mi);
983   else
984     sweep (sp);
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 */