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