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