Introduce EXIM_BUILD_SUFFIX for src/Makefile and testsuite
[users/heiko/exim.git] / src / exim_monitor / em_main.c
1 /*************************************************
2 *                  Exim Monitor                  *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2016 */
6 /* See the file NOTICE for conditions of use and distribution. */
7
8
9 #include "em_hdr.h"
10
11 /* This module contains the main program of the Exim monitor, which
12 sets up the world and then lets the XtAppMainLoop function
13 run things off X events. */
14
15
16 /*************************************************
17 *               Static variables                 *
18 *************************************************/
19
20 /* Fallback resources */
21
22 static String fallback_resources[] = {"eximon.geometry: +150+0", NULL};
23
24 /* X11 fixed argument lists */
25
26 static Arg quit_args[] = {
27   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
28   {XtNlabel,    (XtArgVal) " Quit "},
29   {"left",      XawChainLeft},
30   {"right",     XawChainLeft},
31   {"top",       XawChainTop},
32   {"bottom",    XawChainTop}
33 };
34
35 static Arg resize_args[] = {
36   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
37   {XtNfromHoriz,(XtArgVal) NULL},         /* must be second */
38   {XtNlabel,    (XtArgVal) " Size "},
39   {"left",      XawChainLeft},
40   {"right",     XawChainLeft},
41   {"top",       XawChainTop},
42   {"bottom",    XawChainTop}
43 };
44
45 static Arg update_args[] = {
46   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
47   {XtNlabel,    (XtArgVal) " Update "},
48   {"left",      XawChainLeft},
49   {"right",     XawChainLeft},
50   {"top",       XawChainTop},
51   {"bottom",    XawChainTop}
52 };
53
54 static Arg hide_args[] = {
55   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
56   {XtNfromHoriz,(XtArgVal) NULL},         /* must be second */
57   {XtNlabel,    (XtArgVal) " Hide "},
58   {"left",      XawChainLeft},
59   {"right",     XawChainLeft},
60   {"top",       XawChainTop},
61   {"bottom",    XawChainTop}
62 };
63
64 static Arg unhide_args[] = {
65   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
66   {XtNfromHoriz,(XtArgVal) NULL},         /* must be second */
67   {XtNlabel,    (XtArgVal) " Unhide "},
68   {"left",      XawChainLeft},
69   {"right",     XawChainLeft},
70   {"top",       XawChainTop},
71   {"bottom",    XawChainTop}
72 };
73
74 static Arg log_args[] = {
75   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
76   {"editType",  XawtextEdit},
77   {"useStringInPlace", (XtArgVal)TRUE},
78   {"string",    (XtArgVal)""},            /* dummy to get it going */
79   {"scrollVertical", XawtextScrollAlways},
80   {"scrollHorizontal", XawtextScrollAlways},
81   {"right",     XawChainRight},
82   {"top",       XawChainTop},
83   {"bottom",    XawChainTop}
84 };
85
86 static Arg queue_args[] = {
87   {XtNfromVert, (XtArgVal) NULL},         /* must be first */
88   {"editType",  XawtextEdit},
89   {"string",    (XtArgVal)""},            /* dummy to get it going */
90   {"scrollVertical", XawtextScrollAlways},
91   {"right",     XawChainRight},
92   {"top",       XawChainTop},
93   {"bottom",    XawChainBottom}
94 };
95
96 static Arg sizepos_args[] = {
97   {"width",     (XtArgVal)NULL},
98   {"height",    (XtArgVal)NULL},
99   {"x",         (XtArgVal)NULL},
100   {"y",         (XtArgVal)NULL}
101 };
102
103 XtActionsRec menu_action_table[] = {
104   { "menu-create",  menu_create } };
105
106 /* Types of non-message dialog action */
107
108 enum { da_hide };
109
110 /* Miscellaneous local variables */
111
112 static int dialog_action;
113 static int tick_stripchart_accumulator = 999999;
114 static int tick_interval = 2;
115 static int maxposset = 0;
116 static int minposset = 0;
117 static int x_adjustment = -1;
118 static int y_adjustment = -1;
119 static Dimension screenwidth, screenheight;
120 static Dimension original_x, original_y;
121 static Dimension maxposx, maxposy;
122 static Dimension minposx, minposy;
123 static Dimension maxwidth, maxheight;
124 static Widget outer_form_widget;
125 static Widget hide_widget;
126 static Widget above_queue_widget;
127
128
129
130
131 #ifdef STRERROR_FROM_ERRLIST
132 /*************************************************
133 *     Provide strerror() for non-ANSI libraries  *
134 *************************************************/
135
136 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
137 in their libraries, but can provide the same facility by this simple
138 alternative function. */
139
140 uschar *
141 strerror(int n)
142 {
143 if (n < 0 || n >= sys_nerr) return "unknown error number";
144 return sys_errlist[n];
145 }
146 #endif /* STRERROR_FROM_ERRLIST */
147
148
149
150 /*************************************************
151 *         Handle attempts to write the log       *
152 *************************************************/
153
154 /* The message gets written to stderr when log_write() is called from a
155 utility. The message always gets '\n' added on the end of it. These calls come
156 from modules such as store.c when things go drastically wrong (e.g. malloc()
157 failing). In normal use they won't get obeyed.
158
159 Arguments:
160   selector  not relevant when running a utility
161   flags     not relevant when running a utility
162   format    a printf() format
163   ...       arguments for format
164
165 Returns:    nothing
166 */
167
168 void
169 log_write(unsigned int selector, int flags, const char *format, ...)
170 {
171 va_list ap;
172 va_start(ap, format);
173 vfprintf(stderr, format, ap);
174 fprintf(stderr, "\n");
175 va_end(ap);
176 selector = selector;     /* Keep picky compilers happy */
177 flags = flags;
178 }
179
180
181
182
183 /*************************************************
184 *        Extract port from address string        *
185 *************************************************/
186
187 /* In the spool file, a host plus port is given as an IP address followed by a
188 dot and a port number. This function decodes this. It is needed by the
189 spool-reading function, and copied here to avoid having to include the whole
190 host.c module. One day the interaction between exim and eximon with regard to
191 included code MUST be tidied up!
192
193 Argument:
194   address    points to the string; if there is a port, the '.' in the string
195              is overwritten with zero to terminate the address
196
197 Returns:     0 if there is no port, else the port number.
198 */
199
200 int
201 host_address_extract_port(uschar *address)
202 {
203 int skip = -3;                     /* Skip 3 dots in IPv4 addresses */
204 address--;
205 while (*(++address) != 0)
206   {
207   int ch = *address;
208   if (ch == ':') skip = 0;         /* Skip 0 dots in IPv6 addresses */
209     else if (ch == '.' && skip++ >= 0) break;
210   }
211 if (*address == 0) return 0;
212 *address++ = 0;
213 return Uatoi(address);
214 }
215
216
217
218
219 /*************************************************
220 *                SIGCHLD handler                 *
221 *************************************************/
222
223 /* Operations on messages are done in subprocesses; this handler
224 just catches them when they finish. It causes a queue display update
225 unless configured not to. */
226
227 static void sigchld_handler(int sig)
228 {
229 while (waitpid(-1, NULL, WNOHANG) > 0);
230 signal(sig, sigchld_handler);
231 if (action_queue_update) tick_queue_accumulator = 999999;
232 }
233
234
235
236 /*************************************************
237 *             Callback routines                  *
238 *************************************************/
239
240
241 void updateAction(Widget w, XtPointer client_data, XtPointer call_data)
242 {
243 w = w;       /* Keep picky compilers happy */
244 client_data = client_data;
245 call_data = call_data;
246 scan_spool_input(TRUE);
247 queue_display();
248 tick_queue_accumulator = 0;
249 }
250
251 void hideAction(Widget w, XtPointer client_data, XtPointer call_data)
252 {
253 w = w;       /* Keep picky compilers happy */
254 client_data = client_data;
255 call_data = call_data;
256 actioned_message[0] = 0;
257 dialog_ref_widget = w;
258 dialog_action = da_hide;
259 create_dialog(US"Hide addresses ending with", US"");
260 }
261
262 void unhideAction(Widget w, XtPointer client_data, XtPointer call_data)
263 {
264 skip_item *sk = queue_skip;
265
266 w = w;       /* Keep picky compilers happy */
267 client_data = client_data;
268 call_data = call_data;
269
270 while (sk != NULL)
271   {
272   skip_item *next = sk->next;
273   store_free(sk);
274   sk = next;
275   }
276 queue_skip = NULL;
277
278 XtDestroyWidget(unhide_widget);
279 unhide_widget = NULL;
280
281 scan_spool_input(TRUE);
282 queue_display();
283 tick_queue_accumulator = 0;
284 }
285
286 void quitAction(Widget w, XtPointer client_data, XtPointer call_data)
287 {
288 w = w;       /* Keep picky compilers happy */
289 client_data = client_data;
290 call_data = call_data;
291 exit(0);
292 }
293
294
295 /* Action when the "Size" button is pressed. This is a kludged up mess
296 that I made work after much messing around. Reading the position of the
297 toplevel widget gets the absolute position of the data portion of the window,
298 excluding the window manager's furniture. However, positioning the toplevel
299 widget's window seems to position the top corner of the furniture under the twm
300 window manager, but not under fwvm and others. The two cases are distinguished
301 by the values of x_adjustment and y_adjustment.
302
303 For twm (adjustment >= 0), one has to fudge the miminizing function to ensure
304 that we go back to exactly the same position as before.
305
306 For fwvm (adjustment < 0), one has to fudge the "top left hand corner"
307 positioning to ensure that the window manager's furniture gets displayed on the
308 screen. I haven't found a way of discovering the thickness of the furniture, so
309 some screwed-in values are used.
310
311 This is all ad hoc, developed by floundering around as I haven't found any
312 documentation that tells me what I really should do. */
313
314 void resizeAction(Widget button, XtPointer client_data, XtPointer call_data)
315 {
316 Dimension x, y;
317 Dimension width, height;
318 XWindowAttributes a;
319 Window w = XtWindow(toplevel_widget);
320
321 button = button;    /* Keep picky compilers happy */
322 client_data = client_data;
323 call_data = call_data;
324
325 /* Get the position and size of the top level widget. */
326
327 sizepos_args[0].value = (XtArgVal)(&width);
328 sizepos_args[1].value = (XtArgVal)(&height);
329 sizepos_args[2].value = (XtArgVal)(&x);
330 sizepos_args[3].value = (XtArgVal)(&y);
331 XtGetValues(toplevel_widget, sizepos_args, 4);
332
333 /* Get the position of the widget's window relative to its parent; this
334 gives the thickness of the window manager's furniture. At least it does
335 in twm. For fwvm it gives zero. The size/movement function uses this data.
336 I tried doing this before entering the main loop, but it didn't always
337 work properly with twm. Running it every time seems to be OK. */
338
339 XGetWindowAttributes(X_display, XtWindow(toplevel_widget), &a);
340 if (a.x != 0) x_adjustment = a.x;
341 if (a.y != 0) y_adjustment = a.y;
342
343 /* If at maximum size, reduce to minimum and move back to where it was
344 when maximized, if that value is set, allowing for the furniture in cases
345 where the positioning includes the furniture. */
346
347 if (width == maxwidth && height == maxheight)
348   {
349   maxposx = x;
350   maxposy = y;
351   maxposset = 1;
352
353   if (minposset)
354     xs_SetValues(toplevel_widget, 4,
355       "width",     min_width,
356       "height",    min_height,
357       "x",         minposx - ((x_adjustment >= 0)? x_adjustment : 0),
358       "y",         minposy - ((y_adjustment >= 0)? y_adjustment : 0));
359   else
360     xs_SetValues(toplevel_widget, 2,
361       "width",     min_width,
362       "height",    min_height);
363   }
364
365 /* Else always expand to maximum. If currently at minimum size, remember where
366 it was for coming back. If we don't have a value for the thickness of the
367 furniture, the implication is that the coordinates position the application
368 window, so we can't use (0,0) because that loses the furniture. Use screwed in
369 values that seem to work with fvwm. */
370
371 else
372   {
373   int xx = x;
374   int yy = y;
375
376   if (width == min_width && height == min_height)
377     {
378     minposx = x;
379     minposy = y;
380     minposset = 1;
381     }
382
383   if ((int)(x + maxwidth) > (int)screenwidth ||
384       (int)(y + maxheight + 10) > (int)screenheight)
385     {
386     if (maxposset)
387       {
388       xx = maxposx - ((x_adjustment >= 0)? x_adjustment : 0);
389       yy = maxposy - ((y_adjustment >= 0)? y_adjustment : 0);
390       }
391     else
392       {
393       if ((int)(x + maxwidth) > (int)screenwidth)
394         xx = (x_adjustment >= 0)? 0 : 4;
395       if ((int)(y + maxheight + 10) > (int)screenheight)
396         yy = (y_adjustment >= 0)? 0 : 21;
397       }
398
399     xs_SetValues(toplevel_widget, 4,
400       "width",     maxwidth,
401       "height",    maxheight,
402       "x",         xx,
403       "y",         yy);
404     }
405
406   else xs_SetValues(toplevel_widget, 2,
407         "width",     maxwidth,
408         "height",    maxheight);
409   }
410
411 /* Ensure the window is at the top */
412
413 XRaiseWindow(X_display, w);
414 }
415
416
417
418
419 /*************************************************
420 *          Handle input from non-msg dialogue    *
421 *************************************************/
422
423 /* The various cases here are: hide domain, (no more yet) */
424
425 void NonMessageDialogue(uschar *s)
426 {
427 skip_item *sk;
428
429 switch(dialog_action)
430   {
431   case da_hide:
432
433   /* Create the unhide button if not present */
434
435   if (unhide_widget == NULL)
436     {
437     unhide_args[0].value = (XtArgVal) above_queue_widget;
438     unhide_args[1].value = (XtArgVal) hide_widget;
439     unhide_widget = XtCreateManagedWidget("unhide", commandWidgetClass,
440       outer_form_widget, unhide_args, XtNumber(unhide_args));
441     XtAddCallback(unhide_widget, "callback",  unhideAction, NULL);
442     }
443
444   /* Add item to skip queue */
445
446   sk = (skip_item *)store_malloc(sizeof(skip_item) + Ustrlen(s));
447   sk->next = queue_skip;
448   queue_skip = sk;
449   Ustrcpy(sk->text, s);
450   sk->reveal = time(NULL) + 60 * 60;
451   scan_spool_input(TRUE);
452   queue_display();
453   tick_queue_accumulator = 0;
454   break;
455   }
456 }
457
458
459
460 /*************************************************
461 *              Ticker function                   *
462 *************************************************/
463
464 /* This function is called initially to set up the starting data
465 values; it then sets a timeout so that it continues to be called
466 every 2 seconds. */
467
468 static void ticker(XtPointer pt, XtIntervalId *i)
469 {
470 pipe_item **pp = &pipe_chain;
471 pipe_item *p = pipe_chain;
472 tick_queue_accumulator += tick_interval;
473 tick_stripchart_accumulator += tick_interval;
474 read_log();
475
476 pt = pt;    /* Keep picky compilers happy */
477 i = i;
478
479 /* If we have passed the queue update time, we must do a full
480 scan of the queue, checking for new arrivals, etc. This will
481 as a by-product set the count of items for use by the stripchart
482 display. On some systems, SIGCHLD signals can get lost at busy times,
483 so just in case, clean up any completed children here. */
484
485 if (tick_queue_accumulator >= queue_update)
486   {
487   scan_spool_input(TRUE);
488   queue_display();
489   tick_queue_accumulator = 0;
490   if (tick_stripchart_accumulator >= stripchart_update)
491     tick_stripchart_accumulator = 0;
492   while (waitpid(-1, NULL, WNOHANG) > 0);
493   }
494
495 /* Otherwise, if we have exceeded the stripchart interval,
496 do a reduced queue scan that simply provides the count for
497 the stripchart. */
498
499 else if (tick_stripchart_accumulator >= stripchart_update)
500   {
501   scan_spool_input(FALSE);
502   tick_stripchart_accumulator = 0;
503   }
504
505 /* Scan any pipes that are set up for listening to delivery processes,
506 and display their output if their windows are still open. */
507
508 while (p != NULL)
509   {
510   int count;
511   uschar buffer[256];
512
513   while ((count = read(p->fd, buffer, 254)) > 0)
514     {
515     buffer[count] = 0;
516     if (p->widget != NULL) text_show(p->widget, buffer);
517     }
518
519   if (count == 0)
520     {
521     close(p->fd);
522     *pp = p->next;
523     store_free(p);
524     /* If configured, cause display update */
525     if (action_queue_update) tick_queue_accumulator = 999999;
526     }
527
528   else pp = &(p->next);
529
530   p = *pp;
531   }
532
533 /* Reset the timer for next time */
534
535 XtAppAddTimeOut(X_appcon, tick_interval * 1000, ticker, 0);
536 }
537
538
539
540 /*************************************************
541 *             Find Num Lock modifiers            *
542 *************************************************/
543
544 /* Return a string with the modifiers generated by XK_Num_Lock, or return
545 NULL if XK_Num_Lock doesn't generate any modifiers. This is needed because Num
546 Lock isn't always the same modifier on all servers.
547
548 Arguments:
549   display   the Display
550   buf       a buffer in which to put the answers (long enough to hold 5)
551
552 Returns:    points to the buffer, or NULL
553 */
554
555 static uschar *
556 numlock_modifiers(Display *display, uschar *buf)
557 {
558 XModifierKeymap *m;
559 int i, j;
560 uschar *ret = NULL;
561
562 m = XGetModifierMapping(display);
563 if (m == NULL)
564   {
565   printf("Not enough memory\n");
566   exit (EXIT_FAILURE);
567   }
568
569 /* Look at Mod1 through Mod5, and fill in the buffer as necessary. */
570
571 buf[0] = 0;
572 for (i = 3; i < 8; i++)
573   {
574   for (j = 0; j < m->max_keypermod; j++)
575     {
576     if (XKeycodeToKeysym(display, m->modifiermap [i*m->max_keypermod + j], 0)
577         == XK_Num_Lock)
578       {
579       sprintf(CS(buf+Ustrlen(buf)), " Mod%d", i-2);
580       ret = buf;
581       }
582     }
583   }
584
585 XFreeModifiermap(m);
586 return ret;
587 }
588
589
590
591 /*************************************************
592 *               Initialize                       *
593 *************************************************/
594
595 int main(int argc, char **argv)
596 {
597 int i;
598 struct stat statdata;
599 uschar modbuf[] = " Mod1 Mod2 Mod3 Mod4 Mod5";
600 uschar *numlock;
601 Widget stripchart_form_widget,
602        update_widget,
603        quit_widget,
604        resize_widget;
605
606 /* The exim global message_id needs to get set */
607
608 message_id_external = message_id_option + 1;
609 message_id = message_id_external + 1;
610 message_subdir[1] = 0;
611
612 /* Some store needs getting for big_buffer, which is used for
613 constructing file names and things. This call will initialize
614 the store_get() function. */
615
616 big_buffer = store_get(big_buffer_size);
617
618 /* Set up the version string and date and output them */
619
620 version_init();
621 printf("\nExim Monitor version %s (compiled %s) initializing\n",
622   version_string, version_date);
623
624 /* Initialize various things from the environment and arguments. */
625
626 init(argc, USS argv);
627
628 /* Set up the SIGCHLD handler */
629
630 signal(SIGCHLD, sigchld_handler);
631
632 /* Get the buffer for storing the string for the log display. */
633
634 log_display_buffer = (uschar *)store_malloc(log_buffer_size);
635 log_display_buffer[0] = 0;
636
637 /* Initialize the data structures for the stripcharts */
638
639 stripchart_init();
640
641 /* If log_file contains the empty string, then Exim is running using syslog
642 only, and we can't tail the log. If not, open the log file and position to the
643 end of it. Before doing so, we have to detect whether the log files are
644 datestamped, and if so, sort out the name. The string in log_file already has
645 %s replaced by "main"; if datestamping is occurring, %D or %M will be present.
646 In fact, we don't need to test explicitly - just process the string with
647 string_format.
648
649 Once opened, save the file's inode so that we can detect when the file is
650 switched to another one for non-datestamped files. However, allow the monitor
651 to start up without a log file (can happen if no messages have been sent
652 today.) */
653
654 if (log_file[0] != 0)
655   {
656   /* Do *not* use "%s" here, we need the %D datestamp in the log_file to
657   be expanded! */
658   (void)string_format(log_file_open, sizeof(log_file_open), CS log_file);
659   log_datestamping = string_datestamp_offset >= 0;
660
661   LOG = fopen(CS log_file_open, "r");
662
663   if (LOG == NULL)
664     {
665     printf("*** eximon warning: can't open log file %s - will try "
666       "periodically\n", log_file_open);
667     }
668   else
669     {
670     fseek(LOG, 0, SEEK_END);
671     log_position = ftell(LOG);
672     if (fstat(fileno(LOG), &statdata))
673       {
674       perror("log file fstat");
675       fclose(LOG);
676       LOG=NULL;
677       }
678     else
679       log_inode = statdata.st_ino;
680     }
681   }
682 else
683   {
684   printf("*** eximon warning: no log file available to tail\n");
685   }
686
687 /* Now initialize the X world and create the top-level widget */
688
689 toplevel_widget = XtAppInitialize(&X_appcon, "Eximon", NULL, 0, &argc, argv,
690   fallback_resources, NULL, 0);
691 X_display = XtDisplay(toplevel_widget);
692 xs_SetValues(toplevel_widget, 4,
693   "title",     window_title,
694   "iconName",  window_title,
695   "minWidth",  min_width,
696   "minHeight", min_height);
697
698
699 /* Create the action for setting up the menu in the queue display
700 window, and register the action for positioning the menu. */
701
702 XtAppAddActions(X_appcon, menu_action_table, 1);
703 XawSimpleMenuAddGlobalActions(X_appcon);
704
705 /* Set up translation tables for the text widgets we use. We don't
706 want all the generality of editing, etc. that the defaults provide.
707 This cannot be done before initializing X - the parser complains
708 about unknown events, modifiers, etc. in an unhelpful way... The
709 queue text widget has a different table which includes the button
710 for popping up the menu. Note that the order of things in these
711 tables is significant. Shift<thing> must come before <thing> as
712 otherwise it isn't noticed. */
713
714 /*
715    <FocusIn>:      display-caret(on)\n\
716    <FocusOut>:     display-caret(off)\n\
717 */
718
719 /* The translation manager sets up passive grabs for the menu popups as a
720 result of MenuPopup(), but the grabs match only the exact modifiers listed,
721 hence combinations with and without caps-lock and num-lock must be given,
722 rather than just one "Shift<Btn1Down>" (or whatever menu_event is set to),
723 despite the fact that that notation (without a leading !) should ignore the
724 state of other modifiers. Thanks to Kevin Ryde for this information, and for
725 the function above that discovers which modifier is Num Lock, because it turns
726 out that it varies from server to server. */
727
728 sprintf(CS big_buffer,
729   "!%s:            menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
730    !Lock %s:       menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
731   ", menu_event, menu_event);
732
733 numlock = numlock_modifiers(X_display, modbuf); /* Get Num Lock modifier(s) */
734
735 if (numlock != NULL) sprintf(CS big_buffer + Ustrlen(big_buffer),
736   "!%s %s:         menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
737    !Lock %s %s:    menu-create() XawPositionSimpleMenu(menu) MenuPopup(menu)\n\
738   ", numlock, menu_event, numlock, menu_event);
739
740 sprintf(CS big_buffer + Ustrlen(big_buffer),
741   "<Btn1Down>:     select-start()\n\
742    <Btn1Motion>:   extend-adjust()\n\
743    <Btn1Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
744    <Btn3Down>:     extend-start()\n\
745    <Btn3Motion>:   extend-adjust()\n\
746    <Btn3Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
747    <Key>Up:        scroll-one-line-down()\n\
748    <Key>Down:      scroll-one-line-up()\n\
749    Ctrl<Key>R:     search(backward)\n\
750    Ctrl<Key>S:     search(forward)\n\
751   ");
752
753 queue_trans = XtParseTranslationTable(CS big_buffer);
754
755 text_trans = XtParseTranslationTable(
756   "<Btn1Down>:     select-start()\n\
757    <Btn1Motion>:   extend-adjust()\n\
758    <Btn1Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
759    <Btn3Down>:     extend-start()\n\
760    <Btn3Motion>:   extend-adjust()\n\
761    <Btn3Up>:       extend-end(PRIMARY,CUT_BUFFER0)\n\
762    <Key>Up:        scroll-one-line-down()\n\
763    <Key>Down:      scroll-one-line-up()\n\
764    Ctrl<Key>R:     search(backward)\n\
765    Ctrl<Key>S:     search(forward)\n\
766   ");
767
768
769 /* Create a toplevel form widget to hold all the other things */
770
771 outer_form_widget = XtCreateManagedWidget("form", formWidgetClass,
772   toplevel_widget, NULL, 0);
773
774 /* Now create an inner form to hold the stripcharts */
775
776 stripchart_form_widget = XtCreateManagedWidget("form", formWidgetClass,
777   outer_form_widget, NULL, 0);
778 xs_SetValues(stripchart_form_widget, 5,
779   "defaultDistance", 8,
780   "left",            XawChainLeft,
781   "right",           XawChainLeft,
782   "top",             XawChainTop,
783   "bottom",          XawChainTop);
784
785 /* Create the queue count stripchart and its label. */
786
787 create_stripchart(stripchart_form_widget, queue_stripchart_name);
788
789 /* If configured, create the size monitoring stripchart, but
790 only if the OS supports statfs(). */
791
792 if (size_stripchart != NULL)
793   {
794 #ifdef HAVE_STATFS
795   if (size_stripchart_name == NULL)
796     {
797     size_stripchart_name = size_stripchart + Ustrlen(size_stripchart) - 1;
798     while (size_stripchart_name > size_stripchart &&
799       *size_stripchart_name == '/') size_stripchart_name--;
800     while (size_stripchart_name > size_stripchart &&
801       *size_stripchart_name != '/') size_stripchart_name--;
802     }
803   create_stripchart(stripchart_form_widget, size_stripchart_name);
804 #else
805   printf("Can't create size stripchart: statfs() function not available\n");
806 #endif
807   }
808
809 /* Now create the configured input/output stripcharts; note
810 the total number includes the queue stripchart. */
811
812 for (i = stripchart_varstart; i < stripchart_number; i++)
813   create_stripchart(stripchart_form_widget, stripchart_title[i]);
814
815 /* Next in vertical order come the Resize & Quit buttons */
816
817 quit_args[0].value = (XtArgVal) stripchart_form_widget;
818 quit_widget = XtCreateManagedWidget("quit", commandWidgetClass,
819   outer_form_widget, quit_args, XtNumber(quit_args));
820 XtAddCallback(quit_widget, "callback",  quitAction, NULL);
821
822 resize_args[0].value = (XtArgVal) stripchart_form_widget;
823 resize_args[1].value = (XtArgVal) quit_widget;
824 resize_widget = XtCreateManagedWidget("resize", commandWidgetClass,
825   outer_form_widget, resize_args, XtNumber(resize_args));
826 XtAddCallback(resize_widget, "callback",  resizeAction, NULL);
827
828 /* In the absence of log tailing, the quit widget is the one above the
829 queue listing. */
830
831 above_queue_widget = quit_widget;
832
833 /* Create an Ascii text widget for the log tail display if we are tailing a
834 log. Skip it if not. */
835
836 if (log_file[0] != 0)
837   {
838   log_args[0].value = (XtArgVal) quit_widget;
839   log_widget = XtCreateManagedWidget("log", asciiTextWidgetClass,
840     outer_form_widget, log_args, XtNumber(log_args));
841   XawTextDisplayCaret(log_widget, TRUE);
842   xs_SetValues(log_widget, 6,
843     "editType",  XawtextEdit,
844     "translations", text_trans,
845     "string",    log_display_buffer,
846     "length",    log_buffer_size,
847     "height",    log_depth,
848     "width",     log_width);
849
850   if (log_font != NULL)
851     {
852     XFontStruct *f = XLoadQueryFont(X_display, CS log_font);
853     if (f != NULL) xs_SetValues(log_widget, 1, "font", f);
854     }
855
856   above_queue_widget = log_widget;
857   }
858
859 /* The update button */
860
861 update_args[0].value = (XtArgVal) above_queue_widget;
862 update_widget = XtCreateManagedWidget("update", commandWidgetClass,
863   outer_form_widget, update_args, XtNumber(update_args));
864 XtAddCallback(update_widget, "callback",  updateAction, NULL);
865
866 /* The hide button */
867
868 hide_args[0].value = (XtArgVal) above_queue_widget;
869 hide_args[1].value = (XtArgVal) update_widget;
870 hide_widget = XtCreateManagedWidget("hide", commandWidgetClass,
871   outer_form_widget, hide_args, XtNumber(hide_args));
872 XtAddCallback(hide_widget, "callback",  hideAction, NULL);
873
874 /* Create an Ascii text widget for the queue display. */
875
876 queue_args[0].value = (XtArgVal) update_widget;
877 queue_widget = XtCreateManagedWidget("queue", asciiTextWidgetClass,
878   outer_form_widget, queue_args, XtNumber(queue_args));
879 XawTextDisplayCaret(queue_widget, TRUE);
880
881 xs_SetValues(queue_widget, 4,
882   "editType",  XawtextEdit,
883   "height",    queue_depth,
884   "width",     queue_width,
885   "translations", queue_trans);
886
887 if (queue_font != NULL)
888   {
889   XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
890   if (f != NULL) xs_SetValues(queue_widget, 1, "font", f);
891   }
892
893 /* Call the ticker function to get the initial data set up. It
894 arranges to have itself recalled every 2 seconds. */
895
896 ticker(NULL, NULL);
897
898 /* Everything is now set up; this flag is used by the regerror
899 function and also by the queue reader. */
900
901 eximon_initialized = TRUE;
902 printf("\nExim Monitor running\n");
903
904 /* Realize the toplevel and thereby get things displayed */
905
906 XtRealizeWidget(toplevel_widget);
907
908 /* Find out the size of the initial window, and set that as its
909 maximum. While we are at it, get the initial position. */
910
911 sizepos_args[0].value = (XtArgVal)(&maxwidth);
912 sizepos_args[1].value = (XtArgVal)(&maxheight);
913 sizepos_args[2].value = (XtArgVal)(&original_x);
914 sizepos_args[3].value = (XtArgVal)(&original_y);
915 XtGetValues(toplevel_widget, sizepos_args, 4);
916
917 xs_SetValues(toplevel_widget, 2,
918   "maxWidth",  maxwidth,
919   "maxHeight", maxheight);
920
921 /* Set up the size of the screen */
922
923 screenwidth = XDisplayWidth(X_display, 0);
924 screenheight= XDisplayHeight(X_display,0);
925
926 /* Register the action table */
927
928 XtAppAddActions(X_appcon, actionTable, actionTableSize);
929
930 /* Reduce the window to the small size if this is wanted */
931
932 if (start_small) resizeAction(NULL, NULL, NULL);
933
934 /* Enter the application loop which handles things from here
935 onwards. The return statement is never obeyed, but is needed to
936 keep pedantic ANSI compilers happy. */
937
938 XtAppMainLoop(X_appcon);
939
940 return 0;
941 }
942
943 /* End of em_main.c */
944