1 /*************************************************
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2021 - 2023 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
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. */
18 /*************************************************
20 *************************************************/
22 /* Fallback resources */
24 static String fallback_resources[] = {"eximon.geometry: +150+0", NULL};
26 /* X11 fixed argument lists */
28 static Arg quit_args[] = {
29 {XtNfromVert, (XtArgVal) NULL}, /* must be first */
30 {XtNlabel, (XtArgVal) " Quit "},
31 {"left", XawChainLeft},
32 {"right", XawChainLeft},
34 {"bottom", XawChainTop}
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},
44 {"bottom", XawChainTop}
47 static Arg update_args[] = {
48 {XtNfromVert, (XtArgVal) NULL}, /* must be first */
49 {XtNlabel, (XtArgVal) " Update "},
50 {"left", XawChainLeft},
51 {"right", XawChainLeft},
53 {"bottom", XawChainTop}
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},
63 {"bottom", XawChainTop}
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},
73 {"bottom", XawChainTop}
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},
85 {"bottom", XawChainTop}
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},
95 {"bottom", XawChainBottom}
98 static Arg sizepos_args[] = {
99 {"width", (XtArgVal)NULL},
100 {"height", (XtArgVal)NULL},
101 {"x", (XtArgVal)NULL},
102 {"y", (XtArgVal)NULL}
105 XtActionsRec menu_action_table[] = {
106 { "menu-create", menu_create } };
108 /* Types of non-message dialog action */
112 /* Miscellaneous local variables */
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;
133 #ifdef STRERROR_FROM_ERRLIST
134 /*************************************************
135 * Provide strerror() for non-ANSI libraries *
136 *************************************************/
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. */
145 if (n < 0 || n >= sys_nerr) return "unknown error number";
146 return sys_errlist[n];
148 #endif /* STRERROR_FROM_ERRLIST */
152 /*************************************************
153 * Handle attempts to write the log *
154 *************************************************/
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.
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
171 log_write(unsigned int selector, int flags, const char *format, ...)
174 va_start(ap, format);
175 vfprintf(stderr, format, ap);
176 fprintf(stderr, "\n");
183 /*************************************************
185 *************************************************/
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. */
191 static void sigchld_handler(int sig)
193 while (waitpid(-1, NULL, WNOHANG) > 0);
194 signal(sig, sigchld_handler);
195 if (action_queue_update) tick_queue_accumulator = 999999;
200 /*************************************************
201 * Callback routines *
202 *************************************************/
205 void updateAction(Widget w, XtPointer client_data, XtPointer call_data)
207 scan_spool_input(TRUE);
209 tick_queue_accumulator = 0;
212 void hideAction(Widget w, XtPointer client_data, XtPointer call_data)
214 actioned_message[0] = 0;
215 dialog_ref_widget = w;
216 dialog_action = da_hide;
217 create_dialog(US"Hide addresses ending with", US"");
220 void unhideAction(Widget w, XtPointer client_data, XtPointer call_data)
222 skip_item *sk = queue_skip;
226 skip_item *next = sk->next;
232 XtDestroyWidget(unhide_widget);
233 unhide_widget = NULL;
235 scan_spool_input(TRUE);
237 tick_queue_accumulator = 0;
240 void quitAction(Widget w, XtPointer client_data, XtPointer call_data)
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.
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.
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.
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. */
265 void resizeAction(Widget button, XtPointer client_data, XtPointer call_data)
268 Dimension width, height;
270 Window w = XtWindow(toplevel_widget);
272 /* Get the position and size of the top level widget. */
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);
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. */
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;
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. */
294 if (width == maxwidth && height == maxheight)
301 xs_SetValues(toplevel_widget, 4,
303 "height", min_height,
304 "x", minposx - ((x_adjustment >= 0)? x_adjustment : 0),
305 "y", minposy - ((y_adjustment >= 0)? y_adjustment : 0));
307 xs_SetValues(toplevel_widget, 2,
309 "height", min_height);
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. */
323 if (width == min_width && height == min_height)
330 if ((int)(x + maxwidth) > (int)screenwidth ||
331 (int)(y + maxheight + 10) > (int)screenheight)
335 xx = maxposx - ((x_adjustment >= 0)? x_adjustment : 0);
336 yy = maxposy - ((y_adjustment >= 0)? y_adjustment : 0);
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;
346 xs_SetValues(toplevel_widget, 4,
353 else xs_SetValues(toplevel_widget, 2,
355 "height", maxheight);
358 /* Ensure the window is at the top */
360 XRaiseWindow(X_display, w);
366 /*************************************************
367 * Handle input from non-msg dialogue *
368 *************************************************/
370 /* The various cases here are: hide domain, (no more yet) */
372 void NonMessageDialogue(uschar *s)
376 switch(dialog_action)
380 /* Create the unhide button if not present */
382 if (unhide_widget == NULL)
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);
391 /* Add item to skip queue */
393 sk = (skip_item *)store_malloc(sizeof(skip_item) + Ustrlen(s));
394 sk->next = queue_skip;
396 Ustrcpy(sk->text, s);
397 sk->reveal = time(NULL) + 60 * 60;
398 scan_spool_input(TRUE);
400 tick_queue_accumulator = 0;
407 /*************************************************
409 *************************************************/
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
415 static void ticker(XtPointer pt, XtIntervalId *i)
417 pipe_item **pp = &pipe_chain;
418 pipe_item *p = pipe_chain;
419 tick_queue_accumulator += tick_interval;
420 tick_stripchart_accumulator += tick_interval;
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. */
429 if (tick_queue_accumulator >= queue_update)
431 scan_spool_input(TRUE);
433 tick_queue_accumulator = 0;
434 if (tick_stripchart_accumulator >= stripchart_update)
435 tick_stripchart_accumulator = 0;
436 while (waitpid(-1, NULL, WNOHANG) > 0);
439 /* Otherwise, if we have exceeded the stripchart interval,
440 do a reduced queue scan that simply provides the count for
443 else if (tick_stripchart_accumulator >= stripchart_update)
445 scan_spool_input(FALSE);
446 tick_stripchart_accumulator = 0;
449 /* Scan any pipes that are set up for listening to delivery processes,
450 and display their output if their windows are still open. */
457 while ((count = read(p->fd, buffer, 254)) > 0)
460 if (p->widget != NULL) text_show(p->widget, buffer);
468 /* If configured, cause display update */
469 if (action_queue_update) tick_queue_accumulator = 999999;
472 else pp = &(p->next);
477 /* Reset the timer for next time */
479 XtAppAddTimeOut(X_appcon, tick_interval * 1000, ticker, 0);
484 /*************************************************
485 * Find Num Lock modifiers *
486 *************************************************/
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.
494 buf a buffer in which to put the answers (long enough to hold 5)
496 Returns: points to the buffer, or NULL
500 numlock_modifiers(Display *display, uschar *buf)
506 m = XGetModifierMapping(display);
509 printf("Not enough memory\n");
513 /* Look at Mod1 through Mod5, and fill in the buffer as necessary. */
516 for (i = 3; i < 8; i++)
518 for (j = 0; j < m->max_keypermod; j++)
520 if (XKeycodeToKeysym(display, m->modifiermap [i*m->max_keypermod + j], 0)
523 sprintf(CS(buf+Ustrlen(buf)), " Mod%d", i-2);
535 /*************************************************
537 *************************************************/
540 main(int argc, char **argv)
543 struct stat statdata;
544 uschar modbuf[] = " Mod1 Mod2 Mod3 Mod4 Mod5";
546 Widget stripchart_form_widget,
551 /* The exim global message_id needs to get set */
553 message_id_external = message_id_option + 1;
554 message_id = message_id_external + 1;
555 message_subdir[1] = 0;
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. */
562 big_buffer = store_get(big_buffer_size, GET_UNTAINTED);
564 /* Set up the version string and date and output them */
567 printf("\nExim Monitor version %s (compiled %s) initializing\n",
568 version_string, version_date);
570 /* Initialize various things from the environment and arguments. */
572 init(argc, USS argv);
574 /* Set up the SIGCHLD handler */
576 signal(SIGCHLD, sigchld_handler);
578 /* Get the buffer for storing the string for the log display. */
580 log_display_buffer = US store_malloc(log_buffer_size);
581 log_display_buffer[0] = 0;
583 /* Initialize the data structures for the stripcharts */
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
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
600 if (log_file[0] != 0)
602 /* Do *not* use "%s" here, we need the %D datestamp in the log_file to
604 (void)string_format(log_file_open, sizeof(log_file_open), CS log_file, NULL);
605 log_datestamping = string_datestamp_offset >= 0;
607 LOG = fopen(CS log_file_open, "r");
611 printf("*** eximon warning: can't open log file %s - will try "
612 "periodically\n", log_file_open);
616 fseek(LOG, 0, SEEK_END);
617 log_position = ftell(LOG);
618 if (fstat(fileno(LOG), &statdata))
620 perror("log file fstat");
625 log_inode = statdata.st_ino;
630 printf("*** eximon warning: no log file available to tail\n");
633 /* Now initialize the X world and create the top-level widget */
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);
645 /* Create the action for setting up the menu in the queue display
646 window, and register the action for positioning the menu. */
648 XtAppAddActions(X_appcon, menu_action_table, 1);
649 XawSimpleMenuAddGlobalActions(X_appcon);
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. */
661 <FocusIn>: display-caret(on)\n\
662 <FocusOut>: display-caret(off)\n\
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. */
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);
679 numlock = numlock_modifiers(X_display, modbuf); /* Get Num Lock modifier(s) */
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);
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\
699 queue_trans = XtParseTranslationTable(CS big_buffer);
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\
715 /* Create a toplevel form widget to hold all the other things */
717 outer_form_widget = XtCreateManagedWidget("form", formWidgetClass,
718 toplevel_widget, NULL, 0);
720 /* Now create an inner form to hold the stripcharts */
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,
729 "bottom", XawChainTop);
731 /* Create the queue count stripchart and its label. */
733 create_stripchart(stripchart_form_widget, queue_stripchart_name);
735 /* If configured, create the size monitoring stripchart, but
736 only if the OS supports statfs(). */
738 if (size_stripchart != NULL)
741 if (size_stripchart_name == NULL)
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--;
749 create_stripchart(stripchart_form_widget, size_stripchart_name);
751 printf("Can't create size stripchart: statfs() function not available\n");
755 /* Now create the configured input/output stripcharts; note
756 the total number includes the queue stripchart. */
758 for (i = stripchart_varstart; i < stripchart_number; i++)
759 create_stripchart(stripchart_form_widget, stripchart_title[i]);
761 /* Next in vertical order come the Resize & Quit buttons */
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);
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);
774 /* In the absence of log tailing, the quit widget is the one above the
777 above_queue_widget = quit_widget;
779 /* Create an Ascii text widget for the log tail display if we are tailing a
780 log. Skip it if not. */
782 if (log_file[0] != 0)
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,
796 if (log_font != NULL)
798 XFontStruct *f = XLoadQueryFont(X_display, CS log_font);
799 if (f != NULL) xs_SetValues(log_widget, 1, "font", f);
802 above_queue_widget = log_widget;
805 /* The update button */
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);
812 /* The hide button */
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);
820 /* Create an Ascii text widget for the queue display. */
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);
827 xs_SetValues(queue_widget, 4,
828 "editType", XawtextEdit,
829 "height", queue_depth,
830 "width", queue_width,
831 "translations", queue_trans);
833 if (queue_font != NULL)
835 XFontStruct *f = XLoadQueryFont(X_display, CS queue_font);
836 if (f != NULL) xs_SetValues(queue_widget, 1, "font", f);
839 /* Call the ticker function to get the initial data set up. It
840 arranges to have itself recalled every 2 seconds. */
844 /* Everything is now set up; this flag is used by the regerror
845 function and also by the queue reader. */
847 eximon_initialized = TRUE;
848 printf("\nExim Monitor running\n");
850 /* Realize the toplevel and thereby get things displayed */
852 XtRealizeWidget(toplevel_widget);
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. */
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);
863 xs_SetValues(toplevel_widget, 2,
864 "maxWidth", maxwidth,
865 "maxHeight", maxheight);
867 /* Set up the size of the screen */
869 screenwidth = XDisplayWidth(X_display, 0);
870 screenheight= XDisplayHeight(X_display,0);
872 /* Register the action table */
874 XtAppAddActions(X_appcon, actionTable, actionTableSize);
876 /* Reduce the window to the small size if this is wanted */
878 if (start_small) resizeAction(NULL, NULL, NULL);
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. */
884 XtAppMainLoop(X_appcon);
889 /* End of em_main.c */