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