1 /* xmotd is a  message of the day displayer for X11 and dumb-terminals
2  *
3  * It displays a logo in the top-left corner, a 3-line title to its
4  * right and a text-widget (optionally a HTML widget) for displaying
5  * the message, just below. The motd filename(s) are supplied on the
6  * command-line. A single button is used to sequentailly access the
7  * motd(s) and to dismiss the browser when all the messages have been
8  * viewed. A label displays the time of the file being viewed and
9  * (optionally) the filename. It has options for automatically popping
10  * down w/o user intervention and other features which I can't be
11  * bothered to type-in here and would rather have you look at the
12  * man-page or http://www.ee.ryerson.ca:8080/~elf/xmotd.html
13  * */
14 
15 /* $Id: main.c,v 1.19 2003/02/14 00:35:03 elf Exp elf $ */
16 
17 /*
18  * Copyright 1993-97, 1999, 2003 Luis Fernandes <elf@ee.ryerson.ca>
19  *
20  * Permission to use, copy, hack, and distribute this software and its
21  * documentation for any purpose and without fee is hereby granted,
22  * provided that the above copyright notice appear in all copies and
23  * that both that copyright notice and this permission notice appear
24  * in supporting documentation.  This application is presented as is
25  * without any implied or written warranty.
26  *
27  * This program is free software; you can redistribute it and/or modify
28  * it under the terms of the GNU General Public License as published by
29  * the Free Software Foundation; either version 2 of the License, or
30  * (at your option) any later version.
31  *
32  * This program is distributed in the hope that it will be useful,
33  * but WITHOUT ANY WARRANTY; without even the implied warranty of
34  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
35  * GNU General Public License for more details.
36  *
37  * You should have received a copy of the GNU General Public License
38  * along with this program; if not, write to the Free Software
39  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
40  *
41  * libhtmlw is copyright (C) 1993, Board of Trustees of the University
42  * of Illinois. See the file libhtmlw/HTML.c for the complete text of
43  * the NCSA copyright.
44  */
45 
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <time.h>
49 
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <sys/time.h>
53 #include <utime.h>
54 #include <errno.h>
55 #include <fcntl.h>
56 
57 #if defined(HPUX)
58 
59 #include <ndir.h>
60 #define dirent  direct
61 
62 #else
63 
64 #include <dirent.h>
65 
66 #endif
67 
68 #include <X11/Intrinsic.h>
69 #include <X11/StringDefs.h>
70 #include <X11/Xmu/Editres.h>
71 
72 #ifdef HAVE_XPM
73 #include <X11/xpm.h>
74 #endif
75 
76 #ifdef MOTIF
77 
78 #include <Xm/Text.h>
79 #include <Xm/PushB.h>
80 #include <Xm/Form.h>
81 #include <Xm/Label.h>
82 #include <Xm/Separator.h>
83 
84 #else
85 
86 #include <X11/Shell.h>
87 #include <X11/Xaw/AsciiText.h>
88 #include <X11/Xaw/Command.h>
89 #include <X11/Xaw/Form.h>
90 #include <X11/Xaw/Label.h>
91 
92 #endif
93 
94 #ifdef HAVE_HTML
95 #include <HTML.h>
96 #endif
97 
98 #define MIN_SLEEP_PERIOD 60		/* in seconds */
99 #define HOURS_TO_SECS 3600.0
100 
101 #include "maindefs.h"
102 #include "appdefs.h"
103 #include "main.h"
104 
105 extern time_t motdChanged();
106 extern void nextMessage(Widget w, caddr_t call_data, caddr_t client_data);
107 extern void AnchorCallbackProc(Widget w, caddr_t call_data, caddr_t client_data); /* browser.c */
108 
109 extern void loadLogo(char *logo, Pixmap *icon_pixmap, Pixel fg, Pixel bg); /* in logo.c */
110 
111 /* fwd decls*/
112 XtActionProc reallyQuit(Widget w, XButtonEvent *ev);
113 void Quit(Widget w, caddr_t call_data, caddr_t client_data);
114 void printUsage(char *str);		/* usage.c */
115 int runSilentRunDeep(unsigned slumber);
116 unsigned getAlarmTime(float period);
117 messageptr freeMessage(messageptr msglist);
118 messageptr newMessage(char *file);
119 
120 XtAppContext app_con;
121 Widget topLevel;				/* the application widget*/
122 
123 Widget text, quit;
124 Widget info;
125 
126 XtIntervalId timer;             /* pop-down time-out ID */
127 
128 Pixmap icon_pixmap;
129 
130 app_res_t app_res;				/* application resources, in main.h */
131 
132 /* list of pointers to the messages that will actually be displayed */
133 messageptr msgslist;
134 
135 int gargc;						/* globally accessible argc */
136 char **gargv;					/* globally accessible argv*/
137 
138 char timeStamp[256];
139 
140 static int alreadyForked;		/* flag to remember if we are already
141 								   running in the background */
142 
143 #define offset(field)   XtOffset (struct _resources *, field)
144 
145 static XtResource resources[] = {
146   { "always","Always",XtRInt, sizeof(int),
147 	  offset(always),XtRString, "0"},
148 
149   { "popdown","Popdown",XtRInt, sizeof(int),
150 	  offset(pto),XtRString, "0"},
151   { "usedomains","UseDomains",XtRInt, sizeof(int),
152 	  offset(usedomains),XtRString, "0"},
153   { "showfilename","ShowFilename",XtRInt, sizeof(int),
154 	  offset(showfilename),XtRString, "0"},
155   { "paranoid","Paranoid",XtRInt, sizeof(int),
156 	  offset(paranoid),XtRString, "0"},
157   { "tail","Tail",XtRInt, sizeof(int),
158 	offset(tail),XtRString, "0"},
159   { "bitmaplogo","BitmapLogo",XtRString, sizeof(String),
160 	  offset(logo),XtRString, NULL},
161   { "warnfile","Warnfile",XtRString, sizeof(String),
162 	  offset(warnfile),XtRString, NULL},
163   { "wakeup","Wakeup",XtRFloat, sizeof(float),
164 	  offset(periodic),XtRString, "0"},
165   { "stampfile","Stampfile",XtRString, sizeof(String),
166 	  offset(stampfile),XtRString, TIMESTAMP},
167   { "atom","Atom",XtRString, sizeof(String),
168 	  offset(atomname),XtRString, ATOM},
169 #ifdef HAVE_HTML
170   { "browser","Browser",XtRString, sizeof(String),
171 	  offset(browser),XtRString, BROWSER},
172 #endif
173 };
174 
175 static XrmOptionDescRec options[] = {
176   { "-always",       "always",       XrmoptionNoArg,  "1"},
177   { "-showfilename", "showfilename", XrmoptionNoArg,  "1"},
178   { "-usedomains",   "usedomains",   XrmoptionNoArg,  "1"},
179   { "-paranoid",     "paranoid",     XrmoptionNoArg,  "1"},
180   { "-tail",         "tail",         XrmoptionNoArg,  "1"},
181   { "-popdown",      "popdown",      XrmoptionSepArg, (caddr_t) NULL},
182   { "-bitmaplogo",   "bitmaplogo",   XrmoptionSepArg, (caddr_t) NULL},
183   { "-warnfile",     "warnfile",     XrmoptionSepArg, (caddr_t) NULL},
184   { "-wakeup",       "wakeup",       XrmoptionSepArg, (caddr_t) NULL},
185   { "-stampfile",    "stampfile",    XrmoptionSepArg, (caddr_t) NULL},
186   { "-atom",         "atom",         XrmoptionSepArg, (caddr_t) NULL},
187 #ifdef HAVE_HTML
188   { "-browser",      "browser",      XrmoptionSepArg, (caddr_t) NULL},
189 #endif
190 };
191 
192 /* You can use shift + btn1 to quit xmotd (when run with -wakeup)*/
193 static char shift1Trans[]="#override\n\
194         Shift<Btn1Down>,Shift<Btn1Up>: reallyquit()";
195 
196 static XtActionsRec xlations[]={
197         {"reallyquit", (XtActionProc) reallyQuit},
198 };
199 
200 /* when the text widget is mapped and we are in "tail" mode, scroll to
201    the end of the file*/
202 static char tailTrans[]="#override\n\
203         <Expose>: end-of-file()";
204 
205 char *
getTimeStampName()206 getTimeStampName()
207 {
208   static char buf[256];
209 
210   sprintf(buf, "%s/%s", getenv("HOME"), app_res.stampfile);
211 
212   if(app_res.usedomains)
213 	{
214 	  char domainame[256];
215 
216 	  getdomainname(domainame, 256);
217 
218 	  strcat(buf, ".");
219 	  strcat(buf, domainame);
220 	}
221 
222   return(buf);
223 
224 }
225 
226 /* convert user-specified wakeup time to seconds (argument to sleep)*/
227 unsigned
getAlarmTime(float period)228 getAlarmTime(float period)
229 {
230   unsigned slumber=(period*HOURS_TO_SECS);
231 
232   /*  fprintf(stderr,"slumber=%ld\n", slumber);*/
233 
234   /* limit sleep to 60sec*/
235   return((slumber<MIN_SLEEP_PERIOD)?MIN_SLEEP_PERIOD:slumber);
236 
237 }
238 
239 /* NOTE: Jul 17 1996: This code doesn't work accurately for some
240    reason; the mod-time fo a file doesn't matchup with the time
241    returned by 'date' (this is on 4.1.3_U1). This is only a problem if
242    the wakeup period is something like 1 minute (which I use for
243    testing) -elf*/
244 
245 void
updateTimeStamp(char * motdfile)246 updateTimeStamp(char *motdfile)
247 {
248   struct utimbuf ut;
249   time_t now;
250 
251 /*   fprintf(stderr, "Updating timestamp %s now.\n", getTimeStampName() );  */
252 
253   now = time((time_t *)NULL);
254 
255   ut.actime = now;
256   ut.modtime = now;
257 
258   if(utime(motdfile,&ut))
259 	{
260 	  extern int errno;
261 
262 	  if(errno==ENOENT)			/* if the file does not (1st time),
263 								   create it */
264 		{
265 		  FILE *fp;
266 
267 		  if((fp=fopen(motdfile,"w"))==NULL){
268 			perror("xmotd");
269 		  }
270 
271 		  fclose(fp);
272 
273 		}
274 	  else						/* some other problem */
275 		{
276 		  perror("xmotd");
277 		  exit(1);
278 		}
279 	}
280 
281 }
282 
283 
284 
285 XtActionProc
reallyQuit(Widget w,XButtonEvent * ev)286 reallyQuit(Widget w, XButtonEvent *ev )
287 {
288   extern char *txtbuf;
289 
290   if(txtbuf) free(txtbuf);
291 
292   if(icon_pixmap)
293 	{
294 	  XFreePixmap(XtDisplay(topLevel), icon_pixmap);
295 	}
296 
297   XtDestroyApplicationContext(app_con);
298 
299 /*  fprintf(stderr,"Bye.\n");*/
300   exit(0);
301 
302 }
303 
304 
305 void
306 /*ARGSUSED*/
Quit(Widget w,caddr_t call_data,caddr_t client_data)307 Quit(Widget w, caddr_t call_data, caddr_t client_data)
308 {
309   extern char *txtbuf;
310 
311   if(!app_res.periodic)
312 	{
313 	  /* we can exit because -wakeup is not specified*/
314 	  reallyQuit((Widget)NULL, (XButtonEvent *)NULL );
315 	}
316   else
317 	{
318 	  extern void revisitMessagesAndDisplay(int);
319 
320 	  XUnmapWindow(XtDisplay(topLevel), XtWindow(topLevel));
321 	  XFlush(XtDisplay(topLevel));
322 
323 	  if(!alreadyForked)
324 		{
325 		  if(fork()) exit(0);
326 
327 		  alreadyForked=1;
328 		}
329 
330 	  revisitMessagesAndDisplay(runSilentRunDeep(getAlarmTime(app_res.periodic)));
331 
332 	}
333 
334 }
335 
336 messageptr
freeMessage(messageptr msglist)337 freeMessage(messageptr msglist)
338 {
339   messageptr oldmsg;
340   oldmsg = msglist;
341   msglist = msglist->next;
342 
343   free(oldmsg->file);
344   free(oldmsg);
345   return(msglist);
346 
347 } /*freeMessage*/
348 
349 messageptr
newMessage(char * file)350 newMessage(char *file)
351 {
352   messageptr newmsg;
353 
354   if (!(newmsg = (messageptr)malloc(sizeof(struct messagenode))))
355 	{
356 	  perror("xmotd");
357 	  exit (2);					/*problems, probably no ram HA  */
358 	} /*if*/
359 
360   newmsg->file=(char *)calloc(1,(strlen(file)+1)*sizeof(char));
361 
362   if(!newmsg->file)
363 	{
364 	  perror("xmotd");
365 	  exit(2);
366 	}
367 
368   strcpy(newmsg->file, file);
369   newmsg->next = NULL;
370   return(newmsg);
371 
372 } /*newMessage*/
373 
374 
375 int
numFilesToDisplay(int argc,char ** argv)376 numFilesToDisplay(int argc, char **argv)
377 {
378   register int numsg=0, i;
379   struct stat motdstat;
380   messageptr newmsg, currentmsg = msgslist;
381 
382   /* i starts at 1 since argv[0] is the program name*/
383   for(i=1; i<argc; i++)
384 	{
385       stat(argv[i], &motdstat);
386 
387 	  if(motdstat.st_mode & S_IFDIR) /* it's a directory */
388 		{
389 		  DIR *dir;
390 		  struct dirent *dp;
391 		  char name[BUFSIZ];
392 
393 		  if ((dir = opendir(argv[i])))
394 			{
395 			  while (dp = readdir(dir))
396 				{
397 				  if (dp->d_ino == 0)
398 					continue;
399 				  if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
400 					continue;
401 				  strcpy(name, argv[i]);
402 
403 				  if (name[strlen(name) - 1] != '/')
404 					strcat(name, "/");
405 				  strcat(name, dp->d_name);
406 
407 				  if (access(name, 0) < 0)
408 					continue;
409 
410 				  if(motdChanged(name, timeStamp) || app_res.always)
411 					{
412 					  newmsg = newMessage(name);
413 
414 					  if (!currentmsg) /* first in list */
415 						msgslist = newmsg;
416 					  else
417 						currentmsg->next = newmsg;
418 
419 					  currentmsg = newmsg;
420 					  numsg++;
421 					} /*if motdchanged*/
422 
423                 } /*while*/
424 
425               closedir(dir);
426             } /*if opendir*/
427         }
428       else
429         if(motdChanged(argv[i], timeStamp) || app_res.always)
430 		  {
431             newmsg = newMessage(argv[i]);
432 
433             if (!currentmsg)	/* first in list */
434               msgslist = newmsg;
435             else
436               currentmsg->next = newmsg;
437 
438             currentmsg = newmsg;
439             numsg++;
440           }
441     }
442 
443   if((numsg || app_res.paranoid || app_res.always) && app_res.warnfile)
444 	{
445       newmsg = newMessage(app_res.warnfile);
446       newmsg->next = msgslist;
447       msgslist = newmsg;
448       numsg++;
449     } /*if*/
450 
451   return numsg;
452 
453 }/* numFilesToDisplay*/
454 
455 int
runSilentRunDeep(unsigned slumber)456 runSilentRunDeep(unsigned slumber)
457 {
458   int numsg=0;
459 
460   while(1)
461 	{
462 	  int fd;
463 
464 	  /*	  fprintf(stderr, "Going to sleep! (Zzzzzzzz...)\n");*/
465 	  sleep(slumber);
466 	  /*	  fprintf(stderr, "Waking-up! (Yawn...)\n");*/
467 
468 	  /* first thing we do after waking up is to see if the user is
469          still logged-in*/
470 	  if((fd=open("/dev/console", O_RDONLY, O_RDONLY)<0))
471 		{
472 		  /* since xmotd can't read from the console we assume user
473              has logged-out, so we exit*/
474 		  exit(0);
475 		}
476 
477 	  close(fd);
478 
479 	  /* next check if any messages need to be displayed, if there
480          aren't any, go back to sleep; otherwise return to display
481          messages*/
482 	  if(numsg=numFilesToDisplay(gargc, gargv)) return(numsg);
483 	}
484 
485 }
486 
487 
main(argc,argv)488 main(argc, argv)
489 int argc;
490 char **argv;
491 {
492   extern Boolean atomExists(String);
493   Display *display;
494   register int i, start=0;
495   int numsg;
496 
497 
498   if ((argc > 1) && !(strcmp(argv[1],"-help")))
499 	{
500 	  printUsage(argv[0]);		/* and exit */
501 	}
502 
503   /* Test to see whether we are connected to an X display. If we
504 	 aren't, we proceed in text-only mode: bare-bones functionality;
505 	 output to stdout.  Why bare-bones, I hear you asking? Well, X
506 	 does all the command-line options parsing for me and I don't feel
507 	 like duplicating all that code. So there.*/
508 
509   if((display=XOpenDisplay((char *)NULL))==NULL)
510 	{
511 
512 	  if(argc<2)
513 		{
514 		  fprintf(stderr, "xmotd: ERROR, missing file.\n");
515 		  printUsage(argv[0]);	/* and exit */
516 		}
517 	  else
518 		{
519 		  extern void runInTextMode();
520 		  runInTextMode(argc, argv); /* ...and exit... */
521 		}
522 
523 	  fprintf(stderr,"Never gets here!\n");
524 	  exit(0);				/* just in case */
525 
526 	}
527   else
528 	{
529 	  XCloseDisplay(display);
530 	}
531 
532   /* we have to init the toolkit *before* we check the command-line so
533      we can use X's parsing routines, since -geom options, etc. may be
534      specified, in which case, the motd-filename is *not* the 2nd
535      argument*/
536   topLevel = XtVaAppInitialize(&app_con, "XMotd", options,
537 							   XtNumber(options),
538 							   &argc, argv, fallback_resources,
539 							   NULL);
540 
541   XtGetApplicationResources(topLevel, (caddr_t) &app_res,
542 							resources, XtNumber(resources),
543 							(ArgList) NULL, (Cardinal) 0);
544 
545   if(argc<2)
546 	{
547 	  fprintf(stderr,"xmotd: ERROR, missing file\n");
548 	  printUsage(argv[0]);	/* and exit */
549 	}
550 
551   if(app_res.paranoid && !app_res.warnfile)
552 	{
553 	  fprintf(stderr,"xmotd: ERROR, specified \"-paranoid\" without \"-warnfile\"\n");
554 	  printUsage(argv[0]);	/* and exit */
555 	}
556 
557   strcpy(timeStamp, getTimeStampName());
558 
559   gargc=argc;
560   gargv=argv;
561 
562   /* first figure out how many of the files supplied on the
563      command-line we will be actually displaying; i.e. we only show
564      the new ones (unless -always has been specified, in which case we
565      show all of them)*/
566   numsg=numFilesToDisplay(argc, argv);
567 
568   if(!app_res.periodic && !numsg)
569 	{
570 	  /* if none of the messages need to be displayed and -wakeup not
571 	  specified */
572 
573 	  XtDestroyApplicationContext(app_con);
574 	  exit(0);
575 	}
576 
577   if(app_res.periodic)			/*-wakeup or -timeout specified*/
578 	{
579 
580 	  /*ensure no other copies of xmotd are running*/
581 	  if(atomExists(app_res.atomname)){
582 		XtDestroyApplicationContext(app_con);
583 		exit(0);
584 	  }
585 
586 	  if(fork()) exit(0);		/*we have to daemonize ourselves*/
587 	  alreadyForked=1;			/* make a note of it */
588 
589 	  if(!numsg)
590 		{
591 		  /* if no messages to be displayed, we sleep */
592 		  numsg=runSilentRunDeep(getAlarmTime(app_res.periodic));
593 		}
594 
595 	}
596 
597   createWidgets(numsg);
598   nextMessage((Widget)NULL, (caddr_t)NULL, (caddr_t)NULL);
599 
600   XtAddEventHandler(topLevel, (EventMask)0, True,
601 					(XtEventHandler)_XEditResCheckMessages, 0);
602 
603   XtRealizeWidget(topLevel);
604   XtAppMainLoop(app_con);
605 }
606 
607 
createWidgets(int anymsg)608 createWidgets(int anymsg)
609 {
610   Widget form, paned, logo, mlabel, hline;
611   XtTranslations shift1TransTable, tailTransTable;
612   Pixel fg, bg;
613   Arg args[8];
614   int n;
615 
616 #ifdef MOTIF
617   XmString xmstr;
618 
619   form=XtVaCreateManagedWidget("form", xmFormWidgetClass,topLevel,
620 							   NULL);
621 #else
622 
623   form=XtVaCreateManagedWidget("form", formWidgetClass,topLevel,
624 							   XtNresizable, True,
625 							   NULL);
626 #endif /* ifdef MOTIF */
627 
628   XtVaGetValues(form,
629 				XtNbackground, &bg,
630 				XtNforeground, &fg,
631 				NULL);
632 
633   loadLogo(app_res.logo, &icon_pixmap, fg, bg);
634 
635 #ifdef MOTIF
636   logo=XtVaCreateManagedWidget("logo", xmLabelWidgetClass, form,
637 							   XmNleftAttachment, XmATTACH_FORM,
638 							   XmNtopAttachment, XmATTACH_FORM,
639 							   XmNlabelType, XmPIXMAP,
640 							   XmNlabelPixmap, icon_pixmap,
641 							   XmNborderWidth, 0,
642 							   NULL);
643 
644   mlabel=XtVaCreateManagedWidget("title", xmLabelWidgetClass, form,
645 								 XmNleftAttachment, XmATTACH_WIDGET,
646  								 XmNleftWidget, logo,
647 								 XmNrightAttachment, XmATTACH_FORM,
648 								 NULL);
649 
650   hline=XtVaCreateManagedWidget("hline", xmSeparatorWidgetClass, form,
651 								 XmNtopAttachment, XmATTACH_WIDGET,
652 								 XmNtopWidget, logo,
653 								 NULL);
654 
655   quit = XtVaCreateManagedWidget("quit", xmPushButtonWidgetClass, form,
656 								 XmNtopAttachment, XmATTACH_WIDGET,
657 								 XmNtopWidget, logo,
658 								 XmNlabelType, XmSTRING,
659 
660 								 XmNtraversalOn, False, /* remove
661 								 Dismiss button from the Tab group;
662 								 comment this line out if YOU WANT
663 								 "Space-bar" to activate the dismiss
664 								 button*/
665 								 NULL);
666 
667   info=XtVaCreateManagedWidget("info", xmLabelWidgetClass, form,
668 							   XmNleftAttachment, XmATTACH_FORM,
669 							   XmNtopAttachment, XmATTACH_WIDGET,
670 							   XmNtopWidget, quit,
671 							   XmNlabelType, XmSTRING,
672 							   NULL);
673 
674 #ifdef HAVE_HTML
675 
676    n = 0;
677   XtSetArg(args[n],XmNtopAttachment, XmATTACH_WIDGET); n++;
678   XtSetArg(args[n],XmNtopWidget, info); n++;
679   XtSetArg(args[n],XmNbottomAttachment, XmATTACH_FORM); n++;
680 
681   text = XtVaCreateManagedWidget("text", htmlWidgetClass, form,NULL);
682 
683   XtSetValues(text,args,n);
684 
685   XtManageChild(text);
686 
687 #else
688 
689   n=0;
690   XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
691   XtSetArg(args[n], XmNeditable, False); n++;
692   XtSetArg(args[n], XmNscrollHorizontal, False); n++;
693   XtSetArg(args[n], XmNscrollLeftSide, True); n++;
694   XtSetArg(args[n], XmNcursorPositionVisible, False); n++;
695 
696   text=XmCreateScrolledText(form, "text", args, n);
697   n=0;
698 
699   XtVaSetValues(XtParent(text),
700 				XmNtopAttachment, XmATTACH_WIDGET,
701 				XmNtopWidget, info,
702 				XmNbottomAttachment, XmATTACH_FORM,
703 				NULL);
704 
705   XtManageChild(text);
706 #endif /* ifdef HAVE_HTML & ifdef MOTIF */
707 
708 #else
709 
710   logo=XtVaCreateManagedWidget("logo", labelWidgetClass, form,
711 								XtNleft, XtChainLeft,
712 								XtNright, XtChainLeft,
713 								XtNbitmap, icon_pixmap,
714 								NULL);
715 
716   mlabel=XtVaCreateManagedWidget("title", labelWidgetClass, form,
717 								 XtNfromHoriz, logo,
718 								 NULL);
719 
720   hline=XtVaCreateManagedWidget("hline", labelWidgetClass, form,
721 								 XtNfromVert, logo,
722 								 XtNfromVert, mlabel,
723 								 XtNlabel, (char *)NULL,
724 								 NULL);
725 
726   quit = XtVaCreateManagedWidget("quit", commandWidgetClass, form,
727 								  XtNleft, XtChainLeft,
728 								  XtNright, XtChainLeft,
729 								  XtNfromVert, logo,
730 								  NULL);
731 
732   info=XtVaCreateManagedWidget("info", labelWidgetClass, form,
733 							   XtNright, XtChainLeft,
734 							   XtNfromVert, quit,
735 							   XtNresizable, True,
736 							   NULL);
737 
738 #ifdef HAVE_HTML
739   text = XtVaCreateManagedWidget("text",
740 								 htmlWidgetClass, form,
741 								 XtNfromVert, info,
742 								 XtNwidth, 680,
743 								 XtNheight, 500,
744 								 NULL);
745 
746 #else
747   text = XtVaCreateManagedWidget("text",
748 								 asciiTextWidgetClass, form,
749 								 XtNfromVert, info,
750 								 NULL);
751 
752 #endif /* ifdef HAVE_HTML */
753 
754   if(app_res.tail)
755 	{
756 	  tailTransTable=XtParseTranslationTable(tailTrans);
757 	  XtOverrideTranslations(text, tailTransTable);
758 	}
759 
760 #endif /* ifdef MOTIF */
761 
762   if((anymsg>1))
763 	{
764 #ifdef MOTIF
765 	  xmstr=XmStringCreateLocalized(NEXT_MESSAGE_LABEL);
766 	  XtVaSetValues(quit, XmNlabelString, xmstr, NULL);
767 	  XmStringFree(xmstr);
768 
769 	  XtAddCallback(quit, XmNactivateCallback, (XtCallbackProc)nextMessage, 0);
770 #else
771 	  XtAddCallback(quit, XtNcallback, (XtCallbackProc)nextMessage,(caddr_t)0);
772 	  XtVaSetValues(quit, XtNlabel, NEXT_MESSAGE_LABEL, NULL);
773 #endif
774   	  if(app_res.pto)
775 	    timer=XtAppAddTimeOut(app_con, (unsigned long)(app_res.pto*1000),
776 			(XtTimerCallbackProc)nextMessage, (caddr_t) NULL);
777 	}
778   else
779 	{
780 #ifdef MOTIF
781 	  xmstr=XmStringCreateLocalized(LAST_MESSAGE_LABEL);
782 	  XtVaSetValues(quit, XmNlabelString, xmstr, NULL);
783 	  XmStringFree(xmstr);
784 
785 	  XtAddCallback(quit, XmNactivateCallback, (XtCallbackProc)Quit, 0);
786 #else
787 	  XtAddCallback(quit, XtNcallback, (XtCallbackProc)Quit, 0);
788 	  XtVaSetValues(quit, XtNlabel, LAST_MESSAGE_LABEL, NULL);
789 #endif
790   	  if(app_res.pto)
791 	    timer=XtAppAddTimeOut(app_con, (unsigned long)(app_res.pto*1000),
792 			(XtTimerCallbackProc)Quit, (caddr_t) NULL);
793 	}
794 
795 #ifdef HAVE_HTML
796 	  XtAddCallback(text, WbNanchorCallback,
797 					(XtCallbackProc)AnchorCallbackProc,(caddr_t)0);
798 #endif
799 
800   XtAppAddActions(app_con, xlations, XtNumber(xlations));
801   shift1TransTable=XtParseTranslationTable(shift1Trans);
802   XtOverrideTranslations(quit, shift1TransTable);
803 
804 }/* createWidgets*/
805 
806