From http://www.jwz.org/xscreensaver/xscreensaver-5.33.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 #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
788   glViewport (0, 0, (GLint) width, (GLint) height);
789
790   glMatrixMode(GL_PROJECTION);
791   glLoadIdentity();
792   gluPerspective (30.0, 1/h, 1.0, 100.0);
793
794   glMatrixMode(GL_MODELVIEW);
795   glLoadIdentity();
796   gluLookAt( 0.0, 0.0, 30.0,
797              0.0, 0.0, 0.0,
798              0.0, 1.0, 0.0);
799
800   glClear(GL_COLOR_BUFFER_BIT);
801
802   sp->line_thickness = (MI_IS_WIREFRAME (mi) ? 1 : MAX (1, height / 300.0));
803 }
804
805
806 ENTRYPOINT Bool
807 sonar_handle_event (ModeInfo *mi, XEvent *event)
808 {
809   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
810
811   if (gltrackball_event_handler (event, sp->trackball,
812                                  MI_WIDTH (mi), MI_HEIGHT (mi),
813                                  &sp->button_down_p))
814     return True;
815
816   return False;
817 }
818
819
820 ENTRYPOINT void 
821 init_sonar (ModeInfo *mi)
822 {
823   sonar_configuration *sp;
824
825   if (!sps) {
826     sps = (sonar_configuration *)
827       calloc (MI_NUM_SCREENS(mi), sizeof (sonar_configuration));
828     if (!sps) {
829       fprintf(stderr, "%s: out of memory\n", progname);
830       exit(1);
831     }
832   }
833   sp = &sps[MI_SCREEN(mi)];
834   sp->glx_context = init_GL(mi);
835
836   reshape_sonar (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
837   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
838
839   sp->trackball = gltrackball_init (False);
840   sp->rot = make_rotator (0, 0, 0, 0, speed * 0.003, True);
841
842   sp->texfont = load_texture_font (MI_DISPLAY(mi), "font");
843   check_gl_error ("loading font");
844
845   sp->table_list = glGenLists (1);
846   glNewList (sp->table_list, GL_COMPILE);
847   sp->table_polys = draw_table (mi);
848   glEndList ();
849
850   sp->screen_list = glGenLists (1);
851   glNewList (sp->screen_list, GL_COMPILE);
852   sp->screen_polys = draw_screen (mi, False, False);
853   glEndList ();
854
855   sp->grid_list = glGenLists (1);
856   glNewList (sp->grid_list, GL_COMPILE);
857   sp->grid_polys = draw_screen (mi, True,  False);
858   glEndList ();
859
860   sp->sweep_list = glGenLists (1);
861   glNewList (sp->sweep_list, GL_COMPILE);
862   sp->sweep_polys = draw_screen (mi, False, True);
863   glEndList ();
864
865   sp->start_time = double_time ();
866   sp->sweep_offset = random() % 60;
867   sp->sweep_th = -1;
868   sp->state = MSG;
869 }
870
871
872 # ifdef TEST_ASYNC_NETDB
873
874 #   include <arpa/inet.h>
875
876 static void _print_sockaddr (void *addr, socklen_t addrlen, FILE *stream)
877 {
878   sa_family_t family = ((struct sockaddr *)addr)->sa_family;
879   char buf[256];
880   switch (family)
881     {
882     case AF_INET:
883       fputs (inet_ntoa(((struct sockaddr_in *)addr)->sin_addr), stream);
884       break;
885     case AF_INET6:
886       inet_ntop(family, &((struct sockaddr_in6 *)addr)->sin6_addr,
887                 buf, sizeof (buf));
888       fputs (buf, stream);
889       break;
890     default:
891       abort();
892       break;
893     }
894 }
895
896 static void _print_error (int gai_error, int errno_error, FILE *stream)
897 {
898   fputs (gai_error == EAI_SYSTEM ? strerror(errno_error) : gai_strerror(gai_error), stream);
899 }
900
901 #   if ASYNC_NETDB_USE_GAI
902
903 static void _print_thread (pthread_t thread, FILE *stream)
904 {
905 #     ifdef __linux__
906     fprintf (stream, "%#lx", thread);
907 #     elif defined __APPLE__ && defined __MACH__
908     fprintf (stream, "%p", thread);
909 #     else
910     putc ('?', stream);
911 #     endif
912 }
913
914 #   endif /* ASYNC_NETDB_USE_GAI */
915
916 # endif /* TEST_ASYNC_NETDB */
917
918
919 static void
920 init_sensor (ModeInfo *mi)
921 {
922   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
923
924   if (sp->ssd) abort();
925
926   if (!ping_arg || !*ping_arg ||
927       !strcmp(ping_arg, "default") ||
928       !!strcmp (ping_arg, "simulation"))
929     /* sonar_init_ping() always disavows privs, even on failure. */
930     sp->ssd = sonar_init_ping (MI_DISPLAY (mi), &sp->error, &sp->desc,
931                                ping_arg, ping_timeout, resolve_p, times_p,
932                                debug_p);
933   else
934     setuid(getuid()); /* Disavow privs if not pinging. */
935
936   sp->start_time = double_time ();  /* for error message timing */
937
938   if (!sp->ssd)
939     sp->ssd = sonar_init_simulation (MI_DISPLAY (mi), &sp->error, &sp->desc,
940                                      team_a_name, team_b_name,
941                                      team_a_count, team_b_count,
942                                      debug_p);
943   if (!sp->ssd)
944     abort();
945
946 # if TEST_ASYNC_NETDB
947   /*
948      For extremely mysterious reasons, setuid apparently causes
949      pthread_join(3) to deadlock.
950      A rough guess at the sequence of events:
951      1. Worker thread is created.
952      2. Worker thread exits.
953      3. setuid(getuid()) is called.
954      4. pthread_join is called slightly later.
955
956      This may have something to do with glibc's use of SIGSETXID.
957    */
958
959   putc ('\n', stderr);
960
961 #   if !ASYNC_NETDB_USE_GAI
962   fputs ("Warning: getaddrinfo() was not available at compile time.\n", stderr);
963 #   endif
964
965   {
966     static const unsigned long addresses[] =
967       {
968         INADDR_LOOPBACK,
969         0x00010203,
970         0x08080808
971       };
972     struct sockaddr_in addr;
973     addr.sin_family = AF_INET;
974     addr.sin_port = 0;
975     addr.sin_addr.s_addr = htonl (addresses[random () % 3]);
976
977     sp->query0 = async_name_from_addr_start (MI_DISPLAY (mi), (void *)&addr,
978                                              sizeof(addr));
979     assert (sp->query0);
980     if (sp->query0)
981       {
982         fputs ("Looking up hostname from address: ", stderr);
983         _print_sockaddr (&addr, sizeof(addr), stderr);
984 #   if ASYNC_NETDB_USE_GAI
985         fputs (" @ ", stderr);
986         _print_thread (sp->query0->io.thread, stderr);
987 #   endif
988         putc ('\n', stderr);
989       }
990
991     if (!(random () & 3))
992       {
993         fputs ("Aborted hostname lookup (early)\n", stderr);
994         async_name_from_addr_cancel (sp->query0);
995         sp->query0 = NULL;
996       }
997   }
998
999   {
1000     static const char *const hosts[] =
1001       {
1002         "example.com",
1003         "invalid",
1004         "ip6-localhost"
1005       };
1006     const char *host = hosts[random () % 3];
1007
1008     sp->query1 = async_addr_from_name_start (MI_DISPLAY(mi), host);
1009
1010     assert (sp->query1);
1011
1012     fprintf (stderr, "Looking up address from hostname: %s", host);
1013 #   if ASYNC_NETDB_USE_GAI
1014     fputs (" @ ", stderr);
1015     _print_thread (sp->query1->io.thread, stderr);
1016 #   endif
1017     putc ('\n', stderr);
1018
1019     if (!(random () & 3))
1020       {
1021         fputs ("Aborted address lookup (early)\n", stderr);
1022         async_addr_from_name_cancel (sp->query1);
1023         sp->query1 = NULL;
1024       }
1025   }
1026
1027   fflush (stderr);
1028 # endif
1029 }
1030
1031
1032 ENTRYPOINT void
1033 draw_sonar (ModeInfo *mi)
1034 {
1035   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
1036   Display *dpy = MI_DISPLAY(mi);
1037   Window window = MI_WINDOW(mi);
1038   int wire = MI_IS_WIREFRAME(mi);
1039
1040   if (!sp->glx_context)
1041     return;
1042
1043   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
1044
1045   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1046
1047   if (!wire)
1048     {
1049       GLfloat pos[4] = {0.05, 0.07, 1.00, 0.0};
1050       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
1051       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1052       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
1053
1054       glEnable(GL_TEXTURE_2D);
1055       glEnable(GL_LIGHTING);
1056       glEnable(GL_LIGHT0);
1057       glEnable(GL_CULL_FACE);
1058       glEnable(GL_DEPTH_TEST);
1059       glEnable(GL_NORMALIZE);
1060       glEnable(GL_LINE_SMOOTH);
1061       glEnable(GL_BLEND);
1062       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1063       glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
1064       glShadeModel(GL_SMOOTH);
1065
1066       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1067       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1068       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1069       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1070     }
1071
1072   glPushMatrix ();
1073   glRotatef(current_device_rotation(), 0, 0, 1);
1074
1075   {
1076     GLfloat s = 7;
1077     if (MI_WIDTH(mi) < MI_HEIGHT(mi))
1078       s *= (MI_WIDTH(mi) / (float) MI_HEIGHT(mi));
1079     glScalef (s,s,s);
1080   }
1081
1082   gltrackball_rotate (sp->trackball);
1083
1084   if (wobble_p)
1085     {
1086       double x, y, z;
1087       double max = 40;
1088       get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
1089       glRotatef (max/2 - x*max, 1, 0, 0);
1090       glRotatef (max/2 - z*max, 0, 1, 0);
1091     }
1092
1093   mi->polygon_count = 0;
1094
1095   glPushMatrix();                                       /* table */
1096   glCallList (sp->table_list);
1097   mi->polygon_count += sp->table_polys;
1098   glPopMatrix();
1099
1100   glPushMatrix();                                       /* text */
1101   glTranslatef (0, 0, -0.01);
1102   mi->polygon_count += draw_bogies (mi);
1103   glPopMatrix();
1104
1105   glCallList (sp->screen_list);                         /* glass */
1106   mi->polygon_count += sp->screen_polys;
1107
1108   glTranslatef (0, 0, 0.004);                           /* sweep */
1109   glPushMatrix();
1110   glRotatef ((sp->sweep_th * 180 / M_PI), 0, 0, 1);
1111   if (sp->sweep_th >= 0)
1112     glCallList (sp->sweep_list);
1113   mi->polygon_count += sp->sweep_polys;
1114   glPopMatrix();
1115
1116   glTranslatef (0, 0, 0.004);                           /* grid */
1117   glCallList (sp->grid_list);
1118   mi->polygon_count += sp->screen_polys;
1119
1120   glPushMatrix();
1121   mi->polygon_count += draw_angles (mi);                /* angles */
1122   glPopMatrix();
1123
1124   if (sp->desc)                                         /* local subnet */
1125     {
1126       glPushMatrix();
1127       glTranslatef (0, 0, 0.00002);
1128       mi->polygon_count += draw_text (mi, sp->desc, 1.35, M_PI * 0.75, 0, 10);
1129       /* glRotatef (45, 0, 0, 1); */
1130       /* mi->polygon_count += draw_text (mi, sp->desc, 1.2, M_PI/2, 0, 10); */
1131       glPopMatrix();
1132     }
1133
1134   if (sp->error)
1135     sp->state = MSG;
1136
1137   switch (sp->state) {
1138   case MSG:                     /* Frame 1: get "Resolving Hosts" on screen. */
1139     draw_startup_blurb(mi);
1140     sp->state++;
1141     break;
1142   case RESOLVE:                 /* Frame 2: gethostbyaddr may take a while. */
1143     if (! sp->ssd)
1144       init_sensor (mi);
1145     sp->state++;
1146     break;
1147   case RUN:                     /* Frame N: ping away */
1148     sweep (sp);
1149     break;
1150   }
1151
1152   glPopMatrix ();
1153
1154   if (mi->fps_p) do_fps (mi);
1155   glFinish();
1156
1157   glXSwapBuffers(dpy, window);
1158
1159 # if TEST_ASYNC_NETDB
1160   if(sp->query0 && async_name_from_addr_is_done (sp->query0))
1161     {
1162       if (!(random () & 3))
1163         {
1164           fputs ("Aborted hostname lookup (late)\n", stderr);
1165           async_name_from_addr_cancel (sp->query0);
1166         }
1167       else
1168         {
1169           char *hostname = NULL;
1170           int errno_error;
1171           int gai_error = async_name_from_addr_finish (sp->query0, &hostname,
1172                                                        &errno_error);
1173
1174           if(gai_error)
1175             {
1176               fputs ("Couldn't get hostname: ", stderr);
1177               _print_error (gai_error, errno_error, stderr);
1178               putc ('\n', stderr);
1179             }
1180           else
1181             {
1182               fprintf (stderr, "Got a hostname: %s\n", hostname);
1183               free (hostname);
1184             }
1185         }
1186
1187       sp->query0 = NULL;
1188     }
1189
1190   if(sp->query1 && async_addr_from_name_is_done (sp->query1))
1191     {
1192       if (!(random () & 3))
1193         {
1194           fputs ("Aborted address lookup (late)\n", stderr);
1195           async_addr_from_name_cancel (sp->query1);
1196         }
1197       else
1198         {
1199           async_netdb_sockaddr_storage_t addr;
1200           socklen_t addrlen;
1201           int errno_error;
1202           int gai_error = async_addr_from_name_finish (sp->query1, &addr,
1203                                                        &addrlen, &errno_error);
1204
1205           if (gai_error)
1206             {
1207               fputs ("Couldn't get address: ", stderr);
1208               _print_error (gai_error, errno_error, stderr);
1209               putc ('\n', stderr);
1210             }
1211           else
1212             {
1213               fputs ("Got an address: ", stderr);
1214               _print_sockaddr (&addr, addrlen, stderr);
1215               putc ('\n', stderr);
1216             }
1217         }
1218
1219       sp->query1 = NULL;
1220     }
1221
1222   fflush (stderr);
1223 # endif /* TEST_ASYNC_NETDB */
1224 }
1225
1226 ENTRYPOINT void
1227 release_sonar (ModeInfo *mi)
1228 {
1229 #if 0
1230   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
1231   sonar_bogie *b = sp->displayed;
1232   while (b)
1233     {
1234       sonar_bogie *next = b->next;
1235       free_bogie (sp->ssd, b);
1236       b = next;
1237     }
1238   sp->displayed = 0;
1239
1240   b = sp->pending;
1241   while (b)
1242     {
1243       sonar_bogie *next = b->next;
1244       free_bogie (sp->ssd, b);
1245       b = next;
1246     }
1247   sp->pending = 0;
1248
1249   sp->ssd->free_data_cb (sp->ssd, sp->ssd->closure);
1250   free (sp->ssd);
1251   sp->ssd = 0;
1252 #endif
1253 }
1254
1255 XSCREENSAVER_MODULE ("Sonar", sonar)
1256
1257 #endif /* USE_GL */