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