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