Note about ratelimit resets.
[exim.git] / src / exim_monitor / em_main.c
CommitLineData
184e8823 1/* $Cambridge: exim/src/exim_monitor/em_main.c,v 1.5 2007/01/08 10:50:17 ph10 Exp $ */
059ec3d9
PH
2
3/*************************************************
4* Exim Monitor *
5*************************************************/
6
184e8823 7/* Copyright (c) University of Cambridge 1995 - 2007 */
059ec3d9
PH
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
14sets up the world and then lets the XtAppMainLoop function
15run things off X events. */
16
17
18/*************************************************
19* Static variables *
20*************************************************/
21
22/* Fallback resources */
23
24static String fallback_resources[] = {"eximon.geometry: +150+0", NULL};
25
26/* X11 fixed argument lists */
27
28static 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
37static 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
47static 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
56static 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
66static 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
76static 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
88static 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
98static Arg sizepos_args[] = {
99 {"width", (XtArgVal)NULL},
100 {"height", (XtArgVal)NULL},
101 {"x", (XtArgVal)NULL},
102 {"y", (XtArgVal)NULL}
103};
104
105XtActionsRec menu_action_table[] = {
106 { "menu-create", menu_create } };
107
108/* Types of non-message dialog action */
109
110enum { da_hide };
111
112/* Miscellaneous local variables */
113
114static int dialog_action;
115static int tick_stripchart_accumulator = 999999;
116static int tick_interval = 2;
117static int maxposset = 0;
118static int minposset = 0;
119static int x_adjustment = -1;
120static int y_adjustment = -1;
121static Dimension screenwidth, screenheight;
122static Dimension original_x, original_y;
123static Dimension maxposx, maxposy;
124static Dimension minposx, minposy;
125static Dimension maxwidth, maxheight;
126static Widget outer_form_widget;
127static Widget hide_widget;
128static 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()
139in their libraries, but can provide the same facility by this simple
140alternative function. */
141
142uschar *
143strerror(int n)
144{
145if (n < 0 || n >= sys_nerr) return "unknown error number";
146return 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
157utility. The message always gets '\n' added on the end of it. These calls come
158from modules such as store.c when things go drastically wrong (e.g. malloc()
159failing). In normal use they won't get obeyed.
160
161Arguments:
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
167Returns: nothing
168*/
169
170void
171log_write(unsigned int selector, int flags, char *format, ...)
172{
173va_list ap;
174va_start(ap, format);
175vfprintf(stderr, format, ap);
176fprintf(stderr, "\n");
177va_end(ap);
178selector = selector; /* Keep picky compilers happy */
179flags = 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
190dot and a port number. This function decodes this. It is needed by the
191spool-reading function, and copied here to avoid having to include the whole
192host.c module. One day the interaction between exim and eximon with regard to
193included code MUST be tidied up!
194
195Argument:
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
199Returns: 0 if there is no port, else the port number.
200*/
201
202int
7cd1141b 203host_address_extract_port(uschar *address)
059ec3d9
PH
204{
205int skip = -3; /* Skip 3 dots in IPv4 addresses */
206address--;
207while (*(++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 }
213if (*address == 0) return 0;
214*address++ = 0;
215return Uatoi(address);
216}
217
218
219
220
221/*************************************************
222* SIGCHLD handler *
223*************************************************/
224
225/* Operations on messages are done in subprocesses; this handler
226just catches them when they finish. It causes a queue display update
227unless configured not to. */
228
229static void sigchld_handler(int sig)
230{
231while (waitpid(-1, NULL, WNOHANG) > 0);
232signal(sig, sigchld_handler);
233if (action_queue_update) tick_queue_accumulator = 999999;
234}
235
236
237
238/*************************************************
239* Callback routines *
240*************************************************/
241
242
243void updateAction(Widget w, XtPointer client_data, XtPointer call_data)
244{
245w = w; /* Keep picky compilers happy */
246client_data = client_data;
247call_data = call_data;
248scan_spool_input(TRUE);
249queue_display();
250tick_queue_accumulator = 0;
251}
252
253void hideAction(Widget w, XtPointer client_data, XtPointer call_data)
254{
255w = w; /* Keep picky compilers happy */
256client_data = client_data;
257call_data = call_data;
258actioned_message[0] = 0;
259dialog_ref_widget = w;
260dialog_action = da_hide;
261create_dialog(US"Hide addresses ending with", US"");
262}
263
264void unhideAction(Widget w, XtPointer client_data, XtPointer call_data)
265{
266skip_item *sk = queue_skip;
267
268w = w; /* Keep picky compilers happy */
269client_data = client_data;
270call_data = call_data;
271
272while (sk != NULL)
273 {
274 skip_item *next = sk->next;
275 store_free(sk);
276 sk = next;
277 }
278queue_skip = NULL;
279
280XtDestroyWidget(unhide_widget);
281unhide_widget = NULL;
282
283scan_spool_input(TRUE);
284queue_display();
285tick_queue_accumulator = 0;
286}
287
288void quitAction(Widget w, XtPointer client_data, XtPointer call_data)
289{
290w = w; /* Keep picky compilers happy */
291client_data = client_data;
292call_data = call_data;
293exit(0);
294}
295
296
297/* Action when the "Size" button is pressed. This is a kludged up mess
298that I made work after much messing around. Reading the position of the
299toplevel widget gets the absolute position of the data portion of the window,
300excluding the window manager's furniture. However, positioning the toplevel
301widget's window seems to position the top corner of the furniture under the twm
302window manager, but not under fwvm and others. The two cases are distinguished
303by the values of x_adjustment and y_adjustment.
304
305For twm (adjustment >= 0), one has to fudge the miminizing function to ensure
306that we go back to exactly the same position as before.
307
308For fwvm (adjustment < 0), one has to fudge the "top left hand corner"
309positioning to ensure that the window manager's furniture gets displayed on the
310screen. I haven't found a way of discovering the thickness of the furniture, so
311some screwed-in values are used.
312
313This is all ad hoc, developed by floundering around as I haven't found any
314documentation that tells me what I really should do. */
315
316void resizeAction(Widget button, XtPointer client_data, XtPointer call_data)
317{
318Dimension x, y;
319Dimension width, height;
320XWindowAttributes a;
321Window w = XtWindow(toplevel_widget);
322
323button = button; /* Keep picky compilers happy */
324client_data = client_data;
325call_data = call_data;
326
327/* Get the position and size of the top level widget. */
328
329sizepos_args[0].value = (XtArgVal)(&width);
330sizepos_args[1].value = (XtArgVal)(&height);
331sizepos_args[2].value = (XtArgVal)(&x);
332sizepos_args[3].value = (XtArgVal)(&y);
333XtGetValues(toplevel_widget, sizepos_args, 4);
334
335/* Get the position of the widget's window relative to its parent; this
336gives the thickness of the window manager's furniture. At least it does
337in twm. For fwvm it gives zero. The size/movement function uses this data.
338I tried doing this before entering the main loop, but it didn't always
339work properly with twm. Running it every time seems to be OK. */
340
341XGetWindowAttributes(X_display, XtWindow(toplevel_widget), &a);
342if (a.x != 0) x_adjustment = a.x;
343if (a.y != 0) y_adjustment = a.y;
344
345/* If at maximum size, reduce to minimum and move back to where it was
346when maximized, if that value is set, allowing for the furniture in cases
347where the positioning includes the furniture. */
348
349if (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
368it was for coming back. If we don't have a value for the thickness of the
369furniture, the implication is that the coordinates position the application
370window, so we can't use (0,0) because that loses the furniture. Use screwed in
371values that seem to work with fvwm. */
372
373else
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
415XRaiseWindow(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
427void NonMessageDialogue(uschar *s)
428{
429skip_item *sk;
430
431switch(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
467values; it then sets a timeout so that it continues to be called
468every 2 seconds. */
469
470static void ticker(XtPointer pt, XtIntervalId *i)
471{
472pipe_item **pp = &pipe_chain;
473pipe_item *p = pipe_chain;
474tick_queue_accumulator += tick_interval;
475tick_stripchart_accumulator += tick_interval;
476read_log();
477
478pt = pt; /* Keep picky compilers happy */
479i = i;
480
481/* If we have passed the queue update time, we must do a full
482scan of the queue, checking for new arrivals, etc. This will
483as a by-product set the count of items for use by the stripchart
484display. On some systems, SIGCHLD signals can get lost at busy times,
485so just in case, clean up any completed children here. */
486
487if (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,
498do a reduced queue scan that simply provides the count for
499the stripchart. */
500
501else 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,
508and display their output if their windows are still open. */
509
510while (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
537XtAppAddTimeOut(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
547NULL if XK_Num_Lock doesn't generate any modifiers. This is needed because Num
548Lock isn't always the same modifier on all servers.
549
550Arguments:
551 display the Display
552 buf a buffer in which to put the answers (long enough to hold 5)
553
554Returns: points to the buffer, or NULL
555*/
556
557static uschar *
558numlock_modifiers(Display *display, uschar *buf)
559{
560XModifierKeymap *m;
561int i, j;
562uschar *ret = NULL;
563
564m = XGetModifierMapping(display);
565if (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
573buf[0] = 0;
574for (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
587XFreeModifiermap(m);
588return ret;
589}
590
591
592
593/*************************************************
594* Initialize *
595*************************************************/
596
597int main(int argc, char **argv)
598{
599int i;
600struct stat statdata;
601uschar modbuf[] = " Mod1 Mod2 Mod3 Mod4 Mod5";
602uschar *numlock;
603Widget stripchart_form_widget,
604 update_widget,
605 quit_widget,
606 resize_widget;
607
608/* The exim global message_id needs to get set */
609
610message_id_external = message_id_option + 1;
611message_id = message_id_external + 1;
612message_subdir[1] = 0;
613
614/* Some store needs getting for big_buffer, which is used for
615constructing file names and things. This call will initialize
616the store_get() function. */
617
618big_buffer_size = 1024;
619big_buffer = store_get(big_buffer_size);
620
621/* Set up the version string and date and output them */
622
623version_init();
624printf("\nExim Monitor version %s (compiled %s) initializing\n",
625 version_string, version_date);
626
627/* Initialize various things from the environment and arguments. */
628
629init(argc, USS argv);
630
631/* Set up the SIGCHLD handler */
632
633signal(SIGCHLD, sigchld_handler);
634
635/* Get the buffer for storing the string for the log display. */
636
637log_display_buffer = (uschar *)store_malloc(log_buffer_size);
638log_display_buffer[0] = 0;
639
640/* Initialize the data structures for the stripcharts */
641
642stripchart_init();
643
644/* If log_file contains the empty string, then Exim is running using syslog
645only, and we can't tail the log. If not, open the log file and position to the
646end of it. Before doing so, we have to detect whether the log files are
647datestamped, 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
649fact, we don't need to test explicitly - just process the string with
650string_format.
651
652Once opened, save the file's inode so that we can detect when the file is
653switched to another one for non-datestamped files. However, allow the monitor
654to start up without a log file (can happen if no messages have been sent
655today.) */
656
657if (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 }
677else
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
684toplevel_widget = XtAppInitialize(&X_appcon, "Eximon", NULL, 0, &argc, argv,
685 fallback_resources, NULL, 0);
686X_display = XtDisplay(toplevel_widget);
687xs_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
695window, and register the action for positioning the menu. */
696
697XtAppAddActions(X_appcon, menu_action_table, 1);
698XawSimpleMenuAddGlobalActions(X_appcon);
699
700/* Set up translation tables for the text widgets we use. We don't
701want all the generality of editing, etc. that the defaults provide.
702This cannot be done before initializing X - the parser complains
703about unknown events, modifiers, etc. in an unhelpful way... The
704queue text widget has a different table which includes the button
705for popping up the menu. Note that the order of things in these
706tables is significant. Shift<thing> must come before <thing> as
707otherwise 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
715result of MenuPopup(), but the grabs match only the exact modifiers listed,
716hence combinations with and without caps-lock and num-lock must be given,
717rather than just one "Shift<Btn1Down>" (or whatever menu_event is set to),
718despite the fact that that notation (without a leading !) should ignore the
719state of other modifiers. Thanks to Kevin Ryde for this information, and for
720the function above that discovers which modifier is Num Lock, because it turns
721out that it varies from server to server. */
722
723sprintf(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
728numlock = numlock_modifiers(X_display, modbuf); /* Get Num Lock modifier(s) */
729
730if (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
735sprintf(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
748queue_trans = XtParseTranslationTable(CS big_buffer);
749
750text_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
766outer_form_widget = XtCreateManagedWidget("form", formWidgetClass,
767 toplevel_widget, NULL, 0);
768
769/* Now create an inner form to hold the stripcharts */
770
771stripchart_form_widget = XtCreateManagedWidget("form", formWidgetClass,
772 outer_form_widget, NULL, 0);
773xs_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
782create_stripchart(stripchart_form_widget, queue_stripchart_name);
783
784/* If configured, create the size monitoring stripchart, but
785only if the OS supports statfs(). */
786
787if (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
805the total number includes the queue stripchart. */
806
807for (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
812quit_args[0].value = (XtArgVal) stripchart_form_widget;
813quit_widget = XtCreateManagedWidget("quit", commandWidgetClass,
814 outer_form_widget, quit_args, XtNumber(quit_args));
815XtAddCallback(quit_widget, "callback", quitAction, NULL);
816
817resize_args[0].value = (XtArgVal) stripchart_form_widget;
818resize_args[1].value = (XtArgVal) quit_widget;
819resize_widget = XtCreateManagedWidget("resize", commandWidgetClass,
820 outer_form_widget, resize_args, XtNumber(resize_args));
821XtAddCallback(resize_widget, "callback", resizeAction, NULL);
822
823/* In the absence of log tailing, the quit widget is the one above the
824queue listing. */
825
826above_queue_widget = quit_widget;
827
828/* Create an Ascii text widget for the log tail display if we are tailing a
829log. Skip it if not. */
830
831if (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
856update_args[0].value = (XtArgVal) above_queue_widget;
857update_widget = XtCreateManagedWidget("update", commandWidgetClass,
858 outer_form_widget, update_args, XtNumber(update_args));
859XtAddCallback(update_widget, "callback", updateAction, NULL);
860
861/* The hide button */
862
863hide_args[0].value = (XtArgVal) above_queue_widget;
864hide_args[1].value = (XtArgVal) update_widget;
865hide_widget = XtCreateManagedWidget("hide", commandWidgetClass,
866 outer_form_widget, hide_args, XtNumber(hide_args));
867XtAddCallback(hide_widget, "callback", hideAction, NULL);
868
869/* Create an Ascii text widget for the queue display. */
870
871queue_args[0].value = (XtArgVal) update_widget;
872queue_widget = XtCreateManagedWidget("queue", asciiTextWidgetClass,
873 outer_form_widget, queue_args, XtNumber(queue_args));
874XawTextDisplayCaret(queue_widget, TRUE);
875
876xs_SetValues(queue_widget, 4,
877 "editType", XawtextEdit,
878 "height", queue_depth,
879 "width", queue_width,
880 "translations", queue_trans);
881
882if (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
889arranges to have itself recalled every 2 seconds. */
890
891ticker(NULL, NULL);
892
893/* Everything is now set up; this flag is used by the regerror
894function and also by the queue reader. */
895
896eximon_initialized = TRUE;
897printf("\nExim Monitor running\n");
898
899/* Realize the toplevel and thereby get things displayed */
900
901XtRealizeWidget(toplevel_widget);
902
903/* Find out the size of the initial window, and set that as its
904maximum. While we are at it, get the initial position. */
905
906sizepos_args[0].value = (XtArgVal)(&maxwidth);
907sizepos_args[1].value = (XtArgVal)(&maxheight);
908sizepos_args[2].value = (XtArgVal)(&original_x);
909sizepos_args[3].value = (XtArgVal)(&original_y);
910XtGetValues(toplevel_widget, sizepos_args, 4);
911
912xs_SetValues(toplevel_widget, 2,
913 "maxWidth", maxwidth,
914 "maxHeight", maxheight);
915
916/* Set up the size of the screen */
917
918screenwidth = XDisplayWidth(X_display, 0);
919screenheight= XDisplayHeight(X_display,0);
920
921/* Register the action table */
922
923XtAppAddActions(X_appcon, actionTable, actionTableSize);
924
925/* Reduce the window to the small size if this is wanted */
926
927if (start_small) resizeAction(NULL, NULL, NULL);
928
929/* Enter the application loop which handles things from here
930onwards. The return statement is never obeyed, but is needed to
931keep pedantic ANSI compilers happy. */
932
933XtAppMainLoop(X_appcon);
934
935return 0;
936}
937
938/* End of em_main.c */
939