From http://www.jwz.org/xscreensaver/xscreensaver-5.31.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 "-*-courier-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 #include "thread_util.h"
70
71 #define DEFAULTS        "*delay:        30000       \n" \
72                         "*font:       " DEF_FONT   "\n" \
73                         "*showFPS:      False       \n" \
74                         "*wireframe:    False       \n" \
75                         "*texFontCacheSize: 300     \n" \
76                         THREAD_DEFAULTS_XLOCK
77
78
79 # define refresh_sonar 0
80 #undef countof
81 #define countof(x) (sizeof((x))/sizeof((*x)))
82
83 #ifdef HAVE_UNISTD_H
84 # include <unistd.h>   /* for setuid() */
85 #endif
86
87 #include "xlockmore.h"
88 #include "sonar.h"
89 #include "gltrackball.h"
90 #include "rotator.h"
91 #include "texfont.h"
92 #include <ctype.h>
93
94 #ifdef USE_GL /* whole file */
95
96 /* #define TEST_ASYNC_NETDB 1 */
97
98 # if TEST_ASYNC_NETDB
99 #   include "async_netdb.h"
100
101 #   include <assert.h>
102 #   include <netinet/in.h>
103 #   include <stdio.h>
104 # endif /* TEST_ASYNC_NETDB */
105
106 typedef struct {
107   double x,y,z;
108 } XYZ;
109
110 typedef struct {
111   GLXContext *glx_context;
112   trackball_state *trackball;
113   rotator *rot;
114   Bool button_down_p;
115
116   double start_time;
117   GLfloat sweep_offset;
118
119   GLuint screen_list, grid_list, sweep_list, table_list;
120   int screen_polys, grid_polys, sweep_polys, table_polys;
121   GLfloat sweep_th;
122   GLfloat line_thickness;
123
124   texture_font_data *texfont;
125
126   enum { MSG, RESOLVE, RUN } state;
127   sonar_sensor_data *ssd;
128   char *error;
129   char *desc;
130
131   sonar_bogie *displayed;       /* on screen and fading */
132   sonar_bogie *pending;         /* returned by sensor, not yet on screen */
133
134 # if TEST_ASYNC_NETDB
135   async_name_from_addr_t query0;
136   async_addr_from_name_t query1;
137 # endif
138 } sonar_configuration;
139
140 static sonar_configuration *sps = NULL;
141
142 static GLfloat speed;
143 static GLfloat sweep_size;
144 static GLfloat font_size;
145 static Bool resolve_p;
146 static Bool times_p;
147 static Bool wobble_p;
148 static Bool debug_p;
149
150 static char *team_a_name;
151 static char *team_b_name;
152 static int team_a_count;
153 static int team_b_count;
154 static int ping_timeout;
155 static char *ping_arg;
156
157 static XrmOptionDescRec opts[] = {
158   { "-speed",        ".speed",       XrmoptionSepArg, 0 },
159   { "-sweep-size",   ".sweepSize",   XrmoptionSepArg, 0 },
160   { "-font-size",    ".fontSize",    XrmoptionSepArg, 0 },
161   { "-team-a-name",  ".teamAName",   XrmoptionSepArg, 0 },
162   { "-team-b-name",  ".teamBName",   XrmoptionSepArg, 0 },
163   { "-team-a-count", ".teamACount",  XrmoptionSepArg, 0 },
164   { "-team-b-count", ".teamBCount",  XrmoptionSepArg, 0 },
165   { "-ping",         ".ping",        XrmoptionSepArg, 0 },
166   { "-ping-timeout", ".pingTimeout", XrmoptionSepArg, 0 },
167   { "-dns",          ".resolve",     XrmoptionNoArg, "True" },
168   { "+dns",          ".resolve",     XrmoptionNoArg, "False" },
169   { "-times",        ".times",       XrmoptionNoArg, "True" },
170   { "+times",        ".times",       XrmoptionNoArg, "False" },
171   { "-wobble",       ".wobble",      XrmoptionNoArg, "True" },
172   { "+wobble",       ".wobble",      XrmoptionNoArg, "False" },
173   THREAD_OPTIONS
174   { "-debug",        ".debug",       XrmoptionNoArg, "True" },
175 };
176
177 static argtype vars[] = {
178   {&speed,        "speed",       "Speed",       DEF_SPEED,        t_Float},
179   {&sweep_size,   "sweepSize",   "SweepSize",   DEF_SWEEP_SIZE,   t_Float},
180   {&font_size,    "fontSize",    "FontSize",    DEF_FONT_SIZE,    t_Float},
181   {&team_a_name,  "teamAName",   "TeamName",    DEF_TEAM_A_NAME,  t_String},
182   {&team_b_name,  "teamBName",   "TeamName",    DEF_TEAM_B_NAME,  t_String},
183   {&team_a_count, "teamACount",  "TeamCount",   DEF_TEAM_A_COUNT, t_Int},
184   {&team_b_count, "teamBCount",  "TeamCount",   DEF_TEAM_A_COUNT, t_Int},
185   {&ping_arg,     "ping",        "Ping",        DEF_PING,         t_String},
186   {&ping_timeout, "pingTimeout", "PingTimeout", DEF_PING_TIMEOUT, t_Int},
187   {&resolve_p,    "resolve",     "Resolve",     DEF_RESOLVE,      t_Bool},
188   {&times_p,      "times",       "Times",       DEF_TIMES,        t_Bool},
189   {&wobble_p,     "wobble",      "Wobble",      DEF_WOBBLE,       t_Bool},
190   {&debug_p,      "debug",       "Debug",       DEF_DEBUG,        t_Bool},
191 };
192
193 ENTRYPOINT ModeSpecOpt sonar_opts = {countof(opts), opts, countof(vars), vars, NULL};
194
195
196 static int
197 draw_screen (ModeInfo *mi, Bool mesh_p, Bool sweep_p)
198 {
199   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
200   int wire = MI_IS_WIREFRAME(mi);
201   int polys = 0;
202   int i;
203   int th_steps, r_steps, r_skip, th_skip, th_skip2, outer_r;
204   GLfloat curvature = M_PI * 0.4;
205   GLfloat r0, r1, z0, z1, zoff;
206   XYZ *ring;
207
208   static const GLfloat glass[4]  = {0.0, 0.4, 0.0, 0.5};
209   static const GLfloat lines[4]  = {0.0, 0.7, 0.0, 0.5};
210   static const GLfloat sweepc[4] = {0.2, 1.0, 0.2, 0.5};
211   static const GLfloat spec[4]   = {1.0, 1.0, 1.0, 1.0};
212   static const GLfloat shiny     = 20.0;
213
214   if (wire && !(mesh_p || sweep_p)) return 0;
215
216   glDisable (GL_TEXTURE_2D);
217
218   glFrontFace (GL_CCW);
219   th_steps = 36 * 4;    /* must be a multiple of th_skip2 divisor */
220   r_steps = 40;
221   r_skip = 1;
222   th_skip = 1;
223   th_skip2 = 1;
224   outer_r = 0;
225
226   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
227   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
228   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mesh_p ? lines : glass);
229   if (wire) glColor3fv (lines);
230
231   if (mesh_p) 
232     {
233       th_skip  = th_steps / 12;
234       th_skip2 = th_steps / 36;
235       r_skip = r_steps / 3;
236       outer_r = r_steps * 0.93;
237
238       if (! wire)
239         glLineWidth (sp->line_thickness);
240     }
241
242   ring = (XYZ *) calloc (th_steps, sizeof(*ring));
243
244   for (i = 0; i < th_steps; i++)
245     {
246       double a = M_PI * 2 * i / th_steps;
247       ring[i].x = cos(a);
248       ring[i].y = sin(a);
249     }
250
251   /* place the bottom of the disc on the xy plane. */
252   zoff = cos (curvature/2 * (M_PI/2)) / 2;
253
254   for (i = r_steps; i > 0; i--)
255     {
256       int j0, j1;
257
258       r0 = i     / (GLfloat) r_steps;
259       r1 = (i+1) / (GLfloat) r_steps;
260
261       if (r1 > 1) r1 = 1; /* avoid asin lossage */
262
263       z0 = cos (curvature/2 * asin (r0)) / 2 - zoff;
264       z1 = cos (curvature/2 * asin (r1)) / 2 - zoff;
265
266       glBegin(wire || mesh_p ? GL_LINES : GL_QUAD_STRIP);
267       for (j0 = 0; j0 <= th_steps; j0++)
268         {
269           if (mesh_p && 
270               (i < outer_r
271                ? (j0 % th_skip != 0)
272                : (j0 % th_skip2 != 0)))
273             continue;
274
275           if (sweep_p)
276             {
277               GLfloat color[4];
278               GLfloat r = 1 - (j0 / (GLfloat) (th_steps * sweep_size));
279 #if 0
280               color[0] = glass[0] + (sweepc[0] - glass[0]) * r;
281               color[1] = glass[1] + (sweepc[1] - glass[1]) * r;
282               color[2] = glass[2] + (sweepc[2] - glass[2]) * r;
283               color[3] = glass[3];
284 #else
285               color[0] = sweepc[0];
286               color[1] = sweepc[1];
287               color[2] = sweepc[2];
288               color[3] = r;
289 #endif
290               glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
291             }
292
293           j1 = j0 % th_steps;
294           glNormal3f (r0 * ring[j1].x, r0 * ring[j1].y, z0);
295           glVertex3f (r0 * ring[j1].x, r0 * ring[j1].y, z0);
296           glNormal3f (r1 * ring[j1].x, r1 * ring[j1].y, z1);
297           glVertex3f (r1 * ring[j1].x, r1 * ring[j1].y, z1);
298           polys++;
299
300           if (sweep_p && j0 >= th_steps * sweep_size)
301             break;
302           if (sweep_p && wire)
303             break;
304         }
305       glEnd();
306
307       if (mesh_p &&
308           (i == outer_r ||
309            i == r_steps ||
310            (i % r_skip == 0 &&
311             i < r_steps - r_skip)))
312         {
313           glBegin(GL_LINE_LOOP);
314           for (j0 = 0; j0 < th_steps; j0++)
315             {
316               glNormal3f (r0 * ring[j0].x, r0 * ring[j0].y, z0);
317               glVertex3f (r0 * ring[j0].x, r0 * ring[j0].y, z0);
318               polys++;
319             }
320           glEnd();
321         }
322     }
323
324   /* one more polygon for the middle */
325   if (!wire && !sweep_p)
326     {
327       glBegin(wire || mesh_p ? GL_LINE_LOOP : GL_POLYGON);
328       glNormal3f (0, 0, 1);
329       for (i = 0; i < th_steps; i++)
330         {
331           glNormal3f (r0 * ring[i].x, r0 * ring[i].y, z0);
332           glVertex3f (r0 * ring[i].x, r0 * ring[i].y, z0);
333         }
334       polys++;
335       glEnd();
336     }
337
338   free (ring);
339
340   return polys;
341 }
342
343
344 static int
345 draw_text (ModeInfo *mi, const char *string, GLfloat r, GLfloat th, 
346            GLfloat ttl, GLfloat size)
347 {
348   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
349   int wire = MI_IS_WIREFRAME(mi);
350   int polys = 0;
351   GLfloat font_scale = 0.001 * (size > 0 ? size : font_size) / 14.0;
352   int lines = 0, max_w = 0, lh = 0;
353   char *string2 = strdup (string);
354   char *token = string2;
355   char *line;
356
357   if (size <= 0)   /* if size not specified, draw in yellow with alpha */
358     {
359       GLfloat color[4];
360       color[0] = 1;
361       color[1] = 1;
362       color[2] = 0;
363       color[3] = (ttl / (M_PI * 2)) * 1.2;
364       if (color[3] > 1) color[3] = 1;
365
366       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
367       if (wire)
368         glColor3f (color[0]*color[3], color[1]*color[3], color[2]*color[3]);
369     }
370
371   while ((line = strtok (token, "\r\n")))
372     {
373       int w = texture_string_width (sp->texfont, line, &lh);
374       if (w > max_w) max_w = w;
375       lines++;
376       token = 0;
377     }
378
379   glPushMatrix();
380   glTranslatef (r * cos (th), r * sin(th), 0);
381   glScalef (font_scale, font_scale, font_scale);
382
383   if (size <= 0)                /* Draw the dot */
384     {
385       GLfloat s = font_size * 1.7;
386       glDisable (GL_TEXTURE_2D);
387       glFrontFace (GL_CW);
388       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
389       glVertex3f (0, s, 0);
390       glVertex3f (s, s, 0);
391       glVertex3f (s, 0, 0);
392       glVertex3f (0, 0, 0);
393       glEnd();
394       glTranslatef (-max_w/2, -lh, 0);
395     }
396   else
397     glTranslatef (-max_w/2, -lh/2, 0);
398
399   /* draw each line, centered */
400   if (! wire) glEnable (GL_TEXTURE_2D);
401   free (string2);
402   string2 = strdup (string);
403   token = string2;
404   while ((line = strtok (token, "\r\n")))
405     {
406       int w = texture_string_width (sp->texfont, line, 0);
407       glPushMatrix();
408       glTranslatef ((max_w-w)/2, 0, polys * 4); /* 'polys' stops Z-fighting. */
409
410       if (wire)
411         {
412           glBegin (GL_LINE_LOOP);
413           glVertex3f (0, 0, 0);
414           glVertex3f (w, 0, 0);
415           glVertex3f (w, lh, 0);
416           glVertex3f (0, lh, 0);
417           glEnd();
418         }
419       else
420         {
421           glFrontFace (GL_CW);
422           print_texture_string (sp->texfont, line);
423         }
424       glPopMatrix();
425       glTranslatef (0, -lh, 0);
426       polys++;
427       token = 0;
428     }
429   glPopMatrix();
430
431   free (string2);
432
433   if (! wire) glEnable (GL_DEPTH_TEST);
434
435   return polys;
436 }
437
438
439 /* There's a disc with a hole in it around the screen, to act as a mask
440    preventing slightly off-screen bogies from showing up.  This clips 'em.
441  */
442 static int
443 draw_table (ModeInfo *mi)
444 {
445   /*sonar_configuration *sp = &sps[MI_SCREEN(mi)];*/
446   int wire = MI_IS_WIREFRAME(mi);
447   int polys = 0;
448   int i;
449   int th_steps = 36 * 4;    /* same as in draw_screen */
450
451   static const GLfloat color[4]  = {0.0, 0.0, 0.0, 1.0};
452   static const GLfloat spec[4]   = {0.0, 0.0, 0.0, 1.0};
453   static const GLfloat shiny     = 0.0;
454
455   if (wire) return 0;
456
457   glDisable (GL_TEXTURE_2D);
458
459   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
460   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
461   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
462
463   glFrontFace (GL_CCW);
464   glBegin(wire ? GL_LINES : GL_QUAD_STRIP);
465   glNormal3f (0, 0, 1);
466   for (i = 0; i <= th_steps; i++)
467     {
468       double a = M_PI * 2 * i / th_steps;
469       double x = cos(a);
470       double y = sin(a);
471       glVertex3f (x, y, 0);
472       glVertex3f (x*10, y*10, 0);
473       polys++;
474     }
475   glEnd();
476
477   return polys;
478 }
479
480
481 static int
482 draw_angles (ModeInfo *mi)
483 {
484   int i;
485   int polys = 0;
486
487   static const GLfloat text[4]   = {0.15, 0.15, 0.15, 1.0};
488   static const GLfloat spec[4]   = {0.0, 0.0, 0.0, 1.0};
489
490   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
491   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, text);
492   glTranslatef (0, 0, 0.01);
493   for (i = 0; i < 360; i += 10)
494     {
495       char buf[10];
496       GLfloat a = M_PI/2 - (i / 180.0 * M_PI);
497       sprintf (buf, "%d", i);
498       polys += draw_text (mi, buf, 1.07, a, 0, 10.0);
499     }
500
501   return polys;
502 }
503
504
505 static int
506 draw_bogies (ModeInfo *mi)
507 {
508   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
509   int polys = 0;
510   sonar_bogie *b;
511
512   for (b = sp->displayed; b; b = b->next)
513     {
514       char *s = (char *) 
515         malloc (strlen (b->name) + (b->desc ? strlen(b->desc) : 0) + 3);
516       strcpy (s, b->name);
517       if (b->desc)
518         {
519           strcat (s, "\n");
520           strcat (s, b->desc);
521         }
522       polys += draw_text (mi, s, b->r, b->th, b->ttl, -1);
523       free (s);
524
525       /* Move *very slightly* forward so that the text is not all in the
526          same plane: this prevents flickering with overlapping text as
527          the textures fight for priority. */
528       glTranslatef(0, 0, 0.00002);
529     }
530
531   return polys;
532 }
533
534
535 /* called from sonar-sim.c and sonar-icmp.c */
536 sonar_bogie *
537 sonar_copy_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
538 {
539   sonar_bogie *b2 = (sonar_bogie *) calloc (1, sizeof(*b2));
540   b2->name = strdup (b->name);
541   b2->desc = b->desc ? strdup (b->desc) : 0;
542   b2->r    = b->r;
543   b2->th   = b->th;
544   b2->ttl  = b->ttl;
545   /* does not copy b->closure */
546
547   /* Take this opportunity to normalize 'th' to the range [0-2pi). */
548   while (b2->th < 0)       b2->th += M_PI*2;
549   while (b2->th >= M_PI*2) b2->th -= M_PI*2;
550
551   return b2;
552 }
553
554
555 /* called from sonar-icmp.c */
556 void
557 sonar_free_bogie (sonar_sensor_data *ssd, sonar_bogie *b)
558 {
559   if (b->closure)
560     ssd->free_bogie_cb (ssd, b->closure);
561   free (b->name);
562   if (b->desc) free (b->desc);
563   free (b);
564 }
565
566 /* removes it from the list and frees it
567  */
568 static void
569 delete_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
570               sonar_bogie **from_list)
571 {
572   sonar_bogie *ob, *prev;
573   for (prev = 0, ob = *from_list; ob; prev = ob, ob = ob->next)
574     if (ob == b)
575       {
576         if (prev)
577           prev->next = b->next;
578         else
579           (*from_list) = b->next;
580         sonar_free_bogie (ssd, b);
581         break;
582       }
583 }
584
585
586 /* copies the bogie and adds it to the list.
587    if there's another bogie there with the same name, frees that one.
588  */
589 static void
590 copy_and_insert_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
591                        sonar_bogie **to_list)
592 {
593   sonar_bogie *ob, *next;
594   if (!b) abort();
595   for (ob = *to_list, next = ob ? ob->next : 0; 
596        ob; 
597        ob = next, next = ob ? ob->next : 0)
598     {
599       if (ob == b) abort();   /* this will end badly */
600       if (!strcmp (ob->name, b->name))  /* match! */
601         {
602           delete_bogie (ssd, ob, to_list);
603           break;
604         }
605     }
606
607   b = sonar_copy_bogie (ssd, b);
608   b->next = *to_list;
609   *to_list = b;
610 }
611
612
613 static void
614 update_sensor_data (sonar_configuration *sp)
615 {
616   sonar_bogie *new_list = sp->ssd->scan_cb (sp->ssd);
617   sonar_bogie *b2;
618
619   /* If a bogie exists in 'new_list' but not 'pending', add it.
620      If a bogie exists in both, update it in 'pending'.
621    */
622   for (b2 = new_list; b2; b2 = b2->next)
623     {
624       if (debug_p > 2)
625         fprintf (stderr, "%s:   updated: %s (%5.2f %5.2f %5.2f)\n", 
626                  progname, b2->name, b2->r, b2->th, b2->ttl);
627       copy_and_insert_bogie (sp->ssd, b2, &sp->pending);
628     }
629   if (debug_p > 2) fprintf (stderr, "\n");
630 }
631
632
633 /* Returns whether the given angle lies between two other angles.
634    When those angles cross 0, it assumes the wedge is the smaller one.
635    That is: 5 lies between 10 and 350 degrees (a 20 degree wedge).
636  */
637 static Bool
638 point_in_wedge (GLfloat th, GLfloat low, GLfloat high)
639 {
640   if (low < high)
641     return (th > low && th <= high);
642   else
643     return (th <= high || th > low);
644 }
645
646
647 /* Returns the current time in seconds as a double.
648  */
649 static double
650 double_time (void)
651 {
652   struct timeval now;
653 # ifdef GETTIMEOFDAY_TWO_ARGS
654   struct timezone tzp;
655   gettimeofday(&now, &tzp);
656 # else
657   gettimeofday(&now);
658 # endif
659
660   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
661 }
662
663
664 static void
665 sweep (sonar_configuration *sp)
666 {
667   sonar_bogie *b;
668
669   /* Move the sweep forward (clockwise).
670    */
671   GLfloat prev_sweep, this_sweep, tick;
672   GLfloat cycle_secs = 30 / speed;  /* default to one cycle every N seconds */
673   this_sweep = ((cycle_secs - fmod (double_time() - sp->start_time +
674                                     sp->sweep_offset,
675                                     cycle_secs))
676                 / cycle_secs
677                 * M_PI * 2);
678   prev_sweep = sp->sweep_th;
679   tick = prev_sweep - this_sweep;
680   while (tick < 0) tick += M_PI*2;
681
682   sp->sweep_th = this_sweep;
683
684   if (this_sweep < 0 || this_sweep >= M_PI*2) abort();
685   if (prev_sweep < 0)  /* skip first time */
686     return;
687
688   if (tick < 0 || tick >= M_PI*2) abort();
689
690
691   /* Go through the 'pending' sensor data, find those bogies who are
692      just now being swept, and move them from 'pending' to 'displayed'.
693      (Leave bogies that have not yet been swept alone: we'll get to
694      them when the sweep moves forward.)
695    */
696   b = sp->pending;
697   while (b)
698     {
699       sonar_bogie *next = b->next;
700       if (point_in_wedge (b->th, this_sweep, prev_sweep))
701         {
702           if (debug_p > 1) {
703             time_t t = time((time_t *) 0);
704             fprintf (stderr,
705                      "%s: sweep hit: %02d:%02d: %s: (%5.2f %5.2f %5.2f;"
706                      " th=[%.2f < %.2f <= %.2f])\n", 
707                      progname,
708                      (int) (t / 60) % 60, (int) t % 60,
709                      b->name, b->r, b->th, b->ttl,
710                      this_sweep, b->th, prev_sweep);
711           }
712           b->ttl = M_PI * 2.1;
713           copy_and_insert_bogie (sp->ssd, b, &sp->displayed);
714           delete_bogie (sp->ssd, b, &sp->pending);
715         }
716       b = next;
717     }
718
719
720   /* Update TTL on all currently-displayed bogies; delete the dead.
721
722      Request sensor updates on the ones just now being swept.
723
724      Any updates go into 'pending' and might not show up until
725      the next time the sweep comes around.  This is to prevent
726      already-drawn bogies from jumping to a new position without
727      having faded out first.
728   */
729   b = sp->displayed;
730   while (b)
731     {
732       sonar_bogie *next = b->next;
733       b->ttl -= tick;
734
735       if (b->ttl <= 0)
736         {
737           if (debug_p > 1)
738             fprintf (stderr, "%s: TTL expired: %s (%5.2f %5.2f %5.2f)\n",
739                      progname, b->name, b->r, b->th, b->ttl);
740           delete_bogie (sp->ssd, b, &sp->displayed);
741         }
742       b = next;
743     }
744
745   update_sensor_data (sp);
746 }
747
748
749 static void
750 draw_startup_blurb (ModeInfo *mi)
751 {
752   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
753   const char *msg = (sp->error ? sp->error : "Resolving hosts...");
754   static const GLfloat color[4] = {0, 1, 0, 1};
755
756   if (!sp->error && ping_arg && !strcmp (ping_arg, "simulation"))
757     return;  /* don't bother */
758
759   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
760   glTranslatef (0, 0, 0.3);
761   draw_text (mi, msg, 0, 0, 0, 30.0);
762
763   /* only leave error message up for N seconds */
764   if (sp->error &&
765       sp->start_time + 6 < double_time())
766     {
767       free (sp->error);
768       sp->error = 0;
769     }
770 }
771
772
773 /* Window management, etc
774  */
775 ENTRYPOINT void
776 reshape_sonar (ModeInfo *mi, int width, int height)
777 {
778   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
779   GLfloat h = (GLfloat) height / (GLfloat) width;
780
781   glViewport (0, 0, (GLint) width, (GLint) height);
782
783   glMatrixMode(GL_PROJECTION);
784   glLoadIdentity();
785   gluPerspective (30.0, 1/h, 1.0, 100.0);
786
787   glMatrixMode(GL_MODELVIEW);
788   glLoadIdentity();
789   gluLookAt( 0.0, 0.0, 30.0,
790              0.0, 0.0, 0.0,
791              0.0, 1.0, 0.0);
792
793   glClear(GL_COLOR_BUFFER_BIT);
794
795   sp->line_thickness = (MI_IS_WIREFRAME (mi) ? 1 : MAX (1, height / 300.0));
796 }
797
798
799 ENTRYPOINT Bool
800 sonar_handle_event (ModeInfo *mi, XEvent *event)
801 {
802   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
803
804   if (gltrackball_event_handler (event, sp->trackball,
805                                  MI_WIDTH (mi), MI_HEIGHT (mi),
806                                  &sp->button_down_p))
807     return True;
808
809   return False;
810 }
811
812
813 ENTRYPOINT void 
814 init_sonar (ModeInfo *mi)
815 {
816   sonar_configuration *sp;
817
818   if (!sps) {
819     sps = (sonar_configuration *)
820       calloc (MI_NUM_SCREENS(mi), sizeof (sonar_configuration));
821     if (!sps) {
822       fprintf(stderr, "%s: out of memory\n", progname);
823       exit(1);
824     }
825   }
826   sp = &sps[MI_SCREEN(mi)];
827   sp->glx_context = init_GL(mi);
828
829   reshape_sonar (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
830   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
831
832   sp->trackball = gltrackball_init (False);
833   sp->rot = make_rotator (0, 0, 0, 0, speed * 0.003, True);
834
835   sp->texfont = load_texture_font (MI_DISPLAY(mi), "font");
836   check_gl_error ("loading font");
837
838   sp->table_list = glGenLists (1);
839   glNewList (sp->table_list, GL_COMPILE);
840   sp->table_polys = draw_table (mi);
841   glEndList ();
842
843   sp->screen_list = glGenLists (1);
844   glNewList (sp->screen_list, GL_COMPILE);
845   sp->screen_polys = draw_screen (mi, False, False);
846   glEndList ();
847
848   sp->grid_list = glGenLists (1);
849   glNewList (sp->grid_list, GL_COMPILE);
850   sp->grid_polys = draw_screen (mi, True,  False);
851   glEndList ();
852
853   sp->sweep_list = glGenLists (1);
854   glNewList (sp->sweep_list, GL_COMPILE);
855   sp->sweep_polys = draw_screen (mi, False, True);
856   glEndList ();
857
858   sp->start_time = double_time ();
859   sp->sweep_offset = random() % 60;
860   sp->sweep_th = -1;
861   sp->state = MSG;
862 }
863
864
865 # ifdef TEST_ASYNC_NETDB
866
867 #   include <arpa/inet.h>
868
869 static void _print_sockaddr (void *addr, socklen_t addrlen, FILE *stream)
870 {
871   sa_family_t family = ((struct sockaddr *)addr)->sa_family;
872   char buf[256];
873   switch (family)
874     {
875     case AF_INET:
876       fputs (inet_ntoa(((struct sockaddr_in *)addr)->sin_addr), stream);
877       break;
878     case AF_INET6:
879       inet_ntop(family, &((struct sockaddr_in6 *)addr)->sin6_addr,
880                 buf, sizeof (buf));
881       fputs (buf, stream);
882       break;
883     default:
884       abort();
885       break;
886     }
887 }
888
889 static void _print_error (int gai_error, int errno_error, FILE *stream)
890 {
891   fputs (gai_error == EAI_SYSTEM ? strerror(errno_error) : gai_strerror(gai_error), stream);
892 }
893
894 #   if ASYNC_NETDB_USE_GAI
895
896 static void _print_thread (pthread_t thread, FILE *stream)
897 {
898 #     ifdef __linux__
899     fprintf (stream, "%#lx", thread);
900 #     elif defined __APPLE__ && defined __MACH__
901     fprintf (stream, "%p", thread);
902 #     else
903     putc ('?', stream);
904 #     endif
905 }
906
907 #   endif /* ASYNC_NETDB_USE_GAI */
908
909 # endif /* TEST_ASYNC_NETDB */
910
911
912 static void
913 init_sensor (ModeInfo *mi)
914 {
915   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
916
917   if (sp->ssd) abort();
918
919   if (!ping_arg || !*ping_arg ||
920       !strcmp(ping_arg, "default") ||
921       !!strcmp (ping_arg, "simulation"))
922     sp->ssd = sonar_init_ping (MI_DISPLAY (mi), &sp->error, &sp->desc,
923                                ping_arg, ping_timeout, resolve_p, times_p,
924                                debug_p);
925
926   sp->start_time = double_time ();  /* for error message timing */
927
928   /* Disavow privs.  This was already done in init_ping(), but
929      we might not have called that at all, so do it again. */
930   setuid(getuid());
931
932   if (!sp->ssd)
933     sp->ssd = sonar_init_simulation (MI_DISPLAY (mi), &sp->error, &sp->desc,
934                                      team_a_name, team_b_name,
935                                      team_a_count, team_b_count,
936                                      debug_p);
937   if (!sp->ssd)
938     abort();
939
940 # if TEST_ASYNC_NETDB
941   /*
942      For extremely mysterious reasons, setuid apparently causes
943      pthread_join(3) to deadlock.
944      A rough guess at the sequence of events:
945      1. Worker thread is created.
946      2. Worker thread exits.
947      3. setuid(getuid()) is called.
948      4. pthread_join is called slightly later.
949
950      This may have something to do with glibc's use of SIGSETXID.
951    */
952
953   putc ('\n', stderr);
954
955 #   if !ASYNC_NETDB_USE_GAI
956   fputs ("Warning: getaddrinfo() was not available at compile time.\n", stderr);
957 #   endif
958
959   {
960     static const unsigned long addresses[] =
961       {
962         INADDR_LOOPBACK,
963         0x00010203,
964         0x08080808
965       };
966     struct sockaddr_in addr;
967     addr.sin_family = AF_INET;
968     addr.sin_port = 0;
969     addr.sin_addr.s_addr = htonl (addresses[random () % 3]);
970
971     sp->query0 = async_name_from_addr_start (MI_DISPLAY (mi), (void *)&addr,
972                                              sizeof(addr));
973     assert (sp->query0);
974     if (sp->query0)
975       {
976         fputs ("Looking up hostname from address: ", stderr);
977         _print_sockaddr (&addr, sizeof(addr), stderr);
978 #   if ASYNC_NETDB_USE_GAI
979         fputs (" @ ", stderr);
980         _print_thread (sp->query0->io.thread, stderr);
981 #   endif
982         putc ('\n', stderr);
983       }
984
985     if (!(random () & 3))
986       {
987         fputs ("Aborted hostname lookup (early)\n", stderr);
988         async_name_from_addr_cancel (sp->query0);
989         sp->query0 = NULL;
990       }
991   }
992
993   {
994     static const char *const hosts[] =
995       {
996         "example.com",
997         "invalid",
998         "ip6-localhost"
999       };
1000     const char *host = hosts[random () % 3];
1001
1002     sp->query1 = async_addr_from_name_start (MI_DISPLAY(mi), host);
1003
1004     assert (sp->query1);
1005
1006     fprintf (stderr, "Looking up address from hostname: %s", host);
1007 #   if ASYNC_NETDB_USE_GAI
1008     fputs (" @ ", stderr);
1009     _print_thread (sp->query1->io.thread, stderr);
1010 #   endif
1011     putc ('\n', stderr);
1012
1013     if (!(random () & 3))
1014       {
1015         fputs ("Aborted address lookup (early)\n", stderr);
1016         async_addr_from_name_cancel (sp->query1);
1017         sp->query1 = NULL;
1018       }
1019   }
1020
1021   fflush (stderr);
1022 # endif
1023 }
1024
1025
1026 ENTRYPOINT void
1027 draw_sonar (ModeInfo *mi)
1028 {
1029   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
1030   Display *dpy = MI_DISPLAY(mi);
1031   Window window = MI_WINDOW(mi);
1032   int wire = MI_IS_WIREFRAME(mi);
1033
1034   if (!sp->glx_context)
1035     return;
1036
1037   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
1038
1039   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1040
1041   if (!wire)
1042     {
1043       GLfloat pos[4] = {0.05, 0.07, 1.00, 0.0};
1044       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
1045       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1046       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
1047
1048       glEnable(GL_TEXTURE_2D);
1049       glEnable(GL_LIGHTING);
1050       glEnable(GL_LIGHT0);
1051       glEnable(GL_CULL_FACE);
1052       glEnable(GL_DEPTH_TEST);
1053       glEnable(GL_NORMALIZE);
1054       glEnable(GL_LINE_SMOOTH);
1055       glEnable(GL_BLEND);
1056       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1057       glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
1058       glShadeModel(GL_SMOOTH);
1059
1060       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1061       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1062       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1063       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1064     }
1065
1066   glPushMatrix ();
1067   glRotatef(current_device_rotation(), 0, 0, 1);
1068
1069   {
1070     GLfloat s = 7;
1071     if (MI_WIDTH(mi) < MI_HEIGHT(mi))
1072       s *= (MI_WIDTH(mi) / (float) MI_HEIGHT(mi));
1073     glScalef (s,s,s);
1074   }
1075
1076   gltrackball_rotate (sp->trackball);
1077
1078   if (wobble_p)
1079     {
1080       double x, y, z;
1081       double max = 40;
1082       get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
1083       glRotatef (max/2 - x*max, 1, 0, 0);
1084       glRotatef (max/2 - z*max, 0, 1, 0);
1085     }
1086
1087   mi->polygon_count = 0;
1088
1089   glPushMatrix();                                       /* table */
1090   glCallList (sp->table_list);
1091   mi->polygon_count += sp->table_polys;
1092   glPopMatrix();
1093
1094   glPushMatrix();                                       /* text */
1095   glTranslatef (0, 0, -0.01);
1096   mi->polygon_count += draw_bogies (mi);
1097   glPopMatrix();
1098
1099   glCallList (sp->screen_list);                         /* glass */
1100   mi->polygon_count += sp->screen_polys;
1101
1102   glTranslatef (0, 0, 0.004);                           /* sweep */
1103   glPushMatrix();
1104   glRotatef ((sp->sweep_th * 180 / M_PI), 0, 0, 1);
1105   if (sp->sweep_th >= 0)
1106     glCallList (sp->sweep_list);
1107   mi->polygon_count += sp->sweep_polys;
1108   glPopMatrix();
1109
1110   glTranslatef (0, 0, 0.004);                           /* grid */
1111   glCallList (sp->grid_list);
1112   mi->polygon_count += sp->screen_polys;
1113
1114   glPushMatrix();
1115   mi->polygon_count += draw_angles (mi);                /* angles */
1116   glPopMatrix();
1117
1118   if (sp->desc)                                         /* local subnet */
1119     {
1120       glPushMatrix();
1121       glTranslatef (0, 0, 0.00002);
1122       mi->polygon_count += draw_text (mi, sp->desc, 1.35, M_PI * 0.75, 0, 10);
1123       /* glRotatef (45, 0, 0, 1); */
1124       /* mi->polygon_count += draw_text (mi, sp->desc, 1.2, M_PI/2, 0, 10); */
1125       glPopMatrix();
1126     }
1127
1128   if (sp->error)
1129     sp->state = MSG;
1130
1131   switch (sp->state) {
1132   case MSG:                     /* Frame 1: get "Resolving Hosts" on screen. */
1133     draw_startup_blurb(mi);
1134     sp->state++;
1135     break;
1136   case RESOLVE:                 /* Frame 2: gethostbyaddr may take a while. */
1137     if (! sp->ssd)
1138       init_sensor (mi);
1139     sp->state++;
1140     break;
1141   case RUN:                     /* Frame N: ping away */
1142     sweep (sp);
1143     break;
1144   }
1145
1146   glPopMatrix ();
1147
1148   if (mi->fps_p) do_fps (mi);
1149   glFinish();
1150
1151   glXSwapBuffers(dpy, window);
1152
1153 # if TEST_ASYNC_NETDB
1154   if(sp->query0 && async_name_from_addr_is_done (sp->query0))
1155     {
1156       if (!(random () & 3))
1157         {
1158           fputs ("Aborted hostname lookup (late)\n", stderr);
1159           async_name_from_addr_cancel (sp->query0);
1160         }
1161       else
1162         {
1163           char *hostname = NULL;
1164           int errno_error;
1165           int gai_error = async_name_from_addr_finish (sp->query0, &hostname,
1166                                                        &errno_error);
1167
1168           if(gai_error)
1169             {
1170               fputs ("Couldn't get hostname: ", stderr);
1171               _print_error (gai_error, errno_error, stderr);
1172               putc ('\n', stderr);
1173             }
1174           else
1175             {
1176               fprintf (stderr, "Got a hostname: %s\n", hostname);
1177               free (hostname);
1178             }
1179         }
1180
1181       sp->query0 = NULL;
1182     }
1183
1184   if(sp->query1 && async_addr_from_name_is_done (sp->query1))
1185     {
1186       if (!(random () & 3))
1187         {
1188           fputs ("Aborted address lookup (late)\n", stderr);
1189           async_addr_from_name_cancel (sp->query1);
1190         }
1191       else
1192         {
1193           async_netdb_sockaddr_storage_t addr;
1194           socklen_t addrlen;
1195           int errno_error;
1196           int gai_error = async_addr_from_name_finish (sp->query1, &addr,
1197                                                        &addrlen, &errno_error);
1198
1199           if (gai_error)
1200             {
1201               fputs ("Couldn't get address: ", stderr);
1202               _print_error (gai_error, errno_error, stderr);
1203               putc ('\n', stderr);
1204             }
1205           else
1206             {
1207               fputs ("Got an address: ", stderr);
1208               _print_sockaddr (&addr, addrlen, stderr);
1209               putc ('\n', stderr);
1210             }
1211         }
1212
1213       sp->query1 = NULL;
1214     }
1215
1216   fflush (stderr);
1217 # endif /* TEST_ASYNC_NETDB */
1218 }
1219
1220 ENTRYPOINT void
1221 release_sonar (ModeInfo *mi)
1222 {
1223 #if 0
1224   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
1225   sonar_bogie *b = sp->displayed;
1226   while (b)
1227     {
1228       sonar_bogie *next = b->next;
1229       free_bogie (sp->ssd, b);
1230       b = next;
1231     }
1232   sp->displayed = 0;
1233
1234   b = sp->pending;
1235   while (b)
1236     {
1237       sonar_bogie *next = b->next;
1238       free_bogie (sp->ssd, b);
1239       b = next;
1240     }
1241   sp->pending = 0;
1242
1243   sp->ssd->free_data_cb (sp->ssd, sp->ssd->closure);
1244   free (sp->ssd);
1245   sp->ssd = 0;
1246 #endif
1247 }
1248
1249 XSCREENSAVER_MODULE ("Sonar", sonar)
1250
1251 #endif /* USE_GL */