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