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