http://slackware.bholcomb.com/slackware/slackware-11.0/source/xap/xscreensaver/xscree...
[xscreensaver] / hacks / sonar.c
1 /* sonar.c --- Simulate a sonar screen.
2  * Copyright (C) 1998-2006 by Stephen Martin and Jamie Zawinski
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  * This is an implementation of a general purpose reporting tool in the
12  * format of a Sonar display. It is designed such that a sensor is read
13  * on every movement of a sweep arm and the results of that sensor are
14  * displayed on the screen. The location of the display points (targets) on the
15  * screen are determined by the current localtion of the sweep and a distance
16  * value associated with the target. 
17  *
18  * Currently the only two sensors that are implemented are the simulator
19  * (the default) and the ping sensor. The simulator randomly creates a set
20  * of bogies that move around on the scope while the ping sensor can be
21  * used to display hosts on your network.
22  *
23  * The ping code is only compiled in if you define HAVE_ICMP or HAVE_ICMPHDR,
24  * because, unfortunately, different systems have different ways of creating
25  * these sorts of packets.
26  *
27  * In order to use the ping sensor on most systems, this program must be
28  * installed as setuid root, so that it can create an ICMP RAW socket.  Root
29  * privileges are disavowed shortly after startup (just after connecting to
30  * the X server and reading the resource database) so this is *believed* to
31  * be a safe thing to do, but it is usually recommended that you have as few
32  * setuid programs around as possible, on general principles.
33  *
34  * It is not necessary to make it setuid on MacOS systems, because on those
35  * systems, unprivileged programs can ping by using ICMP DGRAM sockets
36  * instead of ICMP RAW.
37  *
38  * It should be easy to extend this code to support other sorts of sensors.
39  * Some ideas:
40  *   - search the output of "netstat" for the list of hosts to ping;
41  *   - plot the contents of /proc/interrupts;
42  *   - plot the process table, by process size, cpu usage, or total time;
43  *   - plot the logged on users by idle time or cpu usage.
44  *
45  * $Revision: 1.57 $
46  *
47  * Version 1.0 April 27, 1998.
48  * - Initial version, by Stephen Martin <smartin@vanderfleet-martin.net>
49  * - Submitted to RedHat Screensaver Contest
50  * 
51  * Version 1.1 November 3, 1998.
52  * - Added simulation mode.
53  * - Added enhancements by Thomas Bahls <thommy@cs.tu-berlin.de>
54  * - Fixed huge memory leak.
55  * - Submitted to xscreensavers
56  * 
57  * Version 1.2
58  * - All ping code is now ifdef-ed by the compile time symbol HAVE_PING;
59  *   use -DHAVE_PING to include it when you compile.
60  * - Sweep now uses gradients.
61  * - Fixed portability problems with icmphdr on some systems.
62  * - removed lowColor option/resource.
63  * - changed copyright notice so that it could be included in the xscreensavers
64  *   collection.
65  *
66  * Version 1.3 November 16, 1998.
67  * - All ping code is now ifdef-ed by the compile time symbol PING use -DPING
68  *   to include it when you compile.
69  * - Sweep now uses gradients.
70  * - Fixed portability problems with icmphdr on some systems.
71  * - removed lowcolour option/resource.
72  * - changed copyright notice so that it could be included in the xscreensavers
73  *   collection.
74  *
75  * Version 1.4 November 18, 1998.
76  * - More ping portability fixes.
77  *
78  * Version 1.5 November 19, 1998.
79  * - Synced up with jwz's changes.
80  * - Now need to define HAVE_PING to compile in the ping stuff.
81  */
82
83
84 /* These are computed by configure now:
85    #define HAVE_ICMP
86    #define HAVE_ICMPHDR
87  */
88
89
90 /* Include Files */
91
92 #include <stdlib.h>
93 #include <stdio.h>
94 #include <math.h>
95 #include <sys/stat.h>
96
97 #include "screenhack.h"
98 #include "colors.h"
99 #include "hsv.h"
100
101 #undef usleep /* conflicts with unistd.h on OSX */
102
103 #if defined(HAVE_ICMP) || defined(HAVE_ICMPHDR)
104 # include <unistd.h>
105 # include <limits.h>
106 # include <signal.h>
107 # include <fcntl.h>
108 # include <sys/types.h>
109 # include <sys/time.h>
110 # include <sys/ipc.h>
111 # include <sys/shm.h>
112 # include <sys/socket.h>
113 # include <netinet/in_systm.h>
114 # include <netinet/in.h>
115 # include <netinet/ip.h>
116 # include <netinet/ip_icmp.h>
117 # include <netinet/udp.h>
118 # include <arpa/inet.h>
119 # include <netdb.h>
120 #endif /* HAVE_ICMP || HAVE_ICMPHDR */
121
122
123 /* Defines */
124
125 #undef MY_MIN
126 #define MY_MIN(a,b) ((a)<(b)?(a - 50):(b - 10))
127
128 #ifndef LINE_MAX
129 # define LINE_MAX 2048
130 #endif
131
132 /* Frigging icmp */
133
134 #if defined(HAVE_ICMP)
135 # define HAVE_PING
136 # define ICMP             icmp
137 # define ICMP_TYPE(p)     (p)->icmp_type
138 # define ICMP_CODE(p)     (p)->icmp_code
139 # define ICMP_CHECKSUM(p) (p)->icmp_cksum
140 # define ICMP_ID(p)       (p)->icmp_id
141 # define ICMP_SEQ(p)      (p)->icmp_seq
142 #elif defined(HAVE_ICMPHDR)
143 # define HAVE_PING
144 # define ICMP             icmphdr
145 # define ICMP_TYPE(p)     (p)->type
146 # define ICMP_CODE(p)     (p)->code
147 # define ICMP_CHECKSUM(p) (p)->checksum
148 # define ICMP_ID(p)       (p)->un.echo.id
149 # define ICMP_SEQ(p)      (p)->un.echo.sequence
150 #else
151 # undef HAVE_PING
152 #endif
153
154
155 #ifdef HAVE_PING
156 # if defined(__DECC) || defined(_IP_VHL)
157    /* This is how you do it on DEC C, and possibly some BSD systems. */
158 #  define IP_HDRLEN(ip)   ((ip)->ip_vhl & 0x0F)
159 # else
160    /* This is how you do it on everything else. */
161 #  define IP_HDRLEN(ip)   ((ip)->ip_hl)
162 # endif
163 #endif /* HAVE_PING */
164
165
166 /* Forward References */
167
168 #ifdef HAVE_PING
169 static u_short checksum(u_short *, int);
170 #endif
171 static long delta(struct timeval *, struct timeval *);
172
173
174 /* Data Structures */
175
176 /*
177  * The Bogie.
178  *
179  * This represents an object that is visible on the scope.
180  */
181
182 typedef struct Bogie {
183     char *name;                 /* The name of the thing being displayed */
184     char *desc;                 /* Beneath the name (e.g., ping time) */
185     int distance;               /* The distance to this thing (0 - 100) */
186     int tick;                   /* The tick that it was found on */
187     int ttl;                    /* The time to live */
188     int age;                    /* How long it's been around */
189     struct Bogie *next;         /* The next one in the list */
190 } Bogie;
191
192 /*
193  * Sonar Information.
194  *
195  * This contains all of the runtime information about the sonar scope.
196  */
197
198 typedef struct ping_target ping_target;
199
200 typedef struct sonar_info sonar_info;
201 struct sonar_info {
202     Display *dpy;               /* The X display */
203     Window win;                 /* The window */
204     GC hi,                      /* The leading edge of the sweep */
205         lo,                     /* The trailing part of the sweep */
206         erase,                  /* Used to erase things */
207         grid,                   /* Used to draw the grid */
208         text;                   /* Used to draw text */
209     Colormap cmap;              /* The colormap */
210     XFontStruct *font;          /* The font to use for the labels */
211     int text_steps;             /* How many steps to fade text. */
212     XColor *text_colors;        /* Pixel values used to fade text */
213     int sweep_degrees;          /* How much of the circle the sweep uses */
214     int sweep_segs;             /* How many gradients in the sweep. */
215     XColor *sweep_colors;        /* The sweep pixel values */
216     int width, height;          /* Window dimensions */
217     int minx, miny, maxx, maxy, /* Bounds of the scope */
218         centrex, centrey, radius; /* Parts of the scope circle */
219     Bogie *visible;             /* List of visible objects */
220     int current;                /* Current position of sweep */
221     int sweepnum;               /* The current id of the sweep */
222     int delay;                  /* how long between each frame of the anim */
223
224     int TTL;                    /* The number of ticks that bogies are visible
225                                    on the screen before they fade away. */
226
227   ping_target *last_ptr;
228
229   Bogie *(*sensor)(sonar_info *, void *); /* The current sensor */
230   void *sensor_info;                      /* Information about the sensor */
231
232 };
233
234 static Bool debug_p = False;
235 static Bool resolve_p = True;
236 static Bool times_p = True;
237
238
239 /*
240  * A list of targets to ping.
241  */
242
243 struct ping_target {
244     char *name;                 /* The name of the target */
245 #ifdef HAVE_PING
246     struct sockaddr address;    /* The address of the target */
247 #endif /* HAVE_PING */
248     struct ping_target *next;   /* The next one in the list */
249 };
250
251
252 #ifdef HAVE_PING
253 /*
254  * Ping Information.
255  *
256  * This contains the information for the ping sensor.
257  */
258
259 typedef struct {
260     int icmpsock;               /* Socket for sending pings */
261     int pid;                    /* Our process ID */
262     int seq;                    /* Packet sequence number */
263     int timeout;                /* Timeout value for pings */
264     ping_target *targets;       /* List of targets to ping */
265     int numtargets;             /* The number of targets to ping */
266 } ping_info;
267
268 /* Flag to indicate that the timer has expired on us */
269
270 static int timer_expired;
271
272 #endif /* HAVE_PING */
273
274 /*
275  * A list of targets for the simulator
276  */
277
278 typedef struct sim_target {
279     char *name;                 /* The name of the target */
280     int nexttick;               /* The next tick that this will be seen */
281     int nextdist;               /* The distance on that tick */
282     int movedonsweep;           /* The number of the sweep this last moved */
283 } sim_target;
284
285 /*
286  * Simulator Information.
287  *
288  * This contains the information for the simulator mode.
289  */
290
291 typedef struct {
292     sim_target *teamA;          /* The bogies for the A team */
293     int numA;                   /* The number of bogies in team A */
294     char *teamAID;              /* The identifier for bogies in team A */
295     sim_target *teamB;          /* The bogies for the B team */
296     int numB;                   /* The number of bogies in team B */
297     char *teamBID;              /* The identifier for bogies in team B */
298 } sim_info;
299
300
301
302 /*
303  * Create a new Bogie and set some initial values.
304  *
305  * Args:
306  *    name     - The name of the bogie.
307  *    distance - The distance value.
308  *    tick     - The tick value.
309  *    ttl      - The time to live value.
310  *
311  * Returns:
312  *    The newly allocated bogie or null if a memory problem occured.
313  */
314
315 static Bogie *
316 newBogie(char *name, int distance, int tick, int ttl) 
317 {
318
319     /* Local Variables */
320
321     Bogie *new;
322
323     distance *= 1000;
324     /* Allocate a bogie and initialize it */
325
326     if ((new = (Bogie *) calloc(1, sizeof(Bogie))) == NULL) {
327         fprintf(stderr, "%s: Out of Memory\n", progname);
328         return NULL;
329     }
330     new->name = name;
331     new->distance = distance;
332     new->tick = tick;
333     new->ttl = ttl;
334     new->age = 0;
335     new->next = (Bogie *) 0;
336     return new;
337 }
338
339 /*
340  * Free a Bogie.
341  *
342  * Args:
343  *    b - The bogie to free.
344  */
345
346
347 static void
348 freeBogie(Bogie *b) 
349 {
350     if (b->name != (char *) 0)
351         free(b->name);
352     free(b);
353 }
354
355 /*
356  * Find a bogie by name in a list.
357  *
358  * This does a simple linear search of the list for a given name.
359  *
360  * Args:
361  *    bl   - The Bogie list to search.
362  *    name - The name to look for.
363  *
364  * Returns:
365  *    The requested Bogie or null if it wasn't found.
366  */
367
368 static Bogie *
369 findNode(Bogie *bl, char *name) 
370 {
371
372     /* Local Variables */
373
374     Bogie *p;
375
376     /* Abort if the list is empty or no name is given */
377
378     if ((name == NULL) || (bl == NULL))
379         return NULL;
380
381     /* Search the list for the desired name */
382
383     p = bl;
384     while (p != NULL) {
385         if (strcmp(p->name, name) == 0)
386             return p;
387         p = p->next;
388     }
389
390     /* Not found */
391
392     return NULL;
393 }
394
395 #ifdef HAVE_PING
396
397 /* Packs an IP address quad into bigendian network order. */
398 static unsigned long
399 pack_addr (unsigned int a, unsigned int b, unsigned int c, unsigned int d)
400 {
401   unsigned long i = (((a & 255) << 24) |
402                      ((b & 255) << 16) |
403                      ((c & 255) <<  8) |
404                      ((d & 255)      ));
405   return htonl (i);
406 }
407
408 /* Unpacks an IP address quad from bigendian network order. */
409 static void
410 unpack_addr (unsigned long addr,
411              unsigned int *a, unsigned int *b,
412              unsigned int *c, unsigned int *d)
413 {
414   addr = ntohl (addr);
415   *a = (addr >> 24) & 255;
416   *b = (addr >> 16) & 255;
417   *c = (addr >>  8) & 255;
418   *d = (addr      ) & 255;
419 }
420
421
422 /*
423  * Lookup the address for a ping target;
424  *
425  * Args:
426  *    target - The ping_target fill in the address for.
427  *
428  * Returns:
429  *    1 if the host was successfully resolved, 0 otherwise.
430  */
431
432 static int
433 lookupHost(ping_target *target) 
434 {
435   struct hostent *hent;
436   struct sockaddr_in *iaddr;
437
438   unsigned int ip[4];
439   char c;
440
441   iaddr = (struct sockaddr_in *) &(target->address);
442   iaddr->sin_family = AF_INET;
443
444   if (4 == sscanf (target->name, " %u.%u.%u.%u %c",
445                    &ip[0], &ip[1], &ip[2], &ip[3], &c))
446     {
447       /* It's an IP address.
448        */
449       if (ip[3] == 0)
450         {
451           if (debug_p > 1)
452             fprintf (stderr, "%s:   ignoring bogus IP %s\n",
453                      progname, target->name);
454           return 0;
455         }
456
457       iaddr->sin_addr.s_addr = pack_addr (ip[0], ip[1], ip[2], ip[3]);
458       if (resolve_p)
459         hent = gethostbyaddr ((const char *) &iaddr->sin_addr.s_addr,
460                               sizeof(iaddr->sin_addr.s_addr),
461                               AF_INET);
462       else
463         hent = 0;
464
465       if (debug_p > 1)
466         fprintf (stderr, "%s:   %s => %s\n",
467                  progname, target->name,
468                  ((hent && hent->h_name && *hent->h_name)
469                   ? hent->h_name : "<unknown>"));
470
471       if (hent && hent->h_name && *hent->h_name)
472         target->name = strdup (hent->h_name);
473     }
474   else
475     {
476       /* It's a host name.
477        */
478
479
480       /* don't waste time being confused by non-hostname tokens
481          in .ssh/known_hosts */
482       if (!strcmp (target->name, "ssh-rsa") ||
483           !strcmp (target->name, "ssh-dsa") ||
484           !strcmp (target->name, "ssh-dss") ||
485           strlen (target->name) >= 80)
486         return 0;
487
488       hent = gethostbyname (target->name);
489       if (!hent)
490         {
491           if (debug_p)
492             fprintf (stderr, "%s: could not resolve host:  %s\n",
493                      progname, target->name);
494           return 0;
495         }
496
497       memcpy (&iaddr->sin_addr, hent->h_addr_list[0],
498               sizeof(iaddr->sin_addr));
499
500       if (debug_p > 1)
501         {
502           unsigned int a, b, c, d;
503           unpack_addr (iaddr->sin_addr.s_addr, &a, &b, &c, &d);
504           fprintf (stderr, "%s:   %s => %d.%d.%d.%d\n",
505                    progname, target->name, a, b, c, d);
506         }
507     }
508   return 1;
509 }
510
511
512 static void
513 print_host (FILE *out, unsigned long ip, const char *name)
514 {
515   char ips[50];
516   unsigned int a, b, c, d;
517   unpack_addr (ip, &a, &b, &c, &d);             /* ip is in network order */
518   sprintf (ips, "%u.%u.%u.%u", a, b, c, d);
519   if (!name || !*name) name = "<unknown>";
520   fprintf (out, "%-16s %s\n", ips, name);
521 }
522
523
524 /*
525  * Create a target for a host.
526  *
527  * Args:
528  *    name - The name of the host.
529  *
530  * Returns:
531  *    A newly allocated target or null if the host could not be resolved.
532  */
533
534 static ping_target *
535 newHost(char *name) 
536 {
537
538     /* Local Variables */
539
540     ping_target *target = NULL;
541
542     /* Create the target */
543
544     if ((target = calloc(1, sizeof(ping_target))) == NULL) {
545         fprintf(stderr, "%s: Out of Memory\n", progname);
546         goto target_init_error;
547     }
548     if ((target->name = strdup(name)) == NULL) {
549         fprintf(stderr, "%s: Out of Memory\n", progname);
550         goto target_init_error;
551     }
552
553     /* Lookup the host */
554
555     if (! lookupHost(target))
556         goto target_init_error;
557
558     /* Don't ever use loopback (127.0.0.x) hosts */
559     {
560       struct sockaddr_in *iaddr = (struct sockaddr_in *) &(target->address);
561       unsigned long ip = iaddr->sin_addr.s_addr;
562
563       if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L)  /* 127.0.0.x */
564         {
565           if (debug_p)
566             fprintf (stderr, "%s:   ignoring loopback host %s\n",
567                      progname, target->name);
568           goto target_init_error;
569         }
570     }
571
572     /* Don't ever use broadcast (255.x.x.x) hosts */
573     {
574       struct sockaddr_in *iaddr = (struct sockaddr_in *) &(target->address);
575       unsigned long ip = iaddr->sin_addr.s_addr;
576       if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L)  /* 255.x.x.x */
577         {
578           if (debug_p)
579             fprintf (stderr, "%s:   ignoring broadcast host %s\n",
580                      progname, target->name);
581           goto target_init_error;
582         }
583     }
584
585     /* Done */
586
587     if (debug_p)
588       {
589         struct sockaddr_in *iaddr = (struct sockaddr_in *) &(target->address);
590         unsigned long ip = iaddr->sin_addr.s_addr;
591         fprintf (stderr, "%s:   added ", progname);
592         print_host (stderr, ip, target->name);
593       }
594
595     return target;
596
597     /* Handle errors here */
598
599 target_init_error:
600     if (target != NULL)
601         free(target);
602     return NULL;
603 }
604
605 /*
606  * Generate a list of ping targets from the entries in a file.
607  *
608  * Args:
609  *    fname - The name of the file. This file is expected to be in the same
610  *            format as /etc/hosts.
611  *
612  * Returns:
613  *    A list of targets to ping or null if an error occured.
614  */
615
616 static ping_target *
617 readPingHostsFile(char *fname) 
618 {
619     /* Local Variables */
620
621     FILE *fp;
622     char buf[LINE_MAX];
623     char *p;
624     ping_target *list = NULL;
625     char *addr, *name;
626     ping_target *new;
627
628     /* Make sure we in fact have a file to process */
629
630     if ((fname == NULL) || (fname[0] == '\0')) {
631         fprintf(stderr, "%s: invalid ping host file name\n", progname);
632         return NULL;
633     }
634
635     /* Kludge: on OSX, variables have not been expanded in the command
636        line arguments, so as a special case, allow the string to begin
637        with literal "$HOME/" or "~/".
638
639        This is so that the "Known Hosts" menu item in sonar.xml works.
640      */
641     if (!strncmp(fname, "~/", 2) || !strncmp(fname, "$HOME/", 6)) {
642       char *s = strchr (fname, '/');
643       strcpy (buf, getenv("HOME"));
644       strcat (buf, s);
645       fname = buf;
646     }
647
648     /* Open the file */
649
650     if ((fp = fopen(fname, "r")) == NULL) {
651         char msg[1024];
652         sprintf(msg, "%s: unable to open host file %s", progname, fname);
653 #ifdef HAVE_COCOA
654         if (debug_p)  /* on OSX don't syslog this */
655 #endif
656           perror(msg);
657         return NULL;
658     }
659
660     if (debug_p)
661       fprintf (stderr, "%s:  reading file %s\n", progname, fname);
662
663     /* Read the file line by line */
664
665     while ((p = fgets(buf, LINE_MAX, fp)) != NULL) {
666
667         /*
668          * Parse the line skipping those that start with '#'.
669          * The rest of the lines in the file should be in the same
670          * format as a /etc/hosts file. We are only concerned with
671          * the first two field, the IP address and the name
672          */
673
674         while ((*p == ' ') || (*p == '\t'))
675             p++;
676         if (*p == '#')
677             continue;
678
679         /* Get the name and address */
680
681         name = addr = NULL;
682         if ((addr = strtok(buf, " ,;\t\n")) != NULL)
683             name = strtok(NULL, " ,;\t\n");
684         else
685             continue;
686
687         /* Check to see if the addr looks like an addr.  If not, assume
688            the addr is a name and there is no addr.  This way, we can
689            handle files whose lines have "xx.xx.xx.xx hostname" as their
690            first two tokens, and also files that have a hostname as their
691            first token (like .ssh/known_hosts and .rhosts.)
692          */
693         {
694           int i; char c;
695           if (4 != sscanf(addr, "%d.%d.%d.%d%c", &i, &i, &i, &i, &c))
696             {
697               name = addr;
698               addr = NULL;
699             }
700         }
701
702         /* If the name is all digits, it's not a name. */
703         if (name)
704           {
705             const char *s;
706             for (s = name; *s; s++)
707               if (*s < '0' || *s > '9')
708                 break;
709             if (! *s)
710               {
711                 if (debug_p > 1)
712                   fprintf (stderr, "%s:  skipping bogus name \"%s\" (%s)\n",
713                            progname, name, addr);
714                 name = NULL;
715               }
716           }
717
718         /* Create a new target using first the name then the address */
719
720         new = NULL;
721         if (name != NULL)
722             new = newHost(name);
723         if (new == NULL && addr != NULL)
724             new = newHost(addr);
725
726         /* Add it to the list if we got one */
727
728         if (new != NULL) {
729             new->next = list;
730             list = new;
731         }
732     }
733
734     /* Close the file and return the list */
735
736     fclose(fp);
737     return list;
738 }
739
740
741 static ping_target *
742 delete_duplicate_hosts (ping_target *list)
743 {
744   ping_target *head = list;
745   ping_target *rest;
746
747   for (rest = head; rest; rest = rest->next)
748     {
749       struct sockaddr_in *i1 = (struct sockaddr_in *) &(rest->address);
750       unsigned long ip1 = i1->sin_addr.s_addr;
751
752       ping_target *rest2;
753       for (rest2 = rest; rest2; rest2 = rest2->next)
754         {
755           if (rest2 && rest2->next)
756             {
757               struct sockaddr_in *i2 = (struct sockaddr_in *)
758                 &(rest2->next->address);
759               unsigned long ip2 = i2->sin_addr.s_addr;
760
761               if (ip1 == ip2)
762                 {
763                   if (debug_p)
764                     {
765                       fprintf (stderr, "%s: deleted duplicate: ", progname);
766                       print_host (stderr, ip2, rest2->next->name);
767                     }
768                   rest2->next = rest2->next->next;
769                 }
770             }
771         }
772     }
773
774   return head;
775 }
776
777
778
779
780 /*
781  * Generate a list ping targets consisting of all of the entries on
782  * the same subnet.  'base' ip is in network order; 0 means localhost.
783  *
784  * Returns:
785  *    A list of all of the hosts on this net.
786  */
787
788 static ping_target *
789 subnetHostsList(unsigned long n_base, int subnet_width)
790 {
791     unsigned long h_mask;   /* host order */
792     unsigned long h_base;   /* host order */
793
794     /* Local Variables */
795
796     char hostname[BUFSIZ];
797     char address[BUFSIZ];
798     struct hostent *hent;
799     char *p;
800     int i;
801     ping_target *new;
802     ping_target *list = NULL;
803
804     if (subnet_width < 24)
805       {
806         fprintf (stderr,
807    "%s: pinging %lu hosts is a bad idea; please use a subnet mask of 24 bits\n"
808                  "       or more (255 hosts max.)\n",
809                  progname, (unsigned long) (1L << (32 - subnet_width)) - 1);
810         exit (1);
811       }
812     else if (subnet_width > 30)
813       {
814         fprintf (stderr, "%s: a subnet of %d bits doesn't make sense:"
815                  " try \"subnet/24\" or \"subnet/29\".\n",
816                  progname, subnet_width);
817         exit (1);
818       }
819
820
821     if (debug_p)
822       fprintf (stderr, "%s:   adding %d-bit subnet\n", progname, subnet_width);
823
824     /* Get our hostname */
825
826     if (gethostname(hostname, BUFSIZ)) {
827         fprintf(stderr, "%s: unable to get local hostname\n", progname);
828         return NULL;
829     }
830
831     /* Get our IP address and convert it to a string */
832
833     if ((hent = gethostbyname(hostname)) == NULL) {
834         fprintf(stderr, "%s: unable to lookup our IP address\n", progname);
835         return NULL;
836     }
837     strcpy(address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
838
839     /* Construct targets for all addresses in this subnet */
840
841     h_mask = 0;
842     for (i = 0; i < subnet_width; i++)
843       h_mask |= (1L << (31-i));
844
845     /* If no base IP specified, assume localhost. */
846     if (n_base == 0)
847       n_base = pack_addr (hent->h_addr_list[0][0],
848                           hent->h_addr_list[0][1],
849                           hent->h_addr_list[0][2],
850                           hent->h_addr_list[0][3]);
851     h_base = ntohl (n_base);
852
853     if (h_base == 0x7F000001L)   /* 127.0.0.1 in host order */
854       {
855         unsigned int a, b, c, d;
856         unpack_addr (n_base, &a, &b, &c, &d);
857         fprintf (stderr,
858                  "%s: unable to determine local subnet address: \"%s\"\n"
859                  "       resolves to loopback address %u.%u.%u.%u.\n",
860                  progname, hostname, a, b, c, d);
861         return NULL;
862       }
863
864     for (i = 255; i >= 0; i--) {
865         unsigned int a, b, c, d;
866         int ip = (h_base & 0xFFFFFF00L) | i;     /* host order */
867       
868         if ((ip & h_mask) != (h_base & h_mask))  /* not in mask range at all */
869           continue;
870         if ((ip & ~h_mask) == 0)                 /* broadcast address */
871           continue;
872         if ((ip & ~h_mask) == ~h_mask)           /* broadcast address */
873           continue;
874
875         unpack_addr (htonl (ip), &a, &b, &c, &d);
876         sprintf (address, "%u.%u.%u.%u", a, b, c, d);
877
878         if (debug_p > 1)
879           {
880             unsigned int aa, ab, ac, ad;
881             unsigned int ma, mb, mc, md;
882             unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
883             unpack_addr (htonl (h_mask),          &ma, &mb, &mc, &md);
884             fprintf (stderr,
885                      "%s:  subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
886                      progname, address,
887                      aa, ab, ac, ad,
888                      ma, mb, mc, md,
889                      subnet_width);
890           }
891
892         p = address + strlen(address) + 1;
893         sprintf(p, "%d", i);
894
895         new = newHost(address);
896         if (new != NULL) {
897             new->next = list;
898             list = new;
899         }
900     }
901
902     /* Done */
903
904     return list;
905 }
906
907 /*
908  * Initialize the ping sensor.
909  *
910  * Returns:
911  *    A newly allocated ping_info structure or null if an error occured.
912  */
913
914 static ping_target *parse_mode (sonar_info *, Bool ping_works_p);
915
916 /* yes, there is only one, even when multiple savers are running in the
917    same address space - since we can only open this socket before dropping
918    privs.
919  */
920 static int global_icmpsock = 0;
921
922 static ping_info *
923 init_ping(sonar_info *si)
924 {
925
926   Bool socket_initted_p = False;
927   Bool socket_raw_p = False;
928
929     /* Local Variables */
930
931     ping_info *pi = NULL;               /* The new ping_info struct */
932     ping_target *pt;                    /* Used to count the targets */
933
934     /* Create the ping info structure */
935
936     if ((pi = (ping_info *) calloc(1, sizeof(ping_info))) == NULL) {
937         fprintf(stderr, "%s: Out of memory\n", progname);
938         goto ping_init_error;
939     }
940
941     /* Create the ICMP socket.  Do this before dropping privs.
942
943        Raw sockets can only be opened by root (or setuid root), so we
944        only try to do this when the effective uid is 0.
945
946        We used to just always try, and notice the failure.  But apparently
947        that causes "SELinux" to log spurious warnings when running with the
948        "strict" policy.  So to avoid that, we just don't try unless we
949        know it will work.
950
951        On MacOS X, we can avoid the whole problem by using a
952        non-privileged datagram instead of a raw socket.
953      */
954     if (global_icmpsock) {
955       pi->icmpsock = global_icmpsock;
956       socket_initted_p = True;
957       if (debug_p)
958         fprintf (stderr, "%s: re-using icmp socket\n", progname);
959
960     } else if ((pi->icmpsock = 
961                 socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0) {
962       socket_initted_p = True;
963
964     } else if (geteuid() == 0 &&
965                (pi->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0) {
966         socket_initted_p = True;
967         socket_raw_p = True;
968     }
969
970     if (socket_initted_p) {
971       global_icmpsock = pi->icmpsock;
972       socket_initted_p = True;
973       if (debug_p)
974         fprintf (stderr, "%s: opened %s icmp socket\n", progname,
975                  (socket_raw_p ? "raw" : "dgram"));
976     } else if (debug_p)
977       fprintf (stderr, "%s: unable to open icmp socket\n", progname);
978
979     /* Disavow privs */
980
981     setuid(getuid());
982
983
984     pi->pid = getpid() & 0xFFFF;
985     pi->seq = 0;
986     pi->timeout = get_integer_resource(si->dpy, "pingTimeout", "PingTimeout");
987
988     /* Generate a list of targets */
989
990     pi->targets = parse_mode (si, socket_initted_p);
991     pi->targets = delete_duplicate_hosts (pi->targets);
992
993
994     if (debug_p)
995       {
996         ping_target *t;
997         fprintf (stderr, "%s: Target list:\n", progname);
998         for (t = pi->targets; t; t = t->next)
999           {
1000             struct sockaddr_in *iaddr = (struct sockaddr_in *) &(t->address);
1001             unsigned long ip = iaddr->sin_addr.s_addr;
1002             fprintf (stderr, "%s:   ", progname);
1003             print_host (stderr, ip, t->name);
1004           }
1005       }
1006
1007     /* Make sure there is something to ping */
1008
1009     if (pi->targets == NULL) {
1010       goto ping_init_error;
1011     }
1012
1013     /* Count the targets */
1014
1015     pt = pi->targets;
1016     pi->numtargets = 0;
1017     while (pt != NULL) {
1018         pi->numtargets++;
1019         pt = pt->next;
1020     }
1021
1022     /* Done */
1023
1024     return pi;
1025
1026     /* Handle initialization errors here */
1027
1028 ping_init_error:
1029     if (pi != NULL)
1030         free(pi);
1031     return NULL;
1032 }
1033
1034
1035 /*
1036  * Ping a host.
1037  *
1038  * Args:
1039  *    pi   - The ping information strcuture.
1040  *    host - The name or IP address of the host to ping (in ascii).
1041  */
1042
1043 static void
1044 sendping(ping_info *pi, ping_target *pt) 
1045 {
1046
1047     /* Local Variables */
1048
1049     u_char *packet;
1050     struct ICMP *icmph;
1051     int result;
1052
1053     /*
1054      * Note, we will send the character name of the host that we are
1055      * pinging in the packet so that we don't have to keep track of the
1056      * name or do an address lookup when it comes back.
1057      */
1058
1059     int pcktsiz = sizeof(struct ICMP) + sizeof(struct timeval) +
1060         strlen(pt->name) + 1;
1061
1062     /* Create the ICMP packet */
1063
1064     if ((packet = (u_char *) malloc(pcktsiz)) == (void *) 0)
1065         return;  /* Out of memory */
1066     icmph = (struct ICMP *) packet;
1067     ICMP_TYPE(icmph) = ICMP_ECHO;
1068     ICMP_CODE(icmph) = 0;
1069     ICMP_CHECKSUM(icmph) = 0;
1070     ICMP_ID(icmph) = pi->pid;
1071     ICMP_SEQ(icmph) = pi->seq++;
1072 # ifdef GETTIMEOFDAY_TWO_ARGS
1073     gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
1074                  (struct timezone *) 0);
1075 # else
1076     gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
1077 # endif
1078
1079     strcpy((char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)],
1080            pt->name);
1081     ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
1082
1083     /* Send it */
1084
1085     if ((result = sendto(pi->icmpsock, packet, pcktsiz, 0, 
1086                          &pt->address, sizeof(pt->address))) !=  pcktsiz) {
1087 #if 0
1088         char errbuf[BUFSIZ];
1089         sprintf(errbuf, "%s: error sending ping to %s", progname, pt->name);
1090         perror(errbuf);
1091 #endif
1092     }
1093 }
1094
1095 /*
1096  * Catch a signal and do nothing.
1097  *
1098  * Args:
1099  *    sig - The signal that was caught.
1100  */
1101
1102 static void
1103 sigcatcher(int sig)
1104 {
1105     timer_expired = 1;
1106 }
1107
1108 /*
1109  * Compute the checksum on a ping packet.
1110  *
1111  * Args:
1112  *    packet - A pointer to the packet to compute the checksum for.
1113  *    size   - The size of the packet.
1114  *
1115  * Returns:
1116  *    The computed checksum
1117  *    
1118  */
1119
1120 static u_short
1121 checksum(u_short *packet, int size) 
1122 {
1123
1124     /* Local Variables */
1125
1126     register int nleft = size;
1127     register u_short *w = packet;
1128     register int sum = 0;
1129     u_short answer = 0;
1130
1131     /*
1132      * Our algorithm is simple, using a 32 bit accumulator (sum), we add
1133      * sequential 16 bit words to it, and at the end, fold back all the
1134      * carry bits from the top 16 bits into the lower 16 bits.
1135      */
1136
1137     while (nleft > 1)  {
1138         sum += *w++;
1139         nleft -= 2;
1140     }
1141
1142     /* mop up an odd byte, if necessary */
1143
1144     if (nleft == 1) {
1145         *(u_char *)(&answer) = *(u_char *)w ;
1146         *(1 + (u_char *)(&answer)) = 0;
1147         sum += answer;
1148     }
1149
1150     /* add back carry outs from top 16 bits to low 16 bits */
1151
1152     sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
1153     sum += (sum >> 16);                     /* add carry */
1154     answer = ~sum;                          /* truncate to 16 bits */
1155
1156     /* Done */
1157
1158     return(answer);
1159 }
1160
1161 /*
1162  * Look for ping replies.
1163  *
1164  * Retrieve all outstanding ping replies.
1165  *
1166  * Args:
1167  *    si - Information about the sonar.
1168  *    pi - Ping information.
1169  *    ttl - The time each bogie is to live on the screen
1170  *
1171  * Returns:
1172  *    A Bogie list of all the machines that replied.
1173  */
1174
1175 static Bogie *
1176 getping(sonar_info *si, ping_info *pi) 
1177 {
1178
1179     /* Local Variables */
1180
1181     struct sockaddr from;
1182     unsigned int fromlen;  /* Posix says socklen_t, but that's not portable */
1183     int result;
1184     u_char packet[1024];
1185     struct timeval now;
1186     struct timeval *then;
1187     struct ip *ip;
1188     int iphdrlen;
1189     struct ICMP *icmph;
1190     Bogie *bl = NULL;
1191     Bogie *new;
1192     char *name;
1193     struct sigaction sa;
1194     struct itimerval it;
1195     fd_set rfds;
1196     struct timeval tv;
1197
1198     /* Set up a signal to interupt our wait for a packet */
1199
1200     sigemptyset(&sa.sa_mask);
1201     sa.sa_flags = 0;
1202     sa.sa_handler = sigcatcher;
1203     if (sigaction(SIGALRM, &sa, 0) == -1) {
1204         char msg[1024];
1205         sprintf(msg, "%s: unable to trap SIGALRM", progname);
1206         perror(msg);
1207         exit(1);
1208     }
1209
1210     /* Set up a timer to interupt us if we don't get a packet */
1211
1212     it.it_interval.tv_sec = 0;
1213     it.it_interval.tv_usec = 0;
1214     it.it_value.tv_sec = 0;
1215     it.it_value.tv_usec = pi->timeout;
1216     timer_expired = 0;
1217     setitimer(ITIMER_REAL, &it, NULL);
1218
1219     /* Wait for a result packet */
1220
1221     fromlen = sizeof(from);
1222     while (! timer_expired) {
1223       tv.tv_usec=pi->timeout;
1224       tv.tv_sec=0;
1225 #if 0
1226       /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1227       FD_ZERO(&rfds);
1228 #else
1229       memset (&rfds, 0, sizeof(rfds));
1230 #endif
1231       FD_SET(pi->icmpsock,&rfds);
1232       /* only wait a little while, in case we raced with the timer expiration.
1233          From Valentijn Sessink <valentyn@openoffice.nl> */
1234       if (select(pi->icmpsock+1, &rfds, NULL, NULL, &tv) >0) {
1235         result = recvfrom(pi->icmpsock, packet, sizeof(packet),
1236                       0, &from, &fromlen);
1237
1238         /* Check the packet */
1239
1240 # ifdef GETTIMEOFDAY_TWO_ARGS
1241         gettimeofday(&now, (struct timezone *) 0);
1242 # else
1243         gettimeofday(&now);
1244 # endif
1245         ip = (struct ip *) packet;
1246         iphdrlen = IP_HDRLEN(ip) << 2;
1247         icmph = (struct ICMP *) &packet[iphdrlen];
1248         then  = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
1249
1250
1251         /* Was the packet a reply?? */
1252
1253         if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY) {
1254             /* Ignore anything but ICMP Replies */
1255             continue; /* Nope */
1256         }
1257
1258         /* Was it for us? */
1259
1260         if (ICMP_ID(icmph) != pi->pid) {
1261             /* Ignore packets not set from us */
1262             continue; /* Nope */
1263         }
1264
1265         /* Copy the name of the bogie */
1266
1267         if ((name =
1268              strdup((char *) &packet[iphdrlen + 
1269                                     + sizeof(struct ICMP)
1270                                     + sizeof(struct timeval)])) == NULL) {
1271             fprintf(stderr, "%s: Out of memory\n", progname);
1272             return bl;
1273         }
1274
1275 # if 0  /* Don't need to do this -- the host names are already as
1276            resolved as they're going to get.  (We stored the resolved
1277            name in the outgoing ping packet, so that same string just
1278            came back to us.)
1279          */
1280
1281         /* If the name is an IP addr, try to resolve it. */
1282         {
1283           int iip[4];
1284           char c;
1285           if (4 == sscanf(name, " %d.%d.%d.%d %c",
1286                           &iip[0], &iip[1], &iip[2], &iip[3], &c))
1287             {
1288               struct sockaddr_in iaddr;
1289               struct hostent *h;
1290               iaddr.sin_addr.s_addr = pack_addr (iip[0],iip[1],iip[2],iip[3]);
1291               if (resolve_p)
1292                 h = gethostbyaddr ((const char *) &iaddr.sin_addr.s_addr,
1293                                    sizeof(iaddr.sin_addr.s_addr),
1294                                    AF_INET);
1295               else
1296                 h = 0;
1297
1298               if (h && h->h_name && *h->h_name)
1299                 {
1300                   free (name);
1301                   name = strdup (h->h_name);
1302                 }
1303             }
1304         }
1305 # endif /* 0 */
1306
1307         /* Create the new Bogie and add it to the list we are building */
1308
1309         if ((new = newBogie(name, 0, si->current, si->TTL)) == NULL)
1310             return bl;
1311         new->next = bl;
1312         bl = new;
1313
1314         {
1315           float msec = delta(then, &now) / 1000.0;
1316
1317           if (times_p)
1318             {
1319               if (new->desc) free (new->desc);
1320               new->desc = (char *) malloc (30);
1321               if      (msec > 99) sprintf (new->desc, "    %.0f ms   ", msec);
1322               else if (msec >  9) sprintf (new->desc, "    %.1f ms   ", msec);
1323               else if (msec >  1) sprintf (new->desc, "    %.2f ms   ", msec);
1324               else                sprintf (new->desc, "    %.3f ms   ", msec);
1325             }
1326
1327           if (debug_p && times_p)  /* print ping-like stuff to stdout */
1328             {
1329               struct sockaddr_in *iaddr = (struct sockaddr_in *) &from;
1330               unsigned int a, b, c, d;
1331               char ipstr[20];
1332               char *s = strdup (new->desc);
1333               char *s2 = s, *s3 = s;
1334               while (*s2 == ' ') s2++;
1335               s3 = strchr (s2, ' ');
1336               if (s3) *s3 = 0;
1337
1338               unpack_addr (iaddr->sin_addr.s_addr, &a, &b, &c, &d);
1339               sprintf (ipstr, "%d.%d.%d.%d", a, b, c, d);
1340
1341               fprintf (stdout,
1342                        "%3d bytes from %28s: "
1343                        "icmp_seq=%-4d ttl=%d time=%s ms\n",
1344                        result,
1345                        name,
1346                        /*ipstr,*/
1347                        ICMP_SEQ(icmph), si->TTL, s2);
1348               free (s);
1349             }
1350
1351           /* Don't put anyone *too* close to the center of the screen. */
1352           msec += 0.6;
1353
1354           new->distance = msec * 10;
1355         }
1356       }
1357     }
1358
1359     /* Done */
1360
1361     return bl;
1362 }
1363
1364 /*
1365  * Ping hosts.
1366  *
1367  * Args:
1368  *    si - Sonar Information.
1369  *    pi - Ping Information.
1370  *
1371  * Returns:
1372  *    A list of hosts that replied to pings or null if there were none.
1373  */
1374
1375 static Bogie *
1376 ping(sonar_info *si, void *vpi) 
1377 {
1378
1379     /*
1380      * This tries to distribute the targets evely around the field of the
1381      * sonar.
1382      */
1383
1384     ping_info *pi = (ping_info *) vpi;
1385
1386     int tick = si->current * -1 + 1;
1387     if ((si->last_ptr == NULL) && (tick == 1))
1388         si->last_ptr = pi->targets;
1389
1390     if (pi->numtargets <= 90) {
1391         int xdrant = 90 / pi->numtargets;
1392         if ((tick % xdrant) == 0) {
1393             if (si->last_ptr != (ping_target *) 0) {
1394                 sendping(pi, si->last_ptr);
1395                 si->last_ptr = si->last_ptr->next;
1396             }
1397         }
1398
1399     } else if (pi->numtargets > 90) {
1400         if (si->last_ptr != (ping_target *) 0) {
1401             sendping(pi, si->last_ptr);
1402             si->last_ptr = si->last_ptr->next;
1403         }
1404     }
1405
1406     /* Get the results */
1407
1408     return getping(si, pi);
1409 }
1410
1411 #endif /* HAVE_PING */
1412
1413 /*
1414  * Calculate the difference between two timevals in microseconds.
1415  *
1416  * Args:
1417  *    then - The older timeval.
1418  *    now  - The newer timeval.
1419  *
1420  * Returns:
1421  *   The difference between the two in microseconds.
1422  */
1423
1424 static long
1425 delta(struct timeval *then, struct timeval *now) 
1426 {
1427     return (((now->tv_sec - then->tv_sec) * 1000000) + 
1428                (now->tv_usec - then->tv_usec));  
1429 }
1430
1431 /*
1432  * Initialize the simulation mode.
1433  */
1434
1435 #define BELLRAND(x) (((random()%(x)) + (random()%(x)) + (random()%(x)))/3)
1436
1437 static sim_info *
1438 init_sim(Display *dpy) 
1439 {
1440     /* Local Variables */
1441
1442     sim_info *si;
1443     int i;
1444
1445     int maxdist = 20;  /* larger than this is off the (log) display */
1446
1447     /* Create the simulation info structure */
1448
1449     if ((si = (sim_info *) calloc(1, sizeof(sim_info))) == NULL) {
1450         fprintf(stderr, "%s: Out of memory\n", progname);
1451         return NULL;
1452     }
1453
1454     /* Team A */
1455
1456     si->numA = get_integer_resource(dpy, "teamACount", "TeamACount");
1457     if ((si->teamA = (sim_target *)calloc(si->numA, sizeof(sim_target)))
1458         == NULL) {
1459         free(si);
1460         fprintf(stderr, "%s: Out of Memory\n", progname);
1461         return NULL;
1462     }
1463     si->teamAID = get_string_resource(dpy, "teamAName", "TeamAName");
1464     for (i = 0; i < si->numA; i++) {
1465         if ((si->teamA[i].name = (char *) malloc(strlen(si->teamAID) + 4))
1466             == NULL) {
1467             free(si);
1468             fprintf(stderr, "%s: Out of Memory\n", progname);
1469             return NULL;
1470         }
1471         sprintf(si->teamA[i].name, "%s%03d", si->teamAID, i+1);
1472         si->teamA[i].nexttick = random() % 90;
1473         si->teamA[i].nextdist = BELLRAND(maxdist);
1474         si->teamA[i].movedonsweep = -1;
1475     }
1476
1477     /* Team B */
1478
1479     si->numB = get_integer_resource(dpy, "teamBCount", "TeamBCount");
1480     if ((si->teamB = (sim_target *)calloc(si->numB, sizeof(sim_target)))
1481         == NULL) {
1482         free(si);
1483         fprintf(stderr, "%s: Out of Memory\n", progname);
1484         return NULL;
1485     }
1486     si->teamBID = get_string_resource(dpy, "teamBName", "TeamBName");
1487     for (i = 0; i < si->numB; i++) {
1488         if ((si->teamB[i].name = (char *) malloc(strlen(si->teamBID) + 4))
1489             == NULL) {
1490             free(si);
1491             fprintf(stderr, "%s: Out of Memory\n", progname);
1492             return NULL;
1493         }
1494         sprintf(si->teamB[i].name, "%s%03d", si->teamBID, i+1);
1495         si->teamB[i].nexttick = random() % 90;
1496         si->teamB[i].nextdist = BELLRAND(maxdist);
1497         si->teamB[i].movedonsweep = -1;
1498     }
1499
1500     /* Done */
1501
1502     return si;
1503 }
1504
1505 /*
1506  * Creates and returns a drawing mask for the scope:
1507  * mask out anything outside of the disc.
1508  */
1509 static Pixmap
1510 scope_mask (Display *dpy, Window win, sonar_info *si)
1511 {
1512   XGCValues gcv;
1513   Pixmap mask = XCreatePixmap(dpy, win, si->width, si->height, 1);
1514   GC gc;
1515   gcv.foreground = 0;
1516   gc = XCreateGC (dpy, mask, GCForeground, &gcv);
1517   XFillRectangle (dpy, mask, gc, 0, 0, si->width, si->height);
1518   XSetForeground (dpy, gc, 1);
1519   XFillArc(dpy, mask, gc, si->minx, si->miny, 
1520            si->maxx - si->minx, si->maxy - si->miny,
1521            0, 360 * 64);
1522   XFreeGC (dpy, gc);
1523   return mask;
1524 }
1525
1526
1527 static void
1528 reshape (sonar_info *si)
1529 {
1530   XWindowAttributes xgwa;
1531   Pixmap mask;
1532   XGetWindowAttributes(si->dpy, si->win, &xgwa);
1533   si->width = xgwa.width;
1534   si->height = xgwa.height;
1535   si->centrex = si->width / 2;
1536   si->centrey = si->height / 2;
1537   si->maxx = si->centrex + MY_MIN(si->centrex, si->centrey) - 10;
1538   si->minx = si->centrex - MY_MIN(si->centrex, si->centrey) + 10;
1539   si->maxy = si->centrey + MY_MIN(si->centrex, si->centrey) - 10;
1540   si->miny = si->centrey - MY_MIN(si->centrex, si->centrey) + 10;
1541   si->radius = si->maxx - si->centrex;
1542
1543   /* Install the clip mask... */
1544   if (! debug_p) {
1545     mask = scope_mask (si->dpy, si->win, si);
1546     XSetClipMask(si->dpy, si->text, mask);
1547     XSetClipMask(si->dpy, si->erase, mask);
1548     XFreePixmap (si->dpy, mask); /* it's been copied into the GCs */
1549   }
1550 }
1551
1552
1553 /*
1554  * Update the location of a simulated bogie.
1555  */
1556
1557 static void
1558 updateLocation(sim_target *t) 
1559 {
1560
1561     int xdist, xtick;
1562
1563     xtick = (int) (random() %  3) - 1;
1564     xdist = (int) (random() % 11) - 5;
1565     if (((t->nexttick + xtick) < 90) && ((t->nexttick + xtick) >= 0))
1566         t->nexttick += xtick;
1567     else
1568         t->nexttick -= xtick;
1569     if (((t->nextdist + xdist) < 100) && ((t->nextdist+xdist) >= 0))
1570         t->nextdist += xdist;
1571     else
1572         t->nextdist -= xdist;
1573 }
1574
1575 /*
1576  * The simulator. This uses information in the sim_info to simulate a bunch
1577  * of bogies flying around on the screen.
1578  */
1579
1580 /*
1581  * TODO: It would be cool to have the two teams chase each other around and
1582  *       shoot it out.
1583  */
1584
1585 static Bogie *
1586 simulator(sonar_info *si, void *vinfo) 
1587 {
1588
1589     /* Local Variables */
1590
1591     int i;
1592     Bogie *list = NULL;
1593     Bogie *new;
1594     sim_target *t;
1595     sim_info *info = (sim_info *) vinfo;
1596
1597     /* Check team A */
1598
1599     for (i = 0; i < info->numA; i++) {
1600         t = &info->teamA[i];
1601         if ((t->movedonsweep != si->sweepnum) &&
1602             (t->nexttick == (si->current * -1))) {
1603             new = newBogie(strdup(t->name), t->nextdist, si->current, si->TTL);
1604             if (list != NULL)
1605                 new->next = list;
1606             list = new;
1607             updateLocation(t);
1608             t->movedonsweep = si->sweepnum;
1609         }
1610     }
1611
1612     /* Team B */
1613
1614     for (i = 0; i < info->numB; i++) {
1615         t = &info->teamB[i];
1616         if ((t->movedonsweep != si->sweepnum) &&
1617             (t->nexttick == (si->current * -1))) {
1618             new = newBogie(strdup(t->name), t->nextdist, si->current, si->TTL);
1619             if (list != NULL)
1620                 new->next = list;
1621             list = new;
1622             updateLocation(t);
1623             t->movedonsweep = si->sweepnum;
1624         }
1625     }
1626
1627     /* Done */
1628
1629     return list;
1630 }
1631
1632 /*
1633  * Compute the X coordinate of the label.
1634  *
1635  * Args:
1636  *    si - The sonar info block.
1637  *    label - The label that will be drawn.
1638  *    x - The x coordinate of the bogie.
1639  *
1640  * Returns:
1641  *    The x coordinate of the start of the label.
1642  */
1643
1644 static int
1645 computeStringX(sonar_info *si, const char *label, int x) 
1646 {
1647
1648     int width = XTextWidth(si->font, label, strlen(label));
1649     return x - (width / 2);
1650 }
1651
1652 /*
1653  * Compute the Y coordinate of the label.
1654  *
1655  * Args:
1656  *    si - The sonar information.
1657  *    y - The y coordinate of the bogie.
1658  *
1659  * Returns:
1660  *    The y coordinate of the start of the label.
1661  */
1662
1663 static int
1664 computeStringY(sonar_info *si, int y) 
1665 {
1666
1667     int fheight = si->font->ascent /* + si->font->descent */;
1668     return y + fheight;
1669 }
1670
1671 /*
1672  * Draw a Bogie on the radar screen.
1673  *
1674  * Args:
1675  *    si       - Sonar Information.
1676  *    draw     - A flag to indicate if the bogie should be drawn or erased.
1677  *    name     - The name of the bogie.
1678  *    degrees  - The number of degrees that it should apprear at.
1679  *    distance - The distance the object is from the centre.
1680  *    ttl      - The time this bogie has to live.
1681  *    age      - The time this bogie has been around.
1682  */
1683
1684 static void
1685 DrawBogie(sonar_info *si, int draw, const char *name, const char *desc,
1686           int degrees, int distance, int ttl, int age) 
1687 {
1688
1689     /* Local Variables */
1690
1691     int x, y;
1692     GC gc;
1693     int ox = si->centrex;
1694     int oy = si->centrey;
1695     int index, delta;
1696
1697     /* Compute the coordinates of the object */
1698
1699     if (distance != 0)
1700       distance = (log((double) distance) / 10.0) * si->radius;
1701     x = ox + ((double) distance * cos(4.0 * ((double) degrees)/57.29578));
1702     y = oy - ((double) distance * sin(4.0 * ((double) degrees)/57.29578));
1703
1704     /* Set up the graphics context */
1705
1706     if (draw) {
1707
1708         /* Here we attempt to compute the distance into the total life of
1709          * object that we currently are. This distance is used against
1710          * the total lifetime to compute a fraction which is the index of
1711          * the color to draw the bogie.
1712          */
1713
1714         if (si->current <= degrees)
1715             delta = (si->current - degrees) * -1;
1716         else
1717             delta = 90 + (degrees - si->current);
1718         delta += (age * 90);
1719         index = (si->text_steps - 1) * ((float) delta / (90.0 * (float) ttl));
1720         gc = si->text;
1721         XSetForeground(si->dpy, gc, si->text_colors[index].pixel);
1722
1723     } else
1724         gc = si->erase;
1725
1726   /* Draw (or erase) the Bogie */
1727
1728     XFillArc(si->dpy, si->win, gc, x, y, 5, 5, 0, 360 * 64);
1729
1730     x += 3;  /* move away from the dot */
1731     y += 7;
1732     y = computeStringY(si, y);
1733     XDrawString(si->dpy, si->win, gc,
1734                 computeStringX(si, name, x), y,
1735                 name, strlen(name));
1736
1737     if (desc && *desc)
1738       {
1739         y = computeStringY(si, y);
1740         XDrawString(si->dpy, si->win, gc,
1741                     computeStringX(si, desc, x), y,
1742                     desc, strlen(desc));
1743       }
1744 }
1745
1746
1747 /*
1748  * Draw the sonar grid.
1749  *
1750  * Args:
1751  *    si - Sonar information block.
1752  */
1753
1754 static void
1755 drawGrid(sonar_info *si) 
1756 {
1757
1758     /* Local Variables */
1759
1760     int i;
1761     int width = si->maxx - si->minx;
1762     int height = si->maxy - si->miny;
1763   
1764     /* Draw the circles */
1765
1766     XDrawArc(si->dpy, si->win, si->grid, si->minx - 10, si->miny - 10, 
1767              width + 20, height + 20,  0, (360 * 64));
1768
1769     XDrawArc(si->dpy, si->win, si->grid, si->minx, si->miny, 
1770              width, height,  0, (360 * 64));
1771
1772     XDrawArc(si->dpy, si->win, si->grid, 
1773              (int) (si->minx + (.166 * width)), 
1774              (int) (si->miny + (.166 * height)), 
1775              (unsigned int) (.666 * width), (unsigned int)(.666 * height),
1776              0, (360 * 64));
1777
1778     XDrawArc(si->dpy, si->win, si->grid, 
1779              (int) (si->minx + (.333 * width)),
1780              (int) (si->miny + (.333 * height)), 
1781              (unsigned int) (.333 * width), (unsigned int) (.333 * height),
1782              0, (360 * 64));
1783
1784     /* Draw the radial lines */
1785
1786     for (i = 0; i < 360; i += 10)
1787         if (i % 30 == 0)
1788             XDrawLine(si->dpy, si->win, si->grid, si->centrex, si->centrey,
1789                       (int) (si->centrex +
1790                       (si->radius + 10) * (cos((double) i / 57.29578))),
1791                       (int) (si->centrey -
1792                       (si->radius + 10)*(sin((double) i / 57.29578))));
1793         else
1794             XDrawLine(si->dpy, si->win, si->grid, 
1795                       (int) (si->centrex + si->radius *
1796                              (cos((double) i / 57.29578))),
1797                       (int) (si->centrey - si->radius *
1798                              (sin((double) i / 57.29578))),
1799                       (int) (si->centrex +
1800                       (si->radius + 10) * (cos((double) i / 57.29578))),
1801                       (int) (si->centrey - 
1802                       (si->radius + 10) * (sin((double) i / 57.29578))));
1803 }
1804
1805 /*
1806  * Update the Sonar scope.
1807  *
1808  * Args:
1809  *    si - The Sonar information.
1810  *    bl - A list  of bogies to add to the scope.
1811  */
1812
1813 static void
1814 Sonar(sonar_info *si, Bogie *bl) 
1815 {
1816
1817     /* Local Variables */
1818
1819     Bogie *bp, *prev;
1820     int i;
1821
1822     /* Check for expired tagets and remove them from the visible list */
1823
1824     prev = NULL;
1825     for (bp = si->visible; bp != NULL; bp = (bp ? bp->next : 0)) {
1826
1827         /*
1828          * Remove it from the visible list if it's expired or we have
1829          * a new target with the same name.
1830          */
1831
1832         bp->age ++;
1833
1834         if (((bp->tick == si->current) && (++bp->age >= bp->ttl)) ||
1835             (findNode(bl, bp->name) != NULL)) {
1836
1837 #ifndef HAVE_COCOA    /* we repaint every frame: no need to erase */
1838             DrawBogie(si, 0, bp->name, bp->desc, bp->tick,
1839                       bp->distance, bp->ttl, bp->age);
1840 #endif /* HAVE_COCOA */
1841
1842             if (prev == NULL)
1843                 si->visible = bp->next;
1844             else
1845                 prev->next = bp->next;
1846             freeBogie(bp);
1847             bp = prev;
1848         } else
1849             prev = bp;
1850     }
1851
1852     /* Draw the sweep */
1853
1854     {
1855       int start_deg = si->current * 4 * 64;
1856       int end_deg   = start_deg + (si->sweep_degrees * 64);
1857       int seg_deg = (end_deg - start_deg) / si->sweep_segs;
1858       if (seg_deg <= 0) seg_deg = 1;
1859
1860       /* Remove the trailing wedge the sonar */
1861       XFillArc(si->dpy, si->win, si->erase, si->minx, si->miny, 
1862                si->maxx - si->minx, si->maxy - si->miny, 
1863                end_deg, 
1864                4 * 64);
1865
1866       for (i = 0; i < si->sweep_segs; i++) {
1867         int ii = si->sweep_segs - i - 1;
1868         XSetForeground (si->dpy, si->hi, si->sweep_colors[ii].pixel);
1869         XFillArc (si->dpy, si->win, si->hi, si->minx, si->miny, 
1870                   si->maxx - si->minx, si->maxy - si->miny,
1871                   start_deg,
1872                   seg_deg * (ii + 1));
1873       }
1874     }
1875
1876     /* Move the new targets to the visible list */
1877
1878     for (bp = bl; bp != (Bogie *) 0; bp = bl) {
1879         bl = bl->next;
1880         bp->next = si->visible;
1881         si->visible = bp;
1882     }
1883
1884     /* Draw the visible targets */
1885
1886     for (bp = si->visible; bp != NULL; bp = bp->next) {
1887         if (bp->age < bp->ttl)          /* grins */
1888            DrawBogie(si, 1, bp->name, bp->desc,
1889                      bp->tick, bp->distance, bp->ttl,bp->age);
1890     }
1891
1892     /* Redraw the grid */
1893
1894     drawGrid(si);
1895 }
1896
1897
1898 static ping_target *
1899 parse_mode (sonar_info *si, Bool ping_works_p)
1900 {
1901   char *source = get_string_resource (si->dpy, "ping", "Ping");
1902   char *token, *end;
1903 #ifdef HAVE_PING
1904   char dummy;
1905 #endif
1906
1907   ping_target *hostlist = 0;
1908
1909   if (!source) source = strdup("");
1910
1911   if (!*source || !strcmp (source, "default"))
1912     {
1913       free (source);
1914 # ifdef HAVE_PING
1915       if (ping_works_p)         /* if root or setuid, ping will work. */
1916         source = strdup("subnet/29,/etc/hosts");
1917       else
1918 # endif
1919         source = strdup("simulation");
1920     }
1921
1922   token = source;
1923   end = source + strlen(source);
1924   while (token < end)
1925     {
1926       char *next;
1927 # ifdef HAVE_PING
1928       ping_target *new;
1929       struct stat st;
1930       unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1931       char d;
1932 # endif /* HAVE_PING */
1933
1934       for (next = token;
1935            *next &&
1936            *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1937            next++)
1938         ;
1939       *next = 0;
1940
1941
1942       if (debug_p)
1943         fprintf (stderr, "%s: parsing %s\n", progname, token);
1944
1945       if (!strcmp (token, "simulation"))
1946         return 0;
1947
1948       if (!ping_works_p)
1949         {
1950           fprintf(stderr,
1951            "%s: this program must be setuid to root for `ping mode' to work.\n"
1952              "       Running in `simulation mode' instead.\n",
1953                   progname);
1954           return 0;
1955         }
1956
1957 #ifdef HAVE_PING
1958       if ((4 == sscanf (token, "%u.%u.%u/%u %c",    &n0,&n1,&n2,    &m,&d)) ||
1959           (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1960         {
1961           /* subnet: A.B.C.D/M
1962              subnet: A.B.C/M
1963            */
1964           unsigned long ip = pack_addr (n0, n1, n2, n3);
1965           new = subnetHostsList(ip, m);
1966         }
1967       else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1968         {
1969           /* IP: A.B.C.D
1970            */
1971           new = newHost (token);
1972         }
1973       else if (!strcmp (token, "subnet"))
1974         {
1975           new = subnetHostsList(0, 24);
1976         }
1977       else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1978         {
1979           new = subnetHostsList(0, m);
1980         }
1981       else if (*token == '.' || *token == '/' || 
1982                *token == '$' || *token == '~' ||
1983                !stat (token, &st))
1984         {
1985           /* file name
1986            */
1987           new = readPingHostsFile (token);
1988         }
1989       else
1990         {
1991           /* not an existant file - must be a host name
1992            */
1993           new = newHost (token);
1994         }
1995
1996       if (new)
1997         {
1998           ping_target *nn = new;
1999           while (nn && nn->next)
2000             nn = nn->next;
2001           nn->next = hostlist;
2002           hostlist = new;
2003
2004           si->sensor = ping;
2005         }
2006 #endif /* HAVE_PING */
2007
2008       token = next + 1;
2009       while (token < end &&
2010              (*token == ',' || *token == ' ' ||
2011               *token == '\t' || *token == '\n'))
2012         token++;
2013     }
2014
2015   free (source);
2016   return hostlist;
2017 }
2018
2019
2020 /*
2021  * Initialize the Sonar.
2022  *
2023  * Args:
2024  *    dpy - The X display.
2025  *    win - The X window;
2026  *
2027  * Returns:
2028  *   A sonar_info strcuture or null if memory allocation problems occur.
2029  */
2030
2031 static void *
2032 sonar_init (Display *dpy, Window win)
2033 {
2034     /* Local Variables */
2035
2036     XGCValues gcv;
2037     XWindowAttributes xwa;
2038     sonar_info *si;
2039     XColor start, end;
2040     int h1, h2;
2041     double s1, s2, v1, v2;
2042
2043     debug_p = get_boolean_resource (dpy, "debug", "Debug");
2044     resolve_p = get_boolean_resource (dpy, "resolve", "Resolve");
2045     times_p = get_boolean_resource (dpy, "showTimes", "ShowTimes");
2046
2047     /* Create the Sonar information structure */
2048
2049     if ((si = (sonar_info *) calloc(1, sizeof(sonar_info))) == NULL) {
2050         fprintf(stderr, "%s: Out of memory\n", progname);
2051         exit (1);
2052     }
2053
2054     /* Initialize the structure for the current environment */
2055
2056     si->dpy = dpy;
2057     si->win = win;
2058     si->visible = NULL;
2059
2060     XGetWindowAttributes(dpy, win, &xwa);
2061     si->cmap = xwa.colormap;
2062
2063     si->current = 0;
2064     si->sweepnum = 0;
2065
2066     /* Get the font */
2067
2068     {
2069       char *fn = get_string_resource (dpy, "font", "Font");
2070       if (((si->font = XLoadQueryFont(dpy, fn)) == NULL) &&
2071           ((si->font = XLoadQueryFont(dpy, "fixed")) == NULL)) {
2072         fprintf(stderr, "%s: can't load an appropriate font\n", progname);
2073         exit (1);
2074       }
2075       if (fn) free (fn);
2076     }
2077
2078     /* Get the delay between animation frames */
2079
2080     si->delay = get_integer_resource (dpy, "delay", "Integer");
2081
2082     if (si->delay < 0) si->delay = 0;
2083     si->TTL = get_integer_resource(dpy, "ttl", "TTL");
2084
2085     /* Create the Graphics Contexts that will be used to draw things */
2086
2087     gcv.foreground = 
2088         get_pixel_resource (dpy, si->cmap, "sweepColor", "SweepColor");
2089     si->hi = XCreateGC(dpy, win, GCForeground, &gcv);
2090     gcv.font = si->font->fid;
2091     si->text = XCreateGC(dpy, win, GCForeground|GCFont, &gcv);
2092     gcv.foreground = get_pixel_resource(dpy, si->cmap,
2093                                         "scopeColor", "ScopeColor");
2094     si->erase = XCreateGC (dpy, win, GCForeground, &gcv);
2095     gcv.foreground = get_pixel_resource(dpy, si->cmap,
2096                                         "gridColor", "GridColor");
2097     si->grid = XCreateGC (dpy, win, GCForeground, &gcv);
2098
2099     reshape (si);
2100
2101     /* Compute pixel values for fading text on the display */
2102     {
2103       char *s = get_string_resource(dpy, "textColor", "TextColor");
2104       XParseColor(dpy, si->cmap, s, &start);
2105       if (s) free (s);
2106       s = get_string_resource(dpy, "scopeColor", "ScopeColor");
2107       XParseColor(dpy, si->cmap, s, &end);
2108       if (s) free (s);
2109     }
2110
2111     rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
2112     rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
2113
2114     si->text_steps = get_integer_resource(dpy, "textSteps", "TextSteps");
2115     if (si->text_steps < 0 || si->text_steps > 255)
2116       si->text_steps = 10;
2117
2118     si->text_colors = (XColor *) calloc(si->text_steps, sizeof(XColor));
2119     make_color_ramp (dpy, si->cmap,
2120                      h1, s1, v1,
2121                      h2, s2, v2,
2122                      si->text_colors, &si->text_steps,
2123                      False, True, False);
2124
2125     /* Compute the pixel values for the fading sweep */
2126
2127     {
2128       char *s = get_string_resource(dpy, "sweepColor", "SweepColor");
2129       XParseColor(dpy, si->cmap, s, &start);
2130       if (s) free (s);
2131     }
2132
2133     rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
2134
2135     si->sweep_degrees = get_integer_resource(dpy, "sweepDegrees", "Degrees");
2136     if (si->sweep_degrees <= 0) si->sweep_degrees = 20;
2137     if (si->sweep_degrees > 350) si->sweep_degrees = 350;
2138
2139     si->sweep_segs = get_integer_resource(dpy, "sweepSegments", "SweepSegments");
2140     if (si->sweep_segs < 1 || si->sweep_segs > 255)
2141       si->sweep_segs = 255;
2142
2143     si->sweep_colors = (XColor *) calloc(si->sweep_segs, sizeof(XColor));
2144     make_color_ramp (dpy, si->cmap,
2145                      h1, s1, v1,
2146                      h2, s2, v2,
2147                      si->sweep_colors, &si->sweep_segs,
2148                      False, True, False);
2149
2150     if (si->sweep_segs <= 0)
2151       si->sweep_segs = 1;
2152
2153
2154 # ifdef HAVE_PING
2155     si->sensor_info = (void *) init_ping(si);
2156 # else  /* !HAVE_PING */
2157     parse_mode (dpy, 0);  /* just to check argument syntax */
2158 # endif /* !HAVE_PING */
2159
2160     if (!si->sensor)
2161       {
2162         si->sensor = simulator;
2163         si->sensor_info = (void *) init_sim(dpy);
2164         if (! si->sensor_info)
2165           exit(1);
2166       }
2167
2168     /* Done */
2169
2170     return si;
2171 }
2172
2173
2174 static unsigned long
2175 sonar_draw (Display *dpy, Window window, void *closure)
2176 {
2177   sonar_info *si = (sonar_info *) closure;
2178   Bogie *bl;
2179   struct timeval start, finish;
2180   long delay;
2181
2182 # ifdef HAVE_COCOA  /* repaint the whole window so that antialiasing works */
2183   XClearWindow (dpy,window);
2184   XFillRectangle (dpy, window, si->erase, 0, 0, si->width, si->height);
2185 # endif
2186
2187   /* Call the sensor and display the results */
2188
2189 # ifdef GETTIMEOFDAY_TWO_ARGS
2190   gettimeofday(&start, (struct timezone *) 0);
2191 # else
2192   gettimeofday(&start);
2193 # endif
2194   bl = si->sensor(si, si->sensor_info);
2195   Sonar(si, bl);
2196
2197   /* Set up and sleep for the next one */
2198
2199   si->current = (si->current - 1) % 90;
2200   if (si->current == 0)
2201     si->sweepnum++;
2202
2203 # ifdef GETTIMEOFDAY_TWO_ARGS
2204   gettimeofday(&finish, (struct timezone *) 0);
2205 # else
2206   gettimeofday(&finish);
2207 # endif
2208
2209   delay = si->delay - delta(&start, &finish);
2210   if (delay < 0) delay = 0;
2211   return delay;
2212 }
2213
2214
2215 static void
2216 sonar_reshape (Display *dpy, Window window, void *closure, 
2217                  unsigned int w, unsigned int h)
2218 {
2219   sonar_info *si = (sonar_info *) closure;
2220   XClearWindow (si->dpy, si->win);
2221   reshape (si);
2222 }
2223
2224 static Bool
2225 sonar_event (Display *dpy, Window window, void *closure, XEvent *event)
2226 {
2227   return False;
2228 }
2229
2230 static void
2231 sonar_free (Display *dpy, Window window, void *closure)
2232 {
2233 }
2234
2235
2236
2237 static const char *sonar_defaults [] = {
2238     ".background:      #000000",
2239     ".sweepColor:      #00FF00",
2240     "*delay:           100000",
2241     "*scopeColor:      #003300",
2242     "*gridColor:       #00AA00",
2243     "*textColor:       #FFFF00",
2244     "*ttl:             90",
2245     "*mode:            default",
2246     "*font:            fixed",
2247     "*sweepDegrees:    30",
2248
2249     "*textSteps:       80",     /* npixels */
2250     "*sweepSegments:   80",     /* npixels */
2251
2252     "*pingTimeout:     3000",
2253
2254     "*teamAName:       F18",
2255     "*teamBName:       MIG",
2256     "*teamACount:      4",
2257     "*teamBCount:      4",
2258
2259     "*ping:            default",
2260     "*resolve:         true",
2261     "*showTimes:       true",
2262     ".debug:           false",
2263     0
2264 };
2265
2266 static XrmOptionDescRec sonar_options [] = {
2267     {"-background",   ".background",   XrmoptionSepArg, 0 },
2268     {"-sweep-color",  ".sweepColor",   XrmoptionSepArg, 0 },
2269     {"-scope-color",  ".scopeColor",   XrmoptionSepArg, 0 },
2270     {"-grid-color",   ".gridColor",    XrmoptionSepArg, 0 },
2271     {"-text-color",   ".textColor",    XrmoptionSepArg, 0 },
2272     {"-ttl",          ".ttl",          XrmoptionSepArg, 0 },
2273     {"-font",         ".font",         XrmoptionSepArg, 0 },
2274 #ifdef HAVE_PING
2275     {"-ping-timeout", ".pingTimeout",  XrmoptionSepArg, 0 },
2276 #endif /* HAVE_PING */
2277     {"-team-a-name",   ".teamAName",   XrmoptionSepArg, 0 },
2278     {"-team-b-name",   ".teamBName",   XrmoptionSepArg, 0 },
2279     {"-team-a-count",  ".teamACount",  XrmoptionSepArg, 0 },
2280     {"-team-b-count",  ".teamBCount",  XrmoptionSepArg, 0 },
2281
2282     {"-ping",          ".ping",        XrmoptionSepArg, 0 },
2283     {"-no-dns",        ".resolve",     XrmoptionNoArg, "False" },
2284     {"-no-times",      ".showTimes",   XrmoptionNoArg, "False" },
2285     {"-debug",         ".debug",       XrmoptionNoArg, "True" },
2286     { 0, 0, 0, 0 }
2287 };
2288
2289
2290 XSCREENSAVER_MODULE ("Sonar", sonar)