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