From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / hacks / glx / sonar.c
1 /* sonar, Copyright (c) 1998-2018 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 release_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 (MI_WIDTH(mi) > 2560) font_scale /= 2;  /* Retina displays */
358
359   if (size <= 0)   /* if size not specified, draw in yellow with alpha */
360     {
361       GLfloat color[4];
362       color[0] = 1;
363       color[1] = 1;
364       color[2] = 0;
365       color[3] = (ttl / (M_PI * 2)) * 1.2;
366       if (color[3] > 1) color[3] = 1;
367
368       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
369       if (wire)
370         glColor3f (color[0]*color[3], color[1]*color[3], color[2]*color[3]);
371     }
372
373   while ((line = strtok (token, "\r\n")))
374     {
375       XCharStruct e;
376       int w, ascent, descent;
377       texture_string_metrics (sp->texfont, line, &e, &ascent, &descent);
378       w = e.width;
379       lh = ascent + descent;
380       if (w > max_w) max_w = w;
381       lines++;
382       token = 0;
383     }
384
385   glPushMatrix();
386   glTranslatef (r * cos (th), r * sin(th), 0);
387   glScalef (font_scale, font_scale, font_scale);
388
389   if (size <= 0)                /* Draw the dot */
390     {
391       GLfloat s = font_size * 1.7;
392       glDisable (GL_TEXTURE_2D);
393       glFrontFace (GL_CW);
394       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
395       glVertex3f (0, s, 0);
396       glVertex3f (s, s, 0);
397       glVertex3f (s, 0, 0);
398       glVertex3f (0, 0, 0);
399       glEnd();
400       glTranslatef (-max_w/2, -lh, 0);
401     }
402   else
403     glTranslatef (-max_w/2, -lh/2, 0);
404
405   /* draw each line, centered */
406   if (! wire) glEnable (GL_TEXTURE_2D);
407   free (string2);
408   string2 = strdup (string);
409   token = string2;
410   while ((line = strtok (token, "\r\n")))
411     {
412       XCharStruct e;
413       int w;
414       texture_string_metrics (sp->texfont, line, &e, 0, 0);
415       w = e.width;
416       glPushMatrix();
417       glTranslatef ((max_w-w)/2, 0, polys * 4); /* 'polys' stops Z-fighting. */
418
419       if (wire)
420         {
421           glBegin (GL_LINE_LOOP);
422           glVertex3f (0, 0, 0);
423           glVertex3f (w, 0, 0);
424           glVertex3f (w, lh, 0);
425           glVertex3f (0, lh, 0);
426           glEnd();
427         }
428       else
429         {
430           glFrontFace (GL_CW);
431           print_texture_string (sp->texfont, line);
432         }
433       glPopMatrix();
434       glTranslatef (0, -lh, 0);
435       polys++;
436       token = 0;
437     }
438   glPopMatrix();
439
440   free (string2);
441
442   if (! wire) glEnable (GL_DEPTH_TEST);
443
444   return polys;
445 }
446
447
448 /* There's a disc with a hole in it around the screen, to act as a mask
449    preventing slightly off-screen bogies from showing up.  This clips 'em.
450  */
451 static int
452 draw_table (ModeInfo *mi)
453 {
454   /*sonar_configuration *sp = &sps[MI_SCREEN(mi)];*/
455   int wire = MI_IS_WIREFRAME(mi);
456   int polys = 0;
457   int i;
458   int th_steps = 36 * 4;    /* same as in draw_screen */
459
460   static const GLfloat color[4]  = {0.0, 0.0, 0.0, 1.0};
461   static const GLfloat spec[4]   = {0.0, 0.0, 0.0, 1.0};
462   static const GLfloat shiny     = 0.0;
463
464   if (wire) return 0;
465
466   glDisable (GL_TEXTURE_2D);
467
468   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
469   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
470   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
471
472   glFrontFace (GL_CCW);
473   glBegin(wire ? GL_LINES : GL_QUAD_STRIP);
474   glNormal3f (0, 0, 1);
475   for (i = 0; i <= th_steps; i++)
476     {
477       double a = M_PI * 2 * i / th_steps;
478       double x = cos(a);
479       double y = sin(a);
480       glVertex3f (x, y, 0);
481       glVertex3f (x*10, y*10, 0);
482       polys++;
483     }
484   glEnd();
485
486   return polys;
487 }
488
489
490 static int
491 draw_angles (ModeInfo *mi)
492 {
493   int i;
494   int polys = 0;
495
496   static const GLfloat text[4]   = {0.15, 0.15, 0.15, 1.0};
497   static const GLfloat spec[4]   = {0.0, 0.0, 0.0, 1.0};
498
499   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
500   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, text);
501   glTranslatef (0, 0, 0.01);
502   for (i = 0; i < 360; i += 10)
503     {
504       char buf[10];
505       GLfloat a = M_PI/2 - (i / 180.0 * M_PI);
506       sprintf (buf, "%d", i);
507       polys += draw_text (mi, buf, 1.07, a, 0, 10.0);
508     }
509
510   return polys;
511 }
512
513
514 static int
515 draw_bogies (ModeInfo *mi)
516 {
517   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
518   int polys = 0;
519   sonar_bogie *b;
520
521   for (b = sp->displayed; b; b = b->next)
522     {
523       char *s = (char *) 
524         malloc (strlen (b->name) + (b->desc ? strlen(b->desc) : 0) + 3);
525       strcpy (s, b->name);
526       if (b->desc)
527         {
528           strcat (s, "\n");
529           strcat (s, b->desc);
530         }
531       polys += draw_text (mi, s, b->r, b->th, b->ttl, -1);
532       free (s);
533
534       /* Move *very slightly* forward so that the text is not all in the
535          same plane: this prevents flickering with overlapping text as
536          the textures fight for priority. */
537       glTranslatef(0, 0, 0.00002);
538     }
539
540   return polys;
541 }
542
543
544 /* called from sonar-sim.c and sonar-icmp.c */
545 sonar_bogie *
546 sonar_copy_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
547 {
548   sonar_bogie *b2 = (sonar_bogie *) calloc (1, sizeof(*b2));
549   b2->name = strdup (b->name);
550   b2->desc = b->desc ? strdup (b->desc) : 0;
551   b2->r    = b->r;
552   b2->th   = b->th;
553   b2->ttl  = b->ttl;
554   /* does not copy b->closure */
555
556   /* Take this opportunity to normalize 'th' to the range [0-2pi). */
557   while (b2->th < 0)       b2->th += M_PI*2;
558   while (b2->th >= M_PI*2) b2->th -= M_PI*2;
559
560   return b2;
561 }
562
563
564 /* called from sonar-icmp.c */
565 void
566 sonar_free_bogie (sonar_sensor_data *ssd, sonar_bogie *b)
567 {
568   if (b->closure)
569     ssd->free_bogie_cb (ssd, b->closure);
570   free (b->name);
571   if (b->desc) free (b->desc);
572   free (b);
573 }
574
575 /* removes it from the list and frees it
576  */
577 static void
578 delete_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
579               sonar_bogie **from_list)
580 {
581   sonar_bogie *ob, *prev;
582   for (prev = 0, ob = *from_list; ob; prev = ob, ob = ob->next)
583     if (ob == b)
584       {
585         if (prev)
586           prev->next = b->next;
587         else
588           (*from_list) = b->next;
589         sonar_free_bogie (ssd, b);
590         break;
591       }
592 }
593
594
595 /* copies the bogie and adds it to the list.
596    if there's another bogie there with the same name, frees that one.
597  */
598 static void
599 copy_and_insert_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
600                        sonar_bogie **to_list)
601 {
602   sonar_bogie *ob, *next;
603   if (!b) abort();
604   for (ob = *to_list, next = ob ? ob->next : 0; 
605        ob; 
606        ob = next, next = ob ? ob->next : 0)
607     {
608       if (ob == b) abort();   /* this will end badly */
609       if (!strcmp (ob->name, b->name))  /* match! */
610         {
611           delete_bogie (ssd, ob, to_list);
612           break;
613         }
614     }
615
616   b = sonar_copy_bogie (ssd, b);
617   b->next = *to_list;
618   *to_list = b;
619 }
620
621
622 static void
623 update_sensor_data (sonar_configuration *sp)
624 {
625   sonar_bogie *new_list = sp->ssd->scan_cb (sp->ssd);
626   sonar_bogie *b2;
627
628   /* If a bogie exists in 'new_list' but not 'pending', add it.
629      If a bogie exists in both, update it in 'pending'.
630    */
631   for (b2 = new_list; b2; b2 = b2->next)
632     {
633       if (debug_p > 2)
634         fprintf (stderr, "%s:   updated: %s (%5.2f %5.2f %5.2f)\n", 
635                  progname, b2->name, b2->r, b2->th, b2->ttl);
636       copy_and_insert_bogie (sp->ssd, b2, &sp->pending);
637     }
638   if (debug_p > 2) fprintf (stderr, "\n");
639 }
640
641
642 /* Returns whether the given angle lies between two other angles.
643    When those angles cross 0, it assumes the wedge is the smaller one.
644    That is: 5 lies between 10 and 350 degrees (a 20 degree wedge).
645  */
646 static Bool
647 point_in_wedge (GLfloat th, GLfloat low, GLfloat high)
648 {
649   if (low < high)
650     return (th > low && th <= high);
651   else
652     return (th <= high || th > low);
653 }
654
655
656 /* Returns the current time in seconds as a double.
657  */
658 static double
659 double_time (void)
660 {
661   struct timeval now;
662 # ifdef GETTIMEOFDAY_TWO_ARGS
663   struct timezone tzp;
664   gettimeofday(&now, &tzp);
665 # else
666   gettimeofday(&now);
667 # endif
668
669   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
670 }
671
672
673 static void
674 sweep (sonar_configuration *sp)
675 {
676   sonar_bogie *b;
677
678   /* Move the sweep forward (clockwise).
679    */
680   GLfloat prev_sweep, this_sweep, tick;
681   GLfloat cycle_secs = 30 / speed;  /* default to one cycle every N seconds */
682   this_sweep = ((cycle_secs - fmod (double_time() - sp->start_time +
683                                     sp->sweep_offset,
684                                     cycle_secs))
685                 / cycle_secs
686                 * M_PI * 2);
687   prev_sweep = sp->sweep_th;
688   tick = prev_sweep - this_sweep;
689   while (tick < 0) tick += M_PI*2;
690
691   sp->sweep_th = this_sweep;
692
693   if (this_sweep < 0 || this_sweep >= M_PI*2) abort();
694   if (prev_sweep < 0)  /* skip first time */
695     return;
696
697   if (tick < 0 || tick >= M_PI*2) abort();
698
699
700   /* Go through the 'pending' sensor data, find those bogies who are
701      just now being swept, and move them from 'pending' to 'displayed'.
702      (Leave bogies that have not yet been swept alone: we'll get to
703      them when the sweep moves forward.)
704    */
705   b = sp->pending;
706   while (b)
707     {
708       sonar_bogie *next = b->next;
709       if (point_in_wedge (b->th, this_sweep, prev_sweep))
710         {
711           if (debug_p > 1) {
712             time_t t = time((time_t *) 0);
713             fprintf (stderr,
714                      "%s: sweep hit: %02d:%02d: %s: (%5.2f %5.2f %5.2f;"
715                      " th=[%.2f < %.2f <= %.2f])\n", 
716                      progname,
717                      (int) (t / 60) % 60, (int) t % 60,
718                      b->name, b->r, b->th, b->ttl,
719                      this_sweep, b->th, prev_sweep);
720           }
721           b->ttl = M_PI * 2.1;
722           copy_and_insert_bogie (sp->ssd, b, &sp->displayed);
723           delete_bogie (sp->ssd, b, &sp->pending);
724         }
725       b = next;
726     }
727
728
729   /* Update TTL on all currently-displayed bogies; delete the dead.
730
731      Request sensor updates on the ones just now being swept.
732
733      Any updates go into 'pending' and might not show up until
734      the next time the sweep comes around.  This is to prevent
735      already-drawn bogies from jumping to a new position without
736      having faded out first.
737   */
738   b = sp->displayed;
739   while (b)
740     {
741       sonar_bogie *next = b->next;
742       b->ttl -= tick;
743
744       if (b->ttl <= 0)
745         {
746           if (debug_p > 1)
747             fprintf (stderr, "%s: TTL expired: %s (%5.2f %5.2f %5.2f)\n",
748                      progname, b->name, b->r, b->th, b->ttl);
749           delete_bogie (sp->ssd, b, &sp->displayed);
750         }
751       b = next;
752     }
753
754   update_sensor_data (sp);
755 }
756
757
758 static void
759 draw_startup_blurb (ModeInfo *mi)
760 {
761   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
762
763   if (sp->error)
764     {
765       const char *msg = sp->error;
766       static const GLfloat color[4] = {0, 1, 0, 1};
767
768       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
769       glTranslatef (0, 0, 0.3);
770       draw_text (mi, msg, 0, 0, 0, 30.0);
771
772       /* only leave error message up for N seconds */
773       if (sp->start_time + 6 < double_time())
774         {
775           free (sp->error);
776           sp->error = 0;
777         }
778     }
779 }
780
781
782 /* Window management, etc
783  */
784 ENTRYPOINT void
785 reshape_sonar (ModeInfo *mi, int width, int height)
786 {
787   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
788   GLfloat h = (GLfloat) height / (GLfloat) width;
789   int y = 0;
790
791   if (width > height * 5) {   /* tiny window: show middle */
792     height = width * 9/16;
793     y = -height/2;
794     h = height / (GLfloat) width;
795   }
796
797   glViewport (0, y, (GLint) width, (GLint) height);
798
799   glMatrixMode(GL_PROJECTION);
800   glLoadIdentity();
801   gluPerspective (30.0, 1/h, 1.0, 100.0);
802
803   glMatrixMode(GL_MODELVIEW);
804   glLoadIdentity();
805   gluLookAt( 0.0, 0.0, 30.0,
806              0.0, 0.0, 0.0,
807              0.0, 1.0, 0.0);
808
809   glClear(GL_COLOR_BUFFER_BIT);
810
811   sp->line_thickness = (MI_IS_WIREFRAME (mi) ? 1 : MAX (1, height / 300.0));
812 }
813
814
815 ENTRYPOINT Bool
816 sonar_handle_event (ModeInfo *mi, XEvent *event)
817 {
818   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
819
820   if (gltrackball_event_handler (event, sp->trackball,
821                                  MI_WIDTH (mi), MI_HEIGHT (mi),
822                                  &sp->button_down_p))
823     return True;
824
825   return False;
826 }
827
828 ENTRYPOINT void 
829 init_sonar (ModeInfo *mi)
830 {
831   sonar_configuration *sp;
832
833   MI_INIT (mi, sps);
834   sp = &sps[MI_SCREEN(mi)];
835   sp->glx_context = init_GL(mi);
836
837   reshape_sonar (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
838   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
839
840   sp->trackball = gltrackball_init (False);
841   sp->rot = make_rotator (0, 0, 0, 0, speed * 0.003, True);
842
843   sp->texfont = load_texture_font (MI_DISPLAY(mi), "font");
844   check_gl_error ("loading font");
845
846   sp->table_list = glGenLists (1);
847   glNewList (sp->table_list, GL_COMPILE);
848   sp->table_polys = draw_table (mi);
849   glEndList ();
850
851   sp->screen_list = glGenLists (1);
852   glNewList (sp->screen_list, GL_COMPILE);
853   sp->screen_polys = draw_screen (mi, False, False);
854   glEndList ();
855
856   sp->grid_list = glGenLists (1);
857   glNewList (sp->grid_list, GL_COMPILE);
858   sp->grid_polys = draw_screen (mi, True,  False);
859   glEndList ();
860
861   sp->sweep_list = glGenLists (1);
862   glNewList (sp->sweep_list, GL_COMPILE);
863   sp->sweep_polys = draw_screen (mi, False, True);
864   glEndList ();
865
866   sp->start_time = double_time ();
867   sp->sweep_offset = random() % 60;
868   sp->sweep_th = -1;
869   sp->state = MSG;
870 }
871
872
873 # ifdef TEST_ASYNC_NETDB
874
875 #   include <arpa/inet.h>
876
877 static void _print_sockaddr (void *addr, socklen_t addrlen, FILE *stream)
878 {
879   sa_family_t family = ((struct sockaddr *)addr)->sa_family;
880   char buf[256];
881   switch (family)
882     {
883     case AF_INET:
884       fputs (inet_ntoa(((struct sockaddr_in *)addr)->sin_addr), stream);
885       break;
886     case AF_INET6:
887       inet_ntop(family, &((struct sockaddr_in6 *)addr)->sin6_addr,
888                 buf, sizeof (buf));
889       fputs (buf, stream);
890       break;
891     default:
892       abort();
893       break;
894     }
895 }
896
897 static void _print_error (int gai_error, int errno_error, FILE *stream)
898 {
899   fputs (gai_error == EAI_SYSTEM ? strerror(errno_error) : gai_strerror(gai_error), stream);
900 }
901
902 #   if ASYNC_NETDB_USE_GAI
903
904 static void _print_thread (pthread_t thread, FILE *stream)
905 {
906 #     ifdef __linux__
907     fprintf (stream, "%#lx", thread);
908 #     elif defined __APPLE__ && defined __MACH__
909     fprintf (stream, "%p", thread);
910 #     else
911     putc ('?', stream);
912 #     endif
913 }
914
915 #   endif /* ASYNC_NETDB_USE_GAI */
916
917 # endif /* TEST_ASYNC_NETDB */
918
919
920 static void
921 init_sensor (ModeInfo *mi)
922 {
923   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
924
925   if (sp->ssd) abort();
926
927   if (!ping_arg || !*ping_arg ||
928       !strcmp(ping_arg, "default") ||
929       !!strcmp (ping_arg, "simulation"))
930     /* sonar_init_ping() always disavows privs, even on failure. */
931     sp->ssd = sonar_init_ping (MI_DISPLAY (mi), &sp->error, &sp->desc,
932                                ping_arg, ping_timeout, resolve_p, times_p,
933                                debug_p);
934   else
935     setuid(getuid()); /* Disavow privs if not pinging. */
936
937   sp->start_time = double_time ();  /* for error message timing */
938
939   if (!sp->ssd)
940     sp->ssd = sonar_init_simulation (MI_DISPLAY (mi), &sp->error, &sp->desc,
941                                      team_a_name, team_b_name,
942                                      team_a_count, team_b_count,
943                                      debug_p);
944   if (!sp->ssd)
945     abort();
946
947 # if TEST_ASYNC_NETDB
948   /*
949      For extremely mysterious reasons, setuid apparently causes
950      pthread_join(3) to deadlock.
951      A rough guess at the sequence of events:
952      1. Worker thread is created.
953      2. Worker thread exits.
954      3. setuid(getuid()) is called.
955      4. pthread_join is called slightly later.
956
957      This may have something to do with glibc's use of SIGSETXID.
958    */
959
960   putc ('\n', stderr);
961
962 #   if !ASYNC_NETDB_USE_GAI
963   fputs ("Warning: getaddrinfo() was not available at compile time.\n", stderr);
964 #   endif
965
966   {
967     static const unsigned long addresses[] =
968       {
969         INADDR_LOOPBACK,
970         0x00010203,
971         0x08080808
972       };
973     struct sockaddr_in addr;
974     addr.sin_family = AF_INET;
975     addr.sin_port = 0;
976     addr.sin_addr.s_addr = htonl (addresses[random () % 3]);
977
978     sp->query0 = async_name_from_addr_start (MI_DISPLAY (mi), (void *)&addr,
979                                              sizeof(addr));
980     assert (sp->query0);
981     if (sp->query0)
982       {
983         fputs ("Looking up hostname from address: ", stderr);
984         _print_sockaddr (&addr, sizeof(addr), stderr);
985 #   if ASYNC_NETDB_USE_GAI
986         fputs (" @ ", stderr);
987         _print_thread (sp->query0->io.thread, stderr);
988 #   endif
989         putc ('\n', stderr);
990       }
991
992     if (!(random () & 3))
993       {
994         fputs ("Aborted hostname lookup (early)\n", stderr);
995         async_name_from_addr_cancel (sp->query0);
996         sp->query0 = NULL;
997       }
998   }
999
1000   {
1001     static const char *const hosts[] =
1002       {
1003         "example.com",
1004         "invalid",
1005         "ip6-localhost"
1006       };
1007     const char *host = hosts[random () % 3];
1008
1009     sp->query1 = async_addr_from_name_start (MI_DISPLAY(mi), host);
1010
1011     assert (sp->query1);
1012
1013     fprintf (stderr, "Looking up address from hostname: %s", host);
1014 #   if ASYNC_NETDB_USE_GAI
1015     fputs (" @ ", stderr);
1016     _print_thread (sp->query1->io.thread, stderr);
1017 #   endif
1018     putc ('\n', stderr);
1019
1020     if (!(random () & 3))
1021       {
1022         fputs ("Aborted address lookup (early)\n", stderr);
1023         async_addr_from_name_cancel (sp->query1);
1024         sp->query1 = NULL;
1025       }
1026   }
1027
1028   fflush (stderr);
1029 # endif
1030 }
1031
1032
1033 ENTRYPOINT void
1034 draw_sonar (ModeInfo *mi)
1035 {
1036   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
1037   Display *dpy = MI_DISPLAY(mi);
1038   Window window = MI_WINDOW(mi);
1039   int wire = MI_IS_WIREFRAME(mi);
1040
1041   if (!sp->glx_context)
1042     return;
1043
1044   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
1045
1046   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1047
1048   if (!wire)
1049     {
1050       GLfloat pos[4] = {0.05, 0.07, 1.00, 0.0};
1051       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
1052       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1053       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
1054
1055       glEnable(GL_TEXTURE_2D);
1056       glEnable(GL_LIGHTING);
1057       glEnable(GL_LIGHT0);
1058       glEnable(GL_CULL_FACE);
1059       glEnable(GL_DEPTH_TEST);
1060       glEnable(GL_NORMALIZE);
1061       glEnable(GL_LINE_SMOOTH);
1062       glEnable(GL_BLEND);
1063       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1064       glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
1065       glShadeModel(GL_SMOOTH);
1066
1067       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1068       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1069       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1070       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1071     }
1072
1073   glPushMatrix ();
1074   glRotatef(current_device_rotation(), 0, 0, 1);
1075
1076   {
1077     GLfloat s = 7;
1078     if (MI_WIDTH(mi) < MI_HEIGHT(mi))
1079       s *= (MI_WIDTH(mi) / (float) MI_HEIGHT(mi));
1080     glScalef (s,s,s);
1081   }
1082
1083   gltrackball_rotate (sp->trackball);
1084
1085   if (wobble_p)
1086     {
1087       double x, y, z;
1088       double max = 40;
1089       get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
1090       glRotatef (max/2 - x*max, 1, 0, 0);
1091       glRotatef (max/2 - z*max, 0, 1, 0);
1092     }
1093
1094   mi->polygon_count = 0;
1095
1096   glPushMatrix();                                       /* table */
1097   glCallList (sp->table_list);
1098   mi->polygon_count += sp->table_polys;
1099   glPopMatrix();
1100
1101   glPushMatrix();                                       /* text */
1102   glTranslatef (0, 0, -0.01);
1103   mi->polygon_count += draw_bogies (mi);
1104   glPopMatrix();
1105
1106   glCallList (sp->screen_list);                         /* glass */
1107   mi->polygon_count += sp->screen_polys;
1108
1109   glTranslatef (0, 0, 0.004);                           /* sweep */
1110   glPushMatrix();
1111   glRotatef ((sp->sweep_th * 180 / M_PI), 0, 0, 1);
1112   if (sp->sweep_th >= 0)
1113     glCallList (sp->sweep_list);
1114   mi->polygon_count += sp->sweep_polys;
1115   glPopMatrix();
1116
1117   glTranslatef (0, 0, 0.004);                           /* grid */
1118   glCallList (sp->grid_list);
1119   mi->polygon_count += sp->screen_polys;
1120
1121   glPushMatrix();
1122   mi->polygon_count += draw_angles (mi);                /* angles */
1123   glPopMatrix();
1124
1125   if (sp->desc)                                         /* local subnet */
1126     {
1127       glPushMatrix();
1128       glTranslatef (0, 0, 0.00002);
1129       mi->polygon_count += draw_text (mi, sp->desc, 1.35, M_PI * 0.75, 0, 10);
1130       /* glRotatef (45, 0, 0, 1); */
1131       /* mi->polygon_count += draw_text (mi, sp->desc, 1.2, M_PI/2, 0, 10); */
1132       glPopMatrix();
1133     }
1134
1135   if (sp->error)
1136     sp->state = MSG;
1137
1138   switch (sp->state) {
1139   case MSG:                     /* Frame 1: get "Resolving Hosts" on screen. */
1140     draw_startup_blurb(mi);
1141     sp->state++;
1142     break;
1143   case RESOLVE:                 /* Frame 2: gethostbyaddr may take a while. */
1144     if (! sp->ssd)
1145       init_sensor (mi);
1146     sp->state++;
1147     break;
1148   case RUN:                     /* Frame N: ping away */
1149     sweep (sp);
1150     break;
1151   }
1152
1153   glPopMatrix ();
1154
1155   if (mi->fps_p) do_fps (mi);
1156   glFinish();
1157
1158   glXSwapBuffers(dpy, window);
1159
1160 # if TEST_ASYNC_NETDB
1161   if(sp->query0 && async_name_from_addr_is_done (sp->query0))
1162     {
1163       if (!(random () & 3))
1164         {
1165           fputs ("Aborted hostname lookup (late)\n", stderr);
1166           async_name_from_addr_cancel (sp->query0);
1167         }
1168       else
1169         {
1170           char *hostname = NULL;
1171           int errno_error;
1172           int gai_error = async_name_from_addr_finish (sp->query0, &hostname,
1173                                                        &errno_error);
1174
1175           if(gai_error)
1176             {
1177               fputs ("Couldn't get hostname: ", stderr);
1178               _print_error (gai_error, errno_error, stderr);
1179               putc ('\n', stderr);
1180             }
1181           else
1182             {
1183               fprintf (stderr, "Got a hostname: %s\n", hostname);
1184               free (hostname);
1185             }
1186         }
1187
1188       sp->query0 = NULL;
1189     }
1190
1191   if(sp->query1 && async_addr_from_name_is_done (sp->query1))
1192     {
1193       if (!(random () & 3))
1194         {
1195           fputs ("Aborted address lookup (late)\n", stderr);
1196           async_addr_from_name_cancel (sp->query1);
1197         }
1198       else
1199         {
1200           async_netdb_sockaddr_storage_t addr;
1201           socklen_t addrlen;
1202           int errno_error;
1203           int gai_error = async_addr_from_name_finish (sp->query1, &addr,
1204                                                        &addrlen, &errno_error);
1205
1206           if (gai_error)
1207             {
1208               fputs ("Couldn't get address: ", stderr);
1209               _print_error (gai_error, errno_error, stderr);
1210               putc ('\n', stderr);
1211             }
1212           else
1213             {
1214               fputs ("Got an address: ", stderr);
1215               _print_sockaddr (&addr, addrlen, stderr);
1216               putc ('\n', stderr);
1217             }
1218         }
1219
1220       sp->query1 = NULL;
1221     }
1222
1223   fflush (stderr);
1224 # endif /* TEST_ASYNC_NETDB */
1225 }
1226
1227 ENTRYPOINT void
1228 free_sonar (ModeInfo *mi)
1229 {
1230 #if 0
1231   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
1232   sonar_bogie *b = sp->displayed;
1233   while (b)
1234     {
1235       sonar_bogie *next = b->next;
1236       free_bogie (sp->ssd, b);
1237       b = next;
1238     }
1239   sp->displayed = 0;
1240
1241   b = sp->pending;
1242   while (b)
1243     {
1244       sonar_bogie *next = b->next;
1245       free_bogie (sp->ssd, b);
1246       b = next;
1247     }
1248   sp->pending = 0;
1249
1250   sp->ssd->free_data_cb (sp->ssd, sp->ssd->closure);
1251   free (sp->ssd);
1252   sp->ssd = 0;
1253 #endif
1254 }
1255
1256 XSCREENSAVER_MODULE ("Sonar", sonar)
1257
1258 #endif /* USE_GL */