Back to index

tetex-bin  3.0
browser.c
Go to the documentation of this file.
00001 /* routines for launching a browser to retrieve remote documents.
00002  * Copyright(C) 2002-2004 the xdvik development team.
00003  */
00004 /*
00005  * Permission is hereby granted, free of charge, to any person obtaining a copy
00006  * of this software and associated documentation files (the "Software"), to
00007  * deal in the Software without restriction, including without limitation the
00008  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
00009  * sell copies of the Software, and to permit persons to whom the Software is
00010  * furnished to do so, subject to the following conditions:
00011  * 
00012  * The above copyright notice and this permission notice shall be included in
00013  * all copies or substantial portions of the Software.
00014  * 
00015  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00016  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00017  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
00018  * IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
00019  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
00020  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
00021  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00022 */
00023 
00024 #include "xdvi-config.h"
00025 #include "xdvi.h"
00026 
00027 #include <ctype.h>
00028 #include "kpathsea/c-fopen.h"
00029 #include "kpathsea/c-stat.h"
00030 
00031 #include <sys/types.h>
00032 #include <sys/wait.h> /* for waitpid(), WEXITSTATUS */
00033 
00034 #include "util.h"
00035 #include "message-window.h"
00036 #include "events.h"
00037 #include "browser.h"
00038 #include "string-utils.h"
00039 #include "statusline.h"
00040 
00041 static const char *const default_browser_cmd =
00042        "netscape -raise -remote \"openURL(%s,new-window)\""
00043        ":xterm -e lynx %s"
00044        ":xterm -e wget %s";
00045 
00046 static Boolean
00047 do_fork_browser(char *argv[])
00048 {
00049     pid_t pid;
00050     switch(pid = vfork()) {                  
00051     case -1: /* forking error */
00052        perror("fork");
00053        return False;
00054     case 0: /* child */
00055        execvp(argv[0], argv);
00056        /* arrive here only if execvp failed */
00057        XDVI_ERROR((stderr, "Execution of %s failed: %s", argv[0], strerror(errno)));
00058        _exit(EXIT_FAILURE);
00059        return False; /* notreached - make compiler happy */
00060     default: /* parent */
00061        {
00062            int timeout;
00063            for (timeout = 0; timeout < 15; timeout++) {
00064               int status;
00065               if (waitpid(pid, &status, WNOHANG)) {
00066                   TRACE_HTEX((stderr, "waiting for %d: %d", (int)pid, status));
00067                   if (WIFEXITED(status)) {
00068                      if (WEXITSTATUS(status) != 0) {
00069                          fprintf(stderr, "Command `%s' exited with error status %d\n",
00070                                 argv[0], WEXITSTATUS(status));
00071                          return False;
00072                      }
00073                      else {
00074                          TRACE_HTEX((stderr, "Child exited OK."));
00075                          return True;
00076                      }
00077                   }
00078                   else { /* when do we arrive here?? */
00079                      sleep(1);
00080                   }
00081               }
00082               else { /* waiting for child to finish */
00083                   sleep(1);
00084               }
00085            }
00086            return True; /* child not finished in time */
00087        }
00088     }
00089 }
00090 
00091 static Boolean
00092 fork_browser(const char *browser, const char *url)
00093 {
00094     char *cmd, *orig_cmd, *last_arg;
00095     char **argv = NULL;
00096     int argv_len = 0;
00097     int i = 0, j = 0;
00098     int match = 0;
00099     int retval;
00100     
00101 
00102     cmd = xmalloc(strlen(browser) + strlen(url) + 1);
00103     orig_cmd = cmd; /* for freeing it later */
00104     last_arg = cmd;
00105 
00106     /* skip over leading space */
00107     while (isspace((int)browser[i]))
00108        i++;
00109 
00110     while (browser[i] != '\0') {
00111        while (j >= argv_len) {
00112            argv_len += 10;
00113            argv = xrealloc(argv, argv_len * sizeof *argv);
00114        }
00115        /* chop into separate arguments at spaces */
00116        if (isspace((int)browser[i])) {
00117            *cmd++ = '\0';
00118            argv[j++] = format_arg(last_arg, url, &match);
00119            last_arg = cmd;
00120            /* skip over multiple spaces */
00121            while (isspace((int)browser[i]))
00122               i++;
00123        }
00124        /* remove quotes around arguments containing spaces */
00125        else if (browser[i] == '\'' || browser[i] == '"') {
00126            int len = 0;
00127            /* normalize %% and replace %s by URL */
00128            argv[j++] = unquote_arg(browser+i, url, &match, &len);
00129            if (len == 0) { /* excess quote at end of arg; try to recover: */
00130               j--;
00131               break;
00132            }
00133            i += len + 1;
00134        }
00135        else {
00136            *cmd++ = browser[i++];
00137        }
00138     }
00139     *cmd = browser[i]; /* null-teminate */
00140     /* append last, unless it contained only skipped spaces */
00141     if (strlen(last_arg) > 0) {
00142        argv[j++] = format_arg(last_arg, url, &match);
00143     }
00144     
00145     if (match == 0)
00146        argv[j++] = xstrdup(url);
00147     argv[j++] = NULL;
00148 
00149     for (i = 0; argv[i] != NULL; i++) {
00150        TRACE_HTEX((stderr, "arg[%d]: |%s|", i, argv[i]));
00151     }
00152 
00153     /* This will wait for child exits: */
00154     retval = do_fork_browser(argv);
00155     for (i = 0; argv[i] != NULL; i++)
00156        free(argv[i]);
00157     free(orig_cmd);
00158     free(argv);
00159     return retval;
00160 }
00161 
00162 void
00163 launch_browser(const char *filename)
00164 {
00165     const char *browser;
00166     int pid;
00167     struct xchild *my_child;
00168     struct xio *my_io;
00169     int err_pipe[2];
00170     
00171     /* try to set it from possible resources: First command-line argument
00172        or X resource, then environment variables, then default_browser_cmd
00173     */
00174     for (;;) {
00175        if ((browser = resource.browser) != NULL)
00176            break;
00177        if ((browser = getenv("BROWSER")) != NULL)
00178            break;
00179        if ((browser = getenv("WWWBROWSER")) != NULL)
00180            break;
00181        XDVI_INFO((stderr, "Browser not set (xdvi.wwwBrowser, -browser or $BROWSER environment variable)."));
00182        XDVI_INFO((stderr, "Using built-in default: `%s'",
00183                  default_browser_cmd));
00184        browser = default_browser_cmd;
00185        break;
00186     }
00187     
00188     /* fork first time so that we can wait for children, without
00189        freezing the GUI.  FIXME: this copies stuff from
00190        fork_process for the inter-process communication stuff -
00191        it would be better to have this in one function. */
00192     my_child = xmalloc(sizeof *my_child);
00193     my_io = xmalloc(sizeof *my_io);
00194     statusline_print(STATUS_MEDIUM, "Trying to launch browser ...");
00195     /* flush output buffers to avoid double buffering (i.e. data
00196        waiting in the output buffer being written twice, by the parent
00197        and the child) */
00198     fflush(stdout);
00199     fflush(stderr);
00200        
00201     if (pipe(err_pipe) < 0) {
00202        perror("pipe");
00203        _exit(-1);
00204     }
00205        
00206     switch (pid = fork()) {
00207     case -1:  /* forking error */
00208        perror("fork");
00209        close(err_pipe[0]);
00210        close(err_pipe[1]);
00211        return;
00212     case 0:   /* child */
00213        {
00214            char *tmp_browser = xstrdup(browser);
00215 
00216            close(err_pipe[0]);     /* no reading from stderr */
00217               
00218            /* make stderr of child go to err_pipe[1] */
00219            if (dup2(err_pipe[1], STDERR_FILENO) != STDERR_FILENO) {
00220               perror("dup2 for stderr");
00221               _exit(EXIT_FAILURE);
00222               return;       /* make compiler happy */
00223            }
00224               
00225            /*
00226              BROWSER is a colon-separated list of commands, in decreasing preference;
00227              use the first that can be forked successfully. Note that the return
00228              value of the command isn't used at all (with GUI programs, xdvi shouldn't
00229              hang until they terminate!)
00230            */
00231            while (tmp_browser != NULL) {
00232               char *next = strchr(tmp_browser, ':');
00233               if (next != NULL) {
00234                   *next++ = '\0';
00235               }
00236               TRACE_HTEX((stderr, "trying browser |%s|", tmp_browser));
00237               /* fork a second time to start the browser */
00238               if (fork_browser(tmp_browser, filename)) { /* foking worked */
00239                   _exit(EXIT_SUCCESS);
00240               }
00241               tmp_browser = next;
00242            }
00243            /* child arrives here only if none of the commands worked */
00244            XDVI_WARNING((stderr, "None of the browser commands in the `browser' resource (%s) worked\n",
00245                        browser));
00246            free(tmp_browser);
00247            _exit(EXIT_FAILURE);
00248        }
00249     default: /* parent */
00250        close(err_pipe[1]);  /* no writing to stderr */
00251 
00252        my_io->next = NULL;
00253        my_io->fd = err_pipe[0];
00254        my_io->xio_events = XIO_IN;
00255 #if HAVE_POLL
00256        my_io->pfd = NULL;
00257 #endif
00258        my_io->read_proc = read_child_error;
00259        my_io->write_proc = NULL;
00260            
00261        my_child->next = NULL;
00262        my_child->pid = pid;
00263        my_child->name = NULL;
00264        my_child->proc = handle_child_exit;
00265        my_child->data = NULL;
00266        my_child->io = my_io;
00267            
00268        set_chld(my_child);
00269        statusline_print(STATUS_MEDIUM, "Trying to launch browser ... done.");
00270     }
00271 }
00272 
00273