misc.c revision 8dd211ca
1/* $XConsortium: misc.c,v 1.31 94/12/16 21:36:53 gildea Exp $ */
2/* $XdotOrg: xc/programs/xman/misc.c,v 1.6 2004/09/02 08:40:33 kem Exp $ */
3/*
4
5Copyright (c) 1987, 1988  X Consortium
6
7Permission is hereby granted, free of charge, to any person obtaining
8a copy of this software and associated documentation files (the
9"Software"), to deal in the Software without restriction, including
10without limitation the rights to use, copy, modify, merge, publish,
11distribute, sublicense, and/or sell copies of the Software, and to
12permit persons to whom the Software is furnished to do so, subject to
13the following conditions:
14
15The above copyright notice and this permission notice shall be included
16in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
22OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
23ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24OTHER DEALINGS IN THE SOFTWARE.
25
26Except as contained in this notice, the name of the X Consortium shall
27not be used in advertising or otherwise to promote the sale, use or
28other dealings in this Software without prior written authorization
29from the X Consortium.
30
31*/
32/* $XFree86: xc/programs/xman/misc.c,v 1.10 2003/08/02 17:35:48 herrb Exp $ */
33
34/*
35 * xman - X window system manual page display program.
36 * Author:    Chris D. Peterson, MIT Project Athena
37 * Created:   October 27, 1987
38 */
39
40#ifdef HAVE_CONFIG_H
41# include "config.h"
42#endif
43
44#include "globals.h"
45#include "vendor.h"
46#include <X11/Xos.h> 		/* sys/types.h and unistd.h included in here */
47#include <sys/stat.h>
48#include <errno.h>
49#include <X11/Xaw/Dialog.h>
50#include <X11/Shell.h>
51
52static FILE * Uncompress(ManpageGlobals * man_globals, char * filename);
53#ifndef HAS_MKSTEMP
54static Boolean UncompressNamed(ManpageGlobals * man_globals, char * filename,
55			       char * output);
56static Boolean UncompressUnformatted(ManpageGlobals * man_globals,
57				     char * entry, char * filename);
58#else
59static Boolean UncompressNamed(ManpageGlobals * man_globals, char * filename,
60			       char * output, FILE ** output_fd);
61static Boolean UncompressUnformatted(ManpageGlobals * man_globals,
62				     char * entry, char * filename,
63				     FILE **file);
64#endif
65#ifdef HANDLE_ROFFSEQ
66static Boolean ConstructCommand(char * cmdbuf, char * path, char * filename, char * tempfile);
67#endif
68
69#if defined(ISC) || defined(__SCO__) || defined(__UNIXWARE__)
70static char *uncompress_format = NULL;
71static char *uncompress_formats[] =
72      {  UNCOMPRESS_FORMAT_1,
73         UNCOMPRESS_FORMAT_2,
74         UNCOMPRESS_FORMAT_3
75      };
76#endif
77
78/*	Function Name: PopupWarning
79 *	Description: This function pops upa warning message.
80 *	Arguments: string - the specific warning string.
81 *	Returns: none
82 */
83
84extern Widget top;
85static Widget warnShell, warnDialog;
86
87static void
88PopdownWarning(Widget w, XtPointer client, XtPointer call)
89{
90  XtPopdown((Widget)client);
91}
92
93void
94PopupWarning(ManpageGlobals * man_globals, char * string)
95{
96  int n;
97  Arg wargs[3];
98  Dimension topX, topY;
99  char buffer[BUFSIZ];
100  Boolean hasPosition;
101
102  snprintf( buffer, sizeof(buffer), "Xman Warning: %s", string);
103  hasPosition = FALSE;
104  if (top)
105  {
106    n=0;
107    XtSetArg(wargs[n], XtNx, &topX); n++;
108    XtSetArg(wargs[n], XtNy, &topY); n++;
109    XtGetValues(top, wargs, n);
110    hasPosition = TRUE;
111  }
112
113  if (man_globals != NULL)
114    ChangeLabel(man_globals->label, buffer);
115  if (man_globals->label == NULL) {
116    n=0;
117    if (hasPosition)
118    {
119      XtSetArg(wargs[n], XtNx, topX); n++;
120      XtSetArg(wargs[n], XtNy, topY); n++;
121    }
122    XtSetArg(wargs[n], XtNtransientFor, top); n++;
123    warnShell = XtCreatePopupShell("warnShell", transientShellWidgetClass,
124				   initial_widget, wargs, n);
125    XtSetArg(wargs[0], XtNlabel, buffer);
126    warnDialog = XtCreateManagedWidget("warnDialog", dialogWidgetClass,
127				       warnShell, wargs, 1);
128    XawDialogAddButton(warnDialog, "dismiss", PopdownWarning,
129		       (XtPointer)warnShell);
130    XtRealizeWidget(warnShell);
131    Popup(warnShell, XtGrabNone);
132  }
133}
134
135/*	Function Name: PrintError
136 *	Description: This Function prints an error message and exits.
137 *	Arguments: string - the specific message.
138 *	Returns: none. - exits tho.
139 */
140
141void
142PrintError(char * string)
143{
144  fprintf(stderr,"Xman Error: %s\n",string);
145  exit(EXIT_FAILURE);
146}
147
148/*	Function Name: OpenFile
149 *	Description: Assignes a file to the manpage.
150 *	Arguments: man_globals - global structure.
151 *                 file        - the file pointer.
152 *	Returns: none
153 */
154
155void
156OpenFile(ManpageGlobals * man_globals, FILE * file)
157{
158  Arg arglist[1];
159  Cardinal num_args = 0;
160
161  if (man_globals->curr_file) {
162#if 0 /* Ownership rules need to be fixed first */
163    fclose(man_globals->curr_file);
164#endif
165  }
166  man_globals->curr_file = file;
167
168  XtSetArg(arglist[num_args], XtNfile, man_globals->curr_file); num_args++;
169  XtSetValues(man_globals->manpagewidgets.manpage, arglist, num_args);
170}
171
172
173/*	Function Name: FindManualFile
174 *	Description: Opens the manual page file given the entry information.
175 *	Arguments: man_globals - the globals info for this manpage.
176 *                 section_num - section number of the man page.
177 *                 entry_num   - entry number of the man page.
178 *	Returns: fp - the file pointer
179 *
180 * NOTES:
181 *
182 * If there is a uncompressed section it will look there for uncompresed
183 * manual pages first and then for individually compressed file in the
184 * uncompressed section.
185 *
186 * If there is a compressed directory then it will also look there for
187 * the manual pages.
188 *
189 * If both of these fail then it will attempt to format the manual page.
190 */
191
192FILE *
193FindManualFile(ManpageGlobals * man_globals, int section_num, int entry_num)
194{
195  FILE * file;
196  char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], *temp;
197  char filename[BUFSIZ];
198  char * entry = manual[section_num].entries[entry_num];
199  int len_cat = strlen(CAT);
200#if defined(ISC) || defined(__SCO__) || defined(__UNIXWARE__)
201  int i;
202#endif
203
204  temp = CreateManpageName(entry, 0, 0);
205  snprintf(man_globals->manpage_title, sizeof(man_globals->manpage_title),
206    "The current manual page is: %s.", temp);
207  XtFree(temp);
208
209  ParseEntry(entry, path, section, page);
210
211/*
212 * Look for uncompressed files first.
213 */
214#if defined(__OpenBSD__) || defined(__NetBSD__)
215  /* look in machine subdir first */
216  snprintf(filename, sizeof(filename), "%s/%s%s/%s/%s", path, CAT,
217	  section + len_cat, MACHINE, page);
218  if ( (file = fopen(filename,"r")) != NULL)
219    return(file);
220#endif
221
222  snprintf(filename, sizeof(filename), "%s/%s%s/%s",
223    path, CAT, section + len_cat, page);
224  if ( (file = fopen(filename,"r")) != NULL)
225    return(file);
226
227/*
228 * Then for compressed files in an uncompressed directory.
229 */
230
231#if !defined(ISC) && !defined(__UNIXWARE__)
232#if defined(__OpenBSD__) || defined(__NetBSD__)
233  /* look in machine subdir first */
234  snprintf(filename, sizeof(filename), "%s/%s%s/%s/%s.%s", path, CAT,
235	  section + len_cat, MACHINE, page, COMPRESSION_EXTENSION);
236  if ( (file = Uncompress(man_globals, filename)) != NULL)
237    return(file);
238#endif
239  snprintf(filename, sizeof(filename), "%s/%s%s/%s.%s", path, CAT,
240	  section + len_cat, page, COMPRESSION_EXTENSION);
241  if ( (file = Uncompress(man_globals, filename)) != NULL)
242    return(file);
243#ifdef GZIP_EXTENSION
244  else {
245#if defined(__OpenBSD__) || defined(__NetBSD__)
246      /* look in machine subdir first */
247      snprintf(filename, sizeof(filename), "%s/%s%s/%s/%s.%s", path, CAT,
248	      section + len_cat, MACHINE, page, GZIP_EXTENSION);
249      if ( (file = Uncompress(man_globals, filename)) != NULL)
250	  return(file);
251#endif
252    snprintf(filename, sizeof(filename), "%s/%s%s/%s.%s", path, CAT,
253	    section + len_cat, page, GZIP_EXTENSION);
254    if ( (file = Uncompress(man_globals, filename)) != NULL)
255      return(file);
256  }
257#endif
258#else
259  for(i = 0; i < strlen(COMPRESSION_EXTENSIONS); i++) {
260      snprintf(filename, sizeof(filename), "%s/%s%s/%s.%c", path, CAT,
261            section + len_cat, page, COMPRESSION_EXTENSIONS[i]);
262      uncompress_format = uncompress_formats[i];
263#ifdef DEBUG
264      printf("Trying .%c ...\n", COMPRESSION_EXTENSIONS[i]);
265#endif
266      if ( (file = Uncompress(man_globals, filename)) != NULL)
267	return(file);
268  }
269#endif
270
271/*
272 * And lastly files in a compressed directory.
273 *
274 * The directory is not actually compressed it is just named man#.Z
275 * and all files in it are compressed without the .Z extension.
276 * HP does it this way (really :-).
277 */
278
279  snprintf(filename, sizeof(filename), "%s/%s%s.%s/%s", path, CAT,
280	   section + len_cat, COMPRESSION_EXTENSION, page);
281  if ( (file = Uncompress(man_globals, filename)) != NULL)
282    return(file);
283/*
284 * We did not find any preformatted manual pages, try to format it.
285 */
286
287  return(Format(man_globals, entry));
288}
289
290/*	Function Namecompress
291 *	Description: This function will attempt to find a compressed man
292 *                   page and uncompress it.
293 *	Arguments: man_globals - the psuedo global info.
294 *                 filename - name of file to uncompress.
295 *	Returns:; a pointer to the file or NULL.
296 */
297
298static FILE *
299Uncompress(ManpageGlobals * man_globals, char * filename)
300{
301  char tmp_file[BUFSIZ], error_buf[BUFSIZ];
302  FILE * file;
303
304#ifndef HAS_MKSTEMP
305  if ( !UncompressNamed(man_globals, filename, tmp_file) )
306    return(NULL);
307
308  else if ((file = fopen(tmp_file, "r")) == NULL) {
309      sprintf(error_buf, "Something went wrong in retrieving the %s",
310	      "uncompressed manual page try cleaning up /tmp.");
311      PopupWarning(man_globals, error_buf);
312  }
313#else
314  if (!UncompressNamed(man_globals, filename, tmp_file, &file)) {
315      sprintf(error_buf, "Something went wrong in retrieving the %s",
316	      "uncompressed manual page try cleaning up /tmp.");
317      PopupWarning(man_globals, error_buf);
318      return(NULL);
319  }
320#endif
321
322  unlink(tmp_file);		/* remove name in tree, it will remain
323				   until we close the fd, however. */
324  return(file);
325}
326
327/*	Function Name: UncompressNamed
328 *	Description: This function will attempt to find a compressed man
329 *                   page and uncompress it.
330 *	Arguments: man_globals - the psuedo global info.
331 *                 filename - name of file to uncompress.
332 * RETURNED        output - the file name output (must be an allocated string).
333 *	Returns:; TRUE if the file was found.
334 */
335
336#ifndef HAS_MKSTEMP
337static Boolean
338UncompressNamed(ManpageGlobals * man_globals, char * filename, char * output)
339#else
340static Boolean
341UncompressNamed(ManpageGlobals * man_globals, char * filename, char * output,
342		FILE ** output_fd)
343#endif
344{
345  char tmp[BUFSIZ], cmdbuf[BUFSIZ], error_buf[BUFSIZ];
346  struct stat junk;
347#ifdef HAS_MKSTEMP
348  int fd;
349#endif
350
351  if (stat(filename, &junk) != 0) { /* Check for existance of the file. */
352    if (errno != ENOENT) {
353      snprintf(error_buf, sizeof(error_buf),
354	       "Error while stating file %s, errno = %d", filename, errno);
355      PopupWarning(man_globals, error_buf);
356    }
357    return(FALSE);
358  }
359
360/*
361 * Using stdin is necessary to fool zcat since we cannot guarentee
362 * the .Z extension.
363 */
364
365  strcpy(tmp, MANTEMP);		/* get a temp file. */
366#ifndef HAS_MKSTEMP
367  (void) mktemp(tmp);
368#else
369  fd = mkstemp(tmp);
370  if (fd < 0) {
371      PopupWarning(man_globals, "Error creating a temp file");
372      return FALSE;
373  }
374  *output_fd = fdopen(fd, "r");
375#endif
376  strcpy(output, tmp);
377
378#ifdef GZIP_EXTENSION
379  if (streq(filename + strlen(filename) - strlen(GZIP_EXTENSION),
380	    GZIP_EXTENSION))
381    snprintf(cmdbuf, sizeof(cmdbuf), GUNZIP_FORMAT, filename, output);
382  else
383#endif
384  snprintf(cmdbuf, sizeof(cmdbuf), UNCOMPRESS_FORMAT, filename, output);
385  if(system(cmdbuf) == 0) 	/* execute search. */
386    return(TRUE);
387
388  snprintf(error_buf, sizeof(error_buf),
389	   "Error while uncompressing, command was: %s", cmdbuf);
390  PopupWarning(man_globals, error_buf);
391  return(FALSE);
392}
393
394#if defined(SMAN) && defined(SFORMAT)
395/*	Function Name: SgmlToRoffNamed
396 *	Description: This function will attempt to find an SGML man
397 *                   page and convert it to roff format.
398 *	Arguments: man_globals - the psuedo global info.
399 *                 filename - name of file to uncompress.
400 * RETURNED        output - the file name output (must be an allocated string).
401 *	Returns:; TRUE if the file was found.
402 */
403
404#ifndef HAS_MKSTEMP
405static Boolean
406SgmlToRoffNamed(ManpageGlobals * man_globals, char * filename, char * output)
407#else
408static Boolean
409SgmlToRoffNamed(ManpageGlobals * man_globals, char * filename, char * output,
410		FILE ** output_fd)
411#endif
412{
413  char tmp[BUFSIZ], cmdbuf[BUFSIZ], error_buf[BUFSIZ];
414  struct stat junk;
415#ifdef HAS_MKSTEMP
416  int fd;
417#endif
418
419  if (stat(filename, &junk) != 0) { /* Check for existance of the file. */
420    if (errno != ENOENT) {
421      snprintf(error_buf, sizeof(error_buf),
422	       "Error while stating file %s, errno = %d", filename, errno);
423      PopupWarning(man_globals, error_buf);
424    }
425    return(FALSE);
426  }
427
428  strcpy(tmp, MANTEMP);		/* get a temp file. */
429#ifndef HAS_MKSTEMP
430  (void) mktemp(tmp);
431#else
432  fd = mkstemp(tmp);
433  if (fd < 0) {
434      PopupWarning(man_globals, "Error creating a temp file");
435      return FALSE;
436  }
437  *output_fd = fdopen(fd, "r");
438#endif
439  strcpy(output, tmp);
440
441  snprintf(cmdbuf, sizeof(cmdbuf), "%s %s > %s", SFORMAT, filename, output);
442  if(system(cmdbuf) == 0) 	/* execute search. */
443    return(TRUE);
444
445  snprintf(error_buf, sizeof(error_buf),
446	   "Error while converting from sgml, command was: %s", cmdbuf);
447  PopupWarning(man_globals, error_buf);
448  return(FALSE);
449}
450#endif /* defined (SMAN) && defined(SFORMAT) */
451
452/*	Function Name: Format
453 *	Description: This funtion formats the manual pages and interfaces
454 *                   with the user.
455 *	Arguments: man_globals - the psuedo globals
456 *                 file - the file pointer to use and return
457 *                 entry - the current entry struct.
458 *                 current_box - The current directory being displayed.
459 *	Returns: none.
460 */
461
462/* ARGSUSED */
463
464FILE *
465Format(ManpageGlobals * man_globals, char * entry)
466{
467  FILE * file = NULL;
468#ifdef HAS_MKSTEMP
469  int fd;
470#endif
471  Widget manpage = man_globals->manpagewidgets.manpage;
472  char cmdbuf[BUFSIZ], tmp[BUFSIZ], filename[BUFSIZ], error_buf[BUFSIZ];
473  char path[BUFSIZ], sect[BUFSIZ];
474  XEvent event;
475  Position x,y;			/* location to pop up the
476				   "would you like to save" widget. */
477
478#ifndef HAS_MKSTEMP
479  if ( !UncompressUnformatted(man_globals, entry, filename) ) {
480#else
481  if ( !UncompressUnformatted(man_globals, entry, filename, &file) ) {
482#endif
483    /* We Really could not find it, this should never happen, yea right. */
484    snprintf(error_buf, sizeof(error_buf),
485	     "Could not open manual page, %s", entry);
486    PopupWarning(man_globals, error_buf);
487    XtPopdown( XtParent(man_globals->standby) );
488    return(NULL);
489  }
490
491#ifndef HAS_MKSTEMP
492  if ((file = fopen(filename, "r")) != NULL) {
493#else
494  if (file != NULL) {
495#endif
496    char line[BUFSIZ];
497
498    if (fgets(line, sizeof(line), file) != NULL) {
499	if (strncmp(line, ".so ", 4) == 0) {
500	  line[strlen(line) - 1] = '\0';
501	  fclose(file);
502	  unlink(filename);
503	  if (line[4] != '/') {
504	    char *ptr = NULL;
505
506	    strcpy(tmp, entry);
507	    if ((ptr = rindex(tmp, '/')) != NULL) {
508	      *ptr = '\0';
509	      if ((ptr = rindex(tmp, '/')) != NULL)
510		ptr[1] = '\0';
511	    }
512	  }
513	  else
514	    *tmp = '\0';
515	  snprintf(filename, sizeof(filename), "%s%s", tmp, line + 4);
516
517	  return (Format(man_globals, filename));
518	}
519    }
520    fclose(file);
521  }
522
523  Popup(XtParent(man_globals->standby), XtGrabExclusive);
524  while ( !XCheckTypedWindowEvent(XtDisplay(man_globals->standby),
525				  XtWindow(man_globals->standby),
526				  Expose, &event) );
527  XtDispatchEvent( &event );
528  XFlush(XtDisplay(man_globals->standby));
529
530  strcpy(tmp,MANTEMP);		          /* Get a temp file. */
531#ifndef HAS_MKSTEMP
532  (void) mktemp(tmp);
533#else
534  fd = mkstemp(tmp);
535  file = fdopen(fd, "r");
536#endif
537  strcpy(man_globals->tempfile, tmp);
538
539  ParseEntry(entry, path, sect, NULL);
540
541#ifndef HANDLE_ROFFSEQ
542#ifndef HAS_MKSTEMP
543  snprintf(cmdbuf, sizeof(cmdbuf), "cd %s ; %s %s %s > %s %s", path, TBL,
544	  filename, FORMAT, man_globals->tempfile, "2> /dev/null");
545#else
546  snprintf(cmdbuf, sizeof(cmdbuf), "cd %s ; %s %s %s >> %s %s", path, TBL,
547	  filename, FORMAT, man_globals->tempfile, "2> /dev/null");
548#endif
549#else
550  /* Handle more flexible way of specifying the formatting pipeline */
551  if (! ConstructCommand(cmdbuf, path, filename, man_globals->tempfile)) {
552     sprintf(error_buf, "Constructed command was too long!");
553     PopupWarning(man_globals, error_buf);
554     file = NULL;
555  }
556  else
557#endif /* HANDLE_ROFFSEQ */
558
559  if(system(cmdbuf) != 0) {	/* execute search. */
560    snprintf(error_buf, sizeof(error_buf),
561	    "Something went wrong trying to run the command: %s", cmdbuf);
562    PopupWarning(man_globals, error_buf);
563    file = NULL;
564  }
565  else {
566#ifndef HAS_MKSTEMP
567    if ((file = fopen(man_globals->tempfile,"r")) == NULL) {
568      sprintf(error_buf, "Something went wrong in retrieving the %s",
569	      "temp file, try cleaning up /tmp");
570      PopupWarning(man_globals, error_buf);
571    }
572    else {
573#endif
574
575      XtPopdown( XtParent(man_globals->standby) );
576
577      if ( (man_globals->save == NULL) ||
578	   (man_globals->manpagewidgets.manpage == NULL) )
579	unlink(man_globals->tempfile);
580      else {
581	char * ptr, catdir[BUFSIZ];
582
583	/*
584	 * If the catdir is writeable then ask the user if he/she wants to
585	 * write the man page to it.
586	 */
587
588	strcpy(catdir, man_globals->save_file);
589	if ( (ptr = rindex(catdir, '/')) != NULL) {
590	  *ptr = '\0';
591
592	  if ( access(catdir, W_OK) != 0 )
593	    unlink(man_globals->tempfile);
594	  else {
595	    x = (Position) Width(man_globals->manpagewidgets.manpage)/2;
596	    y = (Position) Height(man_globals->manpagewidgets.manpage)/2;
597	    XtTranslateCoords(manpage, x, y, &x, &y);
598	    PositionCenter( man_globals->save, (int) x, (int) y, 0, 0, 0, 0);
599	    XtPopup( man_globals->save, XtGrabExclusive);
600	  }
601	}
602	else
603	  unlink(man_globals->tempfile);
604      }
605#ifndef HAS_MKSTEMP
606    }
607#endif
608  }
609
610 /*
611  * If the original was compressed or in another format, delete temporary file.
612  */
613  if (man_globals->deletetempfile)
614    unlink(filename);
615
616  return(file);
617}
618
619#ifdef HANDLE_ROFFSEQ
620/*      Function Name: ConstructCommand
621 *      Description: Constructs the pipeline of commands necessary to format
622 *                   a manual page.
623 *      Arguments: cmdbuf - the buffer into which to write the command
624 *                 path - the directory in which the original man page resides
625 *                 filename - the (uncompressed) manpage source file
626 *                 tempfile - the name of a temporary file to direct the final
627 *                  output of the pipeline into
628 *      Returns: TRUE if the command fit into the buffer, FALSE if it would
629 *               be too long (more than BUFSIZ characters)
630 */
631static Boolean
632ConstructCommand(cmdbuf, path, filename, tempfile)
633   char *cmdbuf, *path, *filename, *tempfile;
634{
635   /* The original code did the following to produce a command line:
636    *   sprintf(cmdbuf,"cd %s ; %s %s %s > %s %s", path, TBL,
637    *      filename, FORMAT, man_globals->tempfile, "2> /dev/null");
638    * We are more flexible and follow more or less the algorithm used
639    * by the Linux man command:
640    *  + Obtain a string of letters from the following sources in order
641    *    of preference:
642    *    + a command line option (not implemented in xman; it's probably not
643    *      useful)
644    *    + the first line of the manpage source, if it is of the form:
645    *      '\" <string>
646    *    + the MANROFFSEQ environment variable
647    *    + a default string; this is "".
648    *  + Interpret the string as a pipeline of filters:
649    *    + e = eqn   g = grap   p = pic   t = tbl   v = vgrind   r = refer
650    *  + zsoelim is always run as the first preprocessor in any case.
651    *
652    * Strictly speaking we should save a catpage iff the string comes
653    * from the file or is the default.
654    *
655    * You'll notice that we format a man page into ASCII text output and then
656    * attempt to interpret things like L^HL as bold and so forth. This
657    * is so obviously the Wrong Thing it's untrue.
658    */
659   char *c = cmdbuf;           /* current posn in buffer */
660   int left = BUFSIZ;          /* space left in buffer */
661   int used;
662   char *fmt;
663   FILE *file;
664   char fmtbuf[128];
665   int gotfmt = 0;             /* set to 1 if we got a directive from source */
666   char *fname = NULL;
667#ifdef __UNIXOS2__
668   int i;
669#endif
670
671   fmt = NULL;
672   /* If you have a command line option that gives a setting for fmt,
673      set it here. */
674
675   if (!fmt) {
676      /* This is the tricky bit: extract a format string from the source file
677       * Annoyingly, filename might be relative or absolute. We cheat and
678       * use system to get the thing to a known absoute filename.
679       */
680      if (filename[0] == '/') {
681         fname = filename;
682      } else {
683         fname = malloc(strlen(path) + 1 + strlen(filename) + 1);
684         if (!fname)
685            return FALSE;
686         sprintf(fname, "%s/%s", path, filename);
687      }
688      if ((file = fopen(fname, "r")) &&
689          (fgets(fmtbuf, sizeof(fmtbuf), file)) &&
690          (!memcmp(fmtbuf, "'\\\" ", 4))) {
691                              /* that's squote-backslash-dquote-space */
692         int len;
693         fmt = fmtbuf + 3;
694         len = strlen(fmt);
695         if (len && (fmt[len-1] == '\n')) {
696            fmt[len-1] = 0;
697            gotfmt++;
698         }
699      }
700      if (fname && fname != filename)
701         free(fname);
702      if (!gotfmt)                                /* not there or some error */
703      {
704         fmt = getenv("MANROFFSEQ");
705      }
706   }
707
708   if (!fmt)
709   {
710      fmt = DEFAULT_MANROFFSEQ;
711   }
712
713
714   /* Start with the first fixed part of the command line */
715#ifdef __UNIXOS2__
716   for (i = 0; i < strlen(path); i++) {
717     if (path[i] == '/')
718       path[i] = '\\';
719   }
720   used = snprintf(c, left, "cd %s & %s %s ", path, ZSOELIM, filename);
721#else
722   used = snprintf(c, left, "cd %s; %s %s ", path, ZSOELIM, filename);
723#endif
724   left -= used;
725   c += used;
726   if (left <= 1)
727      return (FALSE);
728
729   /* Now add preprocessors of the form '| processor' */
730   for ( ; *fmt; fmt++)
731   {
732      char *filter;
733      switch (*fmt)
734      {
735         case 'e':
736            filter = EQN;
737            break;
738         case 'g':
739            filter = GRAP;
740            break;
741         case 'p':
742            filter = XMAN_PIC;
743            break;
744         case 't':
745            filter = TBL;
746            break;
747         case 'v':
748            filter = VGRIND;
749            break;
750         case 'r':
751            filter = REFER;
752            break;
753         default:
754            filter = NULL;
755            break;
756      }
757      if (filter)
758      {
759         used = snprintf(c, left, " | %s ", filter);
760         left -= used;
761         c += used;
762         if (left <= 1)
763            return (FALSE);
764      }
765   }
766
767   /* Now add the fixed trailing part 'formatprog > tempfile 2> /dev/null' */
768#ifdef __UNIXOS2__
769   used = snprintf(c, left, " | %s > %s 2>NUL", FORMAT, tempfile);
770#else
771#ifndef HAS_MKSTEMP
772   used = snprintf(c, left, " | %s > %s 2>/dev/null", FORMAT, tempfile);
773#else
774   used = snprintf(c, left, " | %s >> %s 2>/dev/null", FORMAT, tempfile);
775#endif
776#endif /* __UNIXOS2__ */
777   left -= used;
778   if (left <= 1)
779      return (FALSE);
780
781   return (TRUE);
782}
783#endif /* HANDLE_ROFFSEQ */
784
785/*	Function Name: UncompressUnformatted
786 *	Description: Finds an uncompressed unformatted manual page.
787 *	Arguments: man_globals - the psuedo global structure.
788 *                 entry - the manual page entry.
789 * RETURNED        filename - location to put the name of the file.
790 *	Returns: TRUE if the file was found.
791 */
792
793static Boolean
794#ifndef HAS_MKSTEMP
795UncompressUnformatted(ManpageGlobals * man_globals, char * entry,
796		      char * filename)
797#else
798UncompressUnformatted(ManpageGlobals * man_globals, char * entry,
799		      char * filename, FILE **file)
800#endif
801{
802  char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], input[BUFSIZ];
803  int len_cat = strlen(CAT), len_man = strlen(MAN);
804#if defined(SMAN) && defined(SFORMAT)
805  int len_sman = strlen(SMAN);
806#endif
807
808  ParseEntry(entry, path, section, page);
809
810#if defined(__OpenBSD__) || defined(__NetBSD__)
811  /*
812   * look for uncompressed file in machine subdir first
813   */
814  snprintf(filename, BUFSIZ, "%s/%s%s/%s/%s", path, MAN,
815	  section + len_cat, MACHINE, page);
816  if ( access( filename, R_OK ) == 0 ) {
817    man_globals->compress = FALSE;
818    man_globals->gzip = FALSE;
819    man_globals->deletetempfile = FALSE;
820    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
821    	     "%s/%s%s/%s/%s", path, CAT, section + len_cat, MACHINE, page);
822    return(TRUE);
823  }
824 /*
825  * Then for compressed files in an uncompressed directory.
826  */
827  snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
828#ifndef HAS_MKSTEMP
829  if ( UncompressNamed(man_globals, input, filename) ) {
830#else
831  if ( UncompressNamed(man_globals, input, filename, file) ) {
832#endif
833    man_globals->compress = TRUE;
834    man_globals->deletetempfile = TRUE;
835    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
836	     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
837	     COMPRESSION_EXTENSION);
838    return(TRUE);
839  }
840#ifdef GZIP_EXTENSION
841  else {
842    snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
843#ifndef HAS_MKSTEMP
844    if ( UncompressNamed(man_globals, input, filename) ) {
845#else
846    if ( UncompressNamed(man_globals, input, filename, file) ) {
847#endif
848      man_globals->compress = TRUE;
849      man_globals->gzip = TRUE;
850      man_globals->deletetempfile = TRUE;
851      snprintf(man_globals->save_file, sizeof(man_globals->save_file),
852	       "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
853	       GZIP_EXTENSION);
854      return(TRUE);
855    }
856  }
857#endif /* GZIP_EXTENSION */
858#endif /* __OpenBSD__ || __NetBSD__ */
859/*
860 * Look for uncompressed file first.
861 */
862
863  snprintf(filename, BUFSIZ, "%s/%s%s/%s", path, MAN, section + len_man, page);
864  if ( access( filename, R_OK ) == 0 ) {
865    man_globals->compress = FALSE;
866    man_globals->gzip = FALSE;
867    man_globals->deletetempfile = FALSE;
868    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
869	     "%s/%s%s/%s", path, CAT, section + len_cat, page);
870    return(TRUE);
871  }
872
873#if defined(SMAN) && defined(SFORMAT)
874 /*
875  * Look for uncompressed sgml file next.
876  */
877
878  snprintf(input, BUFSIZ, "%s/%s%s/%s", path, SMAN, section + len_sman, page);
879#ifndef HAS_MKSTEMP
880  if ( SgmlToRoffNamed(man_globals, input, filename) ) {
881#else
882  if ( SgmlToRoffNamed(man_globals, input, filename, file) ) {
883#endif
884    man_globals->compress = FALSE;
885    man_globals->gzip = FALSE;
886    man_globals->deletetempfile = TRUE;
887    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
888            "%s/%s%s/%s", path, CAT, section + len_cat, page);
889    return(TRUE);
890  }
891#endif
892
893/*
894 * Then for compressed files in an uncompressed directory.
895 */
896
897  snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
898#ifndef HAS_MKSTEMP
899  if ( UncompressNamed(man_globals, input, filename) ) {
900#else
901  if ( UncompressNamed(man_globals, input, filename, file) ) {
902#endif
903    man_globals->compress = TRUE;
904    man_globals->deletetempfile = TRUE;
905    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
906	     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
907	     COMPRESSION_EXTENSION);
908    return(TRUE);
909  }
910#ifdef GZIP_EXTENSION
911  else {
912    snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
913#ifndef HAS_MKSTEMP
914    if ( UncompressNamed(man_globals, input, filename) ) {
915#else
916    if ( UncompressNamed(man_globals, input, filename, file) ) {
917#endif
918      man_globals->compress = TRUE;
919      man_globals->gzip = TRUE;
920      man_globals->deletetempfile = TRUE;
921      snprintf(man_globals->save_file, sizeof(man_globals->save_file),
922	       "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
923	       GZIP_EXTENSION);
924      return(TRUE);
925    }
926  }
927#endif
928/*
929 * And lastly files in a compressed directory.
930 */
931
932  snprintf(input, sizeof(input), "%s/%s%s.%s/%s", path,
933	  MAN, section + len_man, COMPRESSION_EXTENSION, page);
934#ifndef HAS_MKSTEMP
935  if ( UncompressNamed(man_globals, input, filename) ) {
936#else
937  if ( UncompressNamed(man_globals, input, filename, file) ) {
938#endif
939    man_globals->compress = TRUE;
940    man_globals->deletetempfile = TRUE;
941    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
942	     "%s/%s%s.%s/%s", path, CAT, section + len_cat,
943    	     COMPRESSION_EXTENSION, page);
944    return(TRUE);
945  }
946  return(FALSE);
947}
948
949/*	Function Name: AddCursor
950 *	Description: This function adds the cursor to the window.
951 *	Arguments: w - the widget to add the cursor to.
952 *                 cursor - the cursor to add to this widget.
953 *	Returns: none
954 */
955
956void
957AddCursor(Widget w, Cursor cursor)
958{
959  XColor colors[2];
960  Arg args[10];
961  Cardinal num_args = 0;
962  Colormap c_map;
963
964  if (!XtIsRealized(w)) {
965    PopupWarning(NULL, "Widget is not realized, no cursor added.\n");
966    return;
967  }
968
969  XtSetArg( args[num_args], XtNcolormap, &c_map); num_args++;
970  XtGetValues( w, args, num_args);
971
972  colors[0].pixel = resources.cursors.fg_color;
973  colors[1].pixel = resources.cursors.bg_color;
974
975  XQueryColors (XtDisplay(w), c_map, colors, 2);
976  XRecolorCursor(XtDisplay(w), cursor, colors, colors+1);
977  XDefineCursor(XtDisplay(w),XtWindow(w),cursor);
978}
979
980/*	Function Name: ChangeLabel
981 *	Description: This function changes the label field of the
982 *                   given widget to the string in str.
983 *	Arguments: w - the widget.
984 *                 str - the string to change the label to.
985 *	Returns: none
986 */
987
988void
989ChangeLabel(Widget w, char * str)
990{
991  Arg arglist[3];		/* An argument list. */
992
993  if (w == NULL) return;
994
995  XtSetArg(arglist[0], XtNlabel, str);
996
997/* shouldn't really have to do this. */
998  XtSetArg(arglist[1], XtNwidth, 0);
999  XtSetArg(arglist[2], XtNheight, 0);
1000
1001  XtSetValues(w, arglist, (Cardinal) 1);
1002}
1003
1004/*
1005 * In an ideal world this would be part of the XToolkit, and I would not
1006 * have to do it, but such is life sometimes.  Perhaps in X11R3.
1007 */
1008
1009/*	Function Name: PositionCenter
1010 *	Description: This function positions the given widgets center
1011 *                   in the following location.
1012 *	Arguments: widget - the widget widget to postion
1013 *                 x,y - The location for the center of the widget
1014 *                 above - number of pixels above center to locate this widget
1015 *                 left - number of pixels left of center to locate this widget
1016 *                 h_space, v_space - how close to get to the edges of the
1017 *                                    parent window.
1018 *	Returns: none
1019 *      Note:  This should only be used with a popup widget that has override
1020 *             redirect set.
1021 */
1022
1023void
1024PositionCenter(Widget widget, int x, int y, int above, int left, int v_space, int h_space)
1025{
1026  Arg wargs[2];
1027  int x_temp,y_temp;		/* location of the new window. */
1028  int parent_height,parent_width; /* Height and width of the parent widget or
1029				   the root window if it has no parent. */
1030
1031  x_temp = x - left - Width(widget) / 2 + BorderWidth(widget);
1032  y_temp = y - above -  Height(widget) / 2 + BorderWidth(widget);
1033
1034  parent_height = HeightOfScreen(XtScreen(widget));
1035  parent_width = WidthOfScreen(XtScreen(widget));
1036
1037/*
1038 * Check to make sure that all edges are within the viewable part of the
1039 * root window, and if not then force them to be.
1040 */
1041
1042  if (x_temp < h_space)
1043    x_temp = v_space;
1044  if (y_temp < v_space)
1045    (y_temp = 2);
1046
1047  if ( y_temp + Height(widget) + v_space > parent_height )
1048      y_temp = parent_height - Height(widget) - v_space;
1049
1050  if ( x_temp + Width(widget) + h_space > parent_width )
1051      x_temp = parent_width - Width(widget) - h_space;
1052
1053  XtSetArg(wargs[0], XtNx, x_temp);
1054  XtSetArg(wargs[1], XtNy, y_temp);
1055  XtSetValues(widget, wargs, 2);
1056}
1057
1058/*	Function Name: ParseEntry(entry, path, sect, page)
1059 *	Description: Parses the manual pages entry filenames.
1060 *	Arguments: str - the full path name.
1061 *                 path - the path name.      RETURNED
1062 *                 sect - the section name.   RETURNED
1063 *                 page - the page name.      RETURNED
1064 *	Returns: none.
1065 */
1066
1067void
1068ParseEntry(char *entry, char *path, char *sect, char *page)
1069{
1070  char *c, temp[BUFSIZ];
1071
1072  strcpy(temp, entry);
1073
1074  c = rindex(temp, '/');
1075  if (c == NULL)
1076    PrintError("index failure in ParseEntry.");
1077  *c++ = '\0';
1078  if (page != NULL)
1079    strcpy(page, c);
1080
1081  c = rindex(temp, '/');
1082  if (c == NULL)
1083    PrintError("index failure in ParseEntry.");
1084  *c++ = '\0';
1085#if defined(SFORMAT) && defined(SMAN)
1086  /* sgmltoroff sometimes puts an extra ./ in the path to .so entries */
1087  if (strcmp(c, ".") == 0) {
1088      c = rindex(temp, '/');
1089      if (c == NULL)
1090	  PrintError("index failure in ParseEntry.");
1091      *c++ = '\0';
1092  }
1093#endif
1094#if defined(__OpenBSD__) || defined(__NetBSD__)
1095  /* Skip machine subdirectory if present */
1096  if (strcmp(c, MACHINE) == 0) {
1097      c = rindex(temp, '/');
1098      if (c == NULL)
1099	  PrintError("index failure in ParseEntry.");
1100      *c++ = '\0';
1101  }
1102#endif
1103  if (sect != NULL)
1104    strcpy(sect, c);
1105
1106  if (path != NULL)
1107    strcpy(path, temp);
1108}
1109
1110/*      Function Name: GetGlobals
1111 *      Description: Gets the psuedo globals associated with the
1112 *                   manpage associated with this widget.
1113 *      Arguments: w - a widget in the manpage.
1114 *      Returns: the psuedo globals.
1115 *      Notes: initial_widget is a globals variable.
1116 *             manglobals_context is a global variable.
1117 */
1118
1119ManpageGlobals *
1120GetGlobals(Widget w)
1121{
1122  Widget temp;
1123  caddr_t data;
1124
1125  while ( (temp = XtParent(w)) != initial_widget && (temp != NULL))
1126    w = temp;
1127
1128  if (temp == NULL)
1129    XtAppError(XtWidgetToApplicationContext(w),
1130	       "Xman: Could not locate widget in tree, exiting");
1131
1132  if (XFindContext(XtDisplay(w), XtWindow(w),
1133		   manglobals_context, &data) != XCSUCCESS)
1134    XtAppError(XtWidgetToApplicationContext(w),
1135	       "Xman: Could not find global data, exiting");
1136
1137  return( (ManpageGlobals *) data);
1138}
1139
1140/*      Function Name: SaveGlobals
1141 *      Description: Saves the psuedo globals on the widget passed
1142 *                   to this function, although GetGlobals assumes that
1143 *                   the data is associated with the popup child of topBox.
1144 *      Arguments: w - the widget to associate the data with.
1145 *                 globals - data to associate with this widget.
1146 *      Returns: none.
1147 *      Notes: WIDGET MUST BE REALIZED.
1148 *             manglobals_context is a global variable.
1149 */
1150
1151void
1152SaveGlobals(Widget w, ManpageGlobals * globals)
1153{
1154  if (XSaveContext(XtDisplay(w), XtWindow(w), manglobals_context,
1155		   (caddr_t) globals) != XCSUCCESS)
1156    XtAppError(XtWidgetToApplicationContext(w),
1157	       "Xman: Could not save global data, are you out of memory?");
1158}
1159
1160/*      Function Name: RemoveGlobals
1161 *      Description: Removes the psuedo globals from the widget passed
1162 *                   to this function.
1163 *      Arguments: w - the widget to remove the data from.
1164 *      Returns: none.
1165 *      Notes: WIDGET MUST BE REALIZED.
1166 *             manglobals_context is a global variable.
1167 */
1168
1169void
1170RemoveGlobals(Widget w)
1171{
1172  if (XDeleteContext(XtDisplay(w), XtWindow(w),
1173		     manglobals_context) != XCSUCCESS)
1174    XtAppError(XtWidgetToApplicationContext(w),
1175	       "Xman: Could not remove global data?");
1176}
1177