summaryrefslogtreecommitdiff
path: root/support/xpdfopen/pdfopen.c
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /support/xpdfopen/pdfopen.c
Initial commit
Diffstat (limited to 'support/xpdfopen/pdfopen.c')
-rw-r--r--support/xpdfopen/pdfopen.c681
1 files changed, 681 insertions, 0 deletions
diff --git a/support/xpdfopen/pdfopen.c b/support/xpdfopen/pdfopen.c
new file mode 100644
index 0000000000..664a406166
--- /dev/null
+++ b/support/xpdfopen/pdfopen.c
@@ -0,0 +1,681 @@
+/*
+ * pdfopen.c.
+ *
+ * Heavily modified by Jim Diamond (jim.diamond@acadiau.ca) 2010/04/11
+ * to work with AR 9, xpdf and evince.
+ * (Also some mods by Peter Breitenlohner <tex-live@tug.org> from 2009
+ * and suggestions from 2010, and some suggestions from Karl Berry
+ * (2011).)
+ *
+ * Note 1: the calling sequence was changed considerably to match what
+ * the program actually seemed to do as well as what made sense to me.
+ *
+ * Note 2: if there are two instances of acroread displaying that file,
+ * only one is reloaded.
+ *
+ * Note 3: Unlike xpdf and AR[5,7,8,9], okular will not accept X events
+ * from this program unless the okular window has focus. On my system
+ * (Slackware64 13.0, using fvwm2), the functions I added to sendx.c to try
+ * to change the focus to the okular window do not work. (See also the
+ * comments in sendx.c.) If anyone cares to contribute working code,
+ * please send it to me for inclusion.
+
+ * Further modified 2010/05/03 to work with ar5, and to include some code
+ * that does *not* work with okular (see note below).
+ *
+ * Modified 2014/05/16 (V 0.84) to add capability to open a pdf file
+ * at a specified page (currently working with AR9, xpdf and evince).
+ *
+ * Modified 2014/05/22 (V 0.85) to allow '--file' option, for
+ * "compatibility" with windows pdfopen/pdfclose calling sequence.
+ * Drive by: allow '--viewer' for consistency's sake.
+ * Also allow '-V', '-?' and '--page' for compatibility.
+ * Added --goto/-g option... this works in (at least) xpdf 3.03,
+ * evince 3.12.1.
+ *
+ * Modified 2014/05/24 (V 0.86) so that -g works in AR 9.5.5 and
+ * possibly other AR9 versions.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "sendx.h"
+#include "xpdfopen.h"
+#include "utils.h"
+
+#define DEFINE_EXTERNS
+#include "externs.h"
+
+#define MAX_RELOAD_CMDS 10
+#define MAX_PRE_PAGE_OPTIONS 2 /* See struct defn below */
+#define MAX_POST_PAGE_OPTIONS 1 /* See struct defn below */
+#define MAX_PRE_DEST_OPTIONS 2 /* See struct defn below */
+#define MAX_POST_DEST_OPTIONS 1 /* See struct defn below */
+ /* Overkill ... */
+#define MAX_OPTIONS (10 + MAX_PRE_PAGE_OPTIONS \
+ + MAX_POST_PAGE_OPTIONS + MAX_PRE_DEST_OPTIONS \
+ + MAX_POST_DEST_OPTIONS)
+
+/* A string unlikely to be used as a real command by a PDF viewer */
+#define FORK_EXEC_CMD "*FORK EXEC*"
+
+
+/* Used in the pdf_viewer_t type... see comment below */
+typedef enum
+{
+ USE_SENDX, USE_FORK_EXEC
+} reload_method_t;
+
+
+/*
+ * This struct contains the information needed for both
+ * (a) initially starting up a PDF viewer, and
+ * (b) telling the viewer to reload the document.
+ * Of course, the viewer has to support that functionality.
+ * Two possible ways are supported.
+ * (a) Send the viewer's window (a) simulated keypress(ss)
+ * (b) fork()/exec() a second instance of the program which somehow signals
+ * the running instance. Note that this is problematical if the
+ * arguments for the "reload" case are different than the options
+ * for the "start up the viewer" case, because the command-line
+ * (and environment variable) syntax have to be extended.
+ * Arguably, if both the start-up and reload can be done with shell
+ * commands, the user should just write a shell script and be done with
+ * it.
+ * These could be extended with this as well:
+ * (c) send a Unix signal to the currently running viewer.
+ * This would require augmenting reload_method_t and pdf_viewer_t.
+ */
+
+typedef struct
+{
+ const char * short_name; /* cmd line option name */
+ const char * program_name; /* program to exec() */
+ reload_method_t method; /* method to induce reload */
+ const char * window_name; /* when no file is viewed */
+ const char * window_name_fmt; /* fmt string for file viewed */
+ const char * exec_pre_options[MAX_OPTIONS]; /* before filename in cmd */
+ const char * exec_post_options[MAX_OPTIONS];/* after filename in cmd */
+ const char * reload_cmds[MAX_RELOAD_CMDS]; /* cmds to reload a file */
+ /*
+ * If the page number goes before the filename, use these options.
+ * The last one must be a printf format string with a %d and
+ * no other conversions specs.
+ * End list with a NULL.
+ */
+ const char * initial_page_pre_options[MAX_PRE_PAGE_OPTIONS + 1]; /* */
+ /*
+ * If the page number goes after the filename, use these options.
+ * The last one must be a printf format string with a %d and
+ * no other conversions specs.
+ * End list with a NULL.
+ */
+ const char * initial_page_post_options[MAX_POST_PAGE_OPTIONS + 1]; /* */
+ /* Ditto for named destinations */
+ const char * initial_dest_pre_options[MAX_PRE_DEST_OPTIONS + 1]; /* */
+ const char * initial_dest_post_options[MAX_POST_DEST_OPTIONS + 1]; /* */
+} pdf_viewer_t;
+
+
+/* Reload commands (or prefixes thereof) */
+#define CTRL "Ctrl-"
+#define ALT "Alt-"
+#define CTRL_ALT "Ctrl-Alt-"
+#define FN "Fn-"
+#define FOCUS_IN "FOCUS-IN"
+#define FOCUS_OUT "FOCUS-OUT"
+
+
+
+/*
+ * The first struct represents the default viewer with the default options.
+ */
+static
+pdf_viewer_t pdf_viewers[] =
+{
+ {"ar9", "acroread", USE_SENDX,
+ AR9_WIN_NAME, AR9_WIN_NAME_FMT,
+ {"-openInNewInstance", NULL},
+ {NULL},
+ {"Ctrl-R", NULL},
+ {"/a", "page=%d", NULL},
+ {NULL},
+ {"/a", "nameddest=%s", NULL},
+ {NULL}
+ },
+ {"ar9-tab", "acroread", USE_SENDX,
+ AR9_WIN_NAME, AR9_WIN_NAME_FMT,
+ {NULL},
+ {NULL},
+ {"Ctrl-R", NULL},
+ {"/a", "page=%d", NULL},
+ {NULL},
+ {"/a", "nameddest='%s'", NULL},
+ {NULL}
+ },
+ {"ar8", "acroread", USE_SENDX,
+ AR8_WIN_NAME, AR8_WIN_NAME_FMT,
+ {NULL},
+ {NULL},
+ {"Ctrl-W", FORK_EXEC_CMD, NULL},
+ {NULL},
+ {NULL},
+ {NULL},
+ {NULL}
+ },
+ {"ar7", "acroread", USE_SENDX,
+ AR7_WIN_NAME, AR7_WIN_NAME_FMT,
+ {NULL},
+ {NULL},
+ {"Ctrl-W", "Alt-Left", NULL},
+ {NULL},
+ {NULL},
+ {NULL},
+ {NULL}
+ },
+ {"ar5", "acroread", USE_SENDX,
+ AR5_WIN_NAME, AR5_WIN_NAME_FMT,
+ {NULL},
+ {NULL},
+ {"Ctrl-W", "Alt-Left", NULL},
+ {NULL},
+ {NULL},
+ {NULL},
+ {NULL}
+ },
+ /*
+ * xpdf also allows 'xpdf -remote tex -reload', but if that xpdf server
+ * is not running, rather than a failure return, a blank window is
+ * opened. This is not much good to us.
+ * Using "tex-server" as the server name is an arbitrary choice.
+ */
+ {"xpdf", "xpdf", USE_SENDX,
+ XPDF_WIN_NAME, XPDF_WIN_NAME_FMT,
+ {"-remote", "tex-server", NULL},
+ {NULL},
+ {"r", NULL},
+ {NULL},
+ {"%d", NULL},
+ {NULL},
+ {"+%s", NULL}
+ },
+ {"evince", "evince", USE_SENDX,
+ EVINCE_WIN_NAME, EVINCE_WIN_NAME_FMT,
+ {NULL},
+ {NULL},
+ {"Ctrl-R", NULL},
+ {"-i", "%d", NULL},
+ {NULL},
+ {"-n", "%s", NULL},
+ {NULL}
+ },
+#ifdef TRY_OCULAR
+ {"okular", "okular", USE_SENDX,
+ OKULAR_WIN_NAME, OKULAR_WIN_NAME_FMT,
+ {NULL},
+ {NULL},
+ {FOCUS_IN, "Fn-F5", FOCUS_OUT, NULL},
+ {NULL},
+ {NULL},
+ {NULL},
+ {NULL}
+ },
+#endif
+};
+
+#define DEFAULT_VIEWER (pdf_viewers[0].short_name)
+
+
+
+static void
+usage(void)
+{
+ int i;
+ int viewers = sizeof(pdf_viewers) / sizeof(pdf_viewers[0]);
+
+ fprintf(stderr, "This is version %s of %s.\n", VERSION, progname);
+ fprintf(stderr, "Usage:\n %s [-h|--help|-?]\n"
+ " Show this help and exit.\n\n", progname);
+ fprintf(stderr, " %s [-v|--version]\n"
+ " Show the version number and exit.\n\n", progname);
+ fprintf(stderr, " %s [-r|--reset_focus] [[-]-viewer <prog>]\n"
+ "\t\t[-p|--page <n>] [-g|--goto <named-dest>] "
+ "[--file] <file.pdf>\n", progname);
+ fprintf(stderr, " If the PDF viewer <prog> is displaying <file.pdf>, "
+ "reload that file;\n"
+ " Otherwise call <prog> to display <file.pdf>.\n"
+ " If '-r' or '--reset_focus' is used, attempt to "
+ "reset the input focus\n"
+ " after calling the viewer program.\n"
+ " If the viewer allows it and is not already "
+ "running, the argument\n of -p/--page specifies the "
+ "first page to display.\n"
+ " If the viewer allows it and is not already "
+ "running, the argument\n of -g/--goto specifies the "
+ "named destination to view.\n NOTE: -g overrides -p\n\n");
+ fprintf(stderr, "The default viewer program is %s.\n", DEFAULT_VIEWER);
+ fprintf(stderr, "Implemented viewers:");
+ for (i = 0; i < viewers - 1; i++)
+ fprintf(stderr, " '%s'", pdf_viewers[i].short_name);
+ fprintf(stderr, " and '%s'.\n", pdf_viewers[viewers - 1].short_name);
+ fprintf(stderr, "Note: ar9 uses acroread's -openInNewInstance argument,"
+ " ar9-tab does not.\n");
+}
+
+
+
+/*
+ * Send one "command" to the given window.
+ * The command can be of these forms (for X an upper case letter or arrow
+ * key, such as "Left", and D one or more digits; see sendx.c):
+ * - Ctrl-X
+ * - Alt-X
+ * - Ctrl-Atl-X
+ * - Fn-FD
+ * - FOCUS-[IN|OUT]
+ * - <string> (not starting with any of the above prefixes)
+ * Allow other variations on the cases of "Ctrl" and "Alt".
+ *
+ * Return 0 on success, non-0 otherwise.
+ */
+
+static int
+send_command(const char * window_name, const char * reload_cmd)
+{
+ int alt_len, ctrl_len, ctrl_alt_len, fn_len, focus_len;
+
+ if (!strcmp(reload_cmd, FORK_EXEC_CMD))
+ return 999;
+
+ ctrl_alt_len = strlen(CTRL_ALT);
+ if (!strncasecmp(CTRL_ALT, reload_cmd, ctrl_alt_len))
+ return sendx_controlalt_token(reload_cmd + ctrl_alt_len, window_name);
+
+ ctrl_len = strlen(CTRL);
+ if (!strncasecmp(CTRL, reload_cmd, ctrl_len))
+ return sendx_control_token(reload_cmd + ctrl_len, window_name);
+
+ alt_len = strlen(ALT);
+ if (!strncasecmp(ALT, reload_cmd, alt_len))
+ return sendx_alt_token(reload_cmd + alt_len, window_name);
+
+ fn_len = strlen(FN);
+ if (!strncasecmp(FN, reload_cmd, fn_len))
+ return sendx_token(reload_cmd + fn_len, window_name);
+
+ focus_len = strlen(FOCUS_IN);
+ if (!strncasecmp(FOCUS_IN, reload_cmd, focus_len))
+ return set_focus(window_name);
+
+ focus_len = strlen(FOCUS_OUT);
+ if (!strncasecmp(FOCUS_OUT, reload_cmd, focus_len))
+ return reset_focus();
+
+ return sendx_string(reload_cmd, window_name);
+}
+
+
+
+/*
+ * reload_file(): Try to reload the given file.
+ * Return 0 on success, non-0 otherwise.
+ *
+ * If reset_focus is true, (attempt to) reset the input focus to the
+ * window which had focus when this program was called. This is
+ * currently only implemented for the USE_SENDX case.
+ */
+
+static int
+reload_file(const char * filename, int viewer_index, int focus_reset)
+{
+ int success = 0;
+ pdf_viewer_t pdf_viewer = pdf_viewers[viewer_index];
+ const char ** reload_cmds = pdf_viewer.reload_cmds;
+ char * window_name;
+
+ switch (pdf_viewers[viewer_index].method)
+ {
+ case USE_SENDX:
+ window_name = make_window_name(pdf_viewer.window_name_fmt, filename);
+ if (window_name == NULL)
+ return 99;
+
+ if (focus_reset)
+ (void)set_focus(window_name);
+
+ while (*reload_cmds)
+ {
+ /*
+ * Note: there is a race condition in the sendx.c code which
+ * can cause the program to crash and burn with a 'BadWindow'
+ * fault. This has been observed for AR8 (but not AR7, oddly)
+ * after the ^W causes AR8 to change the window name. It seems
+ * that Window_With_Name() tries to access a BadWindow within
+ * its recursive call.
+ * If this continues to be a problem, a usleep() may make the
+ * problem go away.
+ */
+ success = ! send_command(window_name, *reload_cmds);
+ if (! success && reload_cmds != pdf_viewer.reload_cmds)
+ {
+ /*
+ * In some cases the window name might be changed because
+ * of the first command.
+ * For example, in AR7 the first command (^W) "closes"
+ * the document, and the window title changes to reflect
+ * the fact there is no document. So try that command with
+ * the "plain" window name.
+ */
+ success = ! send_command(pdf_viewer.window_name, *reload_cmds);
+ }
+ if (! success)
+ break;
+ reload_cmds++;
+ }
+ if (focus_reset)
+ (void)reset_focus();
+ break;
+
+ case USE_FORK_EXEC:
+ return 2; /* TODO: someone who cares can put some code here */
+ break;
+
+ default:
+ fprintf(stderr, "%s: internal error: invalid reload_method. Sorry.\n",
+ progname);
+ return 99;
+ break;
+ }
+
+ if (success)
+ return 0;
+
+ return 1;
+}
+
+/*
+ * view_file(): (Attempt to) reload or view the given filename.
+ *
+ * First call reload_file() in case the given viewer is already
+ * displaying the given file.
+ *
+ * If that is unsuccessful, try to start up a new instance of the
+ * given viewer with the given file.
+ *
+ * If dest != NULL, and the viewer allows it, on initial opening of
+ * the file go to that named destination.
+ * As of V 0.85, this is implemented for xpdf, evince, and, according
+ * to the man page for acroread 9.5.5, for ar9.
+ * Otherwise, if the viewer allows it, open the document at the given
+ * page.
+ */
+
+static int
+view_file(const char * filename, int viewer_index, int focus_reset,
+ int initial_page, char * dest)
+{
+ pid_t pid;
+ pdf_viewer_t pdf_viewer = pdf_viewers[viewer_index];
+ const char * argv_list[2 * MAX_OPTIONS + 3];
+ const char ** p;
+ int i = 0;
+
+ /* First try to reload the file */
+ if (reload_file(filename, viewer_index, focus_reset) == 0)
+ {
+ /* Success! */
+ return 0;
+ }
+
+ /*
+ * No luck re-loading the file.
+ * (Try to) start a new instance of the viewer program.
+ * Start by forking into the background.
+ */
+ pid = fork();
+ if (pid < 0)
+ {
+ fprintf(stderr, "%s: fork() failed!?\n", progname);
+ return 10;
+ }
+ if (pid > 0)
+ return 0;
+
+ /* Child code: */
+ argv_list[i++] = pdf_viewer.program_name;
+ for (p = pdf_viewer.exec_pre_options; *p;)
+ argv_list[i++] = *p++;
+
+ if (pdf_viewer.initial_page_pre_options[0] != NULL && dest == NULL)
+ {
+ int j = 0;
+ char * buf;
+
+ while (pdf_viewer.initial_page_pre_options[j + 1] != NULL)
+ argv_list[i++] = pdf_viewer.initial_page_pre_options[j++];
+
+ /* 10 is more than enough for the page number */
+ buf = malloc(strlen(pdf_viewer.initial_page_pre_options[j]) + 10);
+ if (buf != NULL)
+ {
+ /* 1e8 or more pages? I doubt it. */
+ sprintf(buf, pdf_viewer.initial_page_pre_options[j],
+ initial_page > 99999999 ? 1 : initial_page);
+ argv_list[i++] = buf;
+ }
+ else
+ fprintf(stderr, "%s: unable to allocate memory for page "
+ "specification!!\n"
+ "Crossing fingers and continuing.", progname);
+ }
+
+ if (pdf_viewer.initial_dest_pre_options[0] != NULL && dest != NULL)
+ {
+ int j = 0;
+ char * buf;
+
+ while (pdf_viewer.initial_dest_pre_options[j + 1] != NULL)
+ argv_list[i++] = pdf_viewer.initial_dest_pre_options[j++];
+
+ buf = malloc(strlen(pdf_viewer.initial_dest_pre_options[j])
+ + strlen(dest) + 1);
+ if (buf != NULL)
+ {
+ sprintf(buf, pdf_viewer.initial_dest_pre_options[j], dest);
+ argv_list[i++] = buf;
+ }
+ else
+ fprintf(stderr, "%s: unable to allocate memory for named-dest "
+ "specification!!\n"
+ "Crossing fingers and continuing.", progname);
+ }
+
+ argv_list[i++] = filename;
+
+ if (pdf_viewer.initial_page_post_options[0] != NULL && dest == NULL)
+ {
+ int j = 0;
+ char * buf;
+
+ while (pdf_viewer.initial_page_post_options[j + 1] != NULL)
+ argv_list[i++] = pdf_viewer.initial_page_post_options[j++];
+
+ buf = malloc(strlen(pdf_viewer.initial_page_post_options[j]) + 10);
+ if (buf != NULL)
+ {
+ sprintf(buf, pdf_viewer.initial_page_post_options[j],
+ initial_page > 99999999 ? 1 : initial_page);
+ argv_list[i++] = buf;
+ }
+ else
+ fprintf(stderr, "%s: unable to allocate memory for page "
+ "specification!!\n"
+ "Crossing fingers and continuing.", progname);
+ }
+
+ if (pdf_viewer.initial_dest_post_options[0] != NULL && dest != NULL)
+ {
+ int j = 0;
+ char * buf;
+
+ while (pdf_viewer.initial_dest_post_options[j + 1] != NULL)
+ argv_list[i++] = pdf_viewer.initial_dest_post_options[j++];
+
+ buf = malloc(strlen(pdf_viewer.initial_dest_post_options[j])
+ + strlen(dest) + 1);
+ if (buf != NULL)
+ {
+ sprintf(buf, pdf_viewer.initial_dest_post_options[j], dest);
+ argv_list[i++] = buf;
+ }
+ else
+ fprintf(stderr, "%s: unable to allocate memory for named-dest "
+ "specification!!\n"
+ "Crossing fingers and continuing.", progname);
+ }
+
+ argv_list[i++] = NULL;
+#ifdef DEBUG
+ {
+ int k = 0;
+ if (dest == NULL)
+ fprintf(stderr, "dest == NULL\n");
+ else
+ fprintf(stderr, "dest == '%s'\n", dest);
+ for (; k < i-1; k++)
+ fprintf(stderr, "%s\n", argv_list[k]);
+ }
+#endif
+ execvp(pdf_viewer.program_name, (char **)argv_list);
+
+ /* If we are here, Something Bad happened. */
+ fprintf(stderr, "%s: %s startup failed\n", progname,
+ pdf_viewer.program_name);
+ return 5;
+}
+
+
+
+int
+main(int argc, char * argv[])
+{
+ const char * viewer = pdf_viewers[0].short_name;
+ char * env_viewer;
+ char * file = NULL;
+ char * dest = NULL;
+ int i;
+ int initial_page = 1;
+ int viewer_index = -1;
+ int focus_reset = 0;
+
+ progname = argv[0];
+ argc--;
+ argv++;
+
+ env_viewer = getenv("PDF_VIEWER");
+ if (env_viewer != NULL)
+ viewer = env_viewer;
+
+ if (argc < 1 || !strcmp(argv[0], "-h") || !strcmp(argv[0], "--help")
+ || !strcmp(argv[0], "-?"))
+ {
+ usage();
+ return argc < 1 ? EXIT_FAILURE : EXIT_SUCCESS;
+ }
+
+ if (!strcmp(argv[0], "-v") || !strcmp(argv[0], "-V")
+ || !strcmp(argv[0], "--version"))
+ {
+ printf("This is pdfopen version %s\n", VERSION);
+ return EXIT_SUCCESS;
+ }
+
+ while (argc > 0)
+ {
+ if (!strcmp(argv[0], "--reset_focus") || !strcmp(argv[0], "-r"))
+ {
+ argc--;
+ argv++;
+ focus_reset = 1;
+ continue;
+ }
+
+ if (argc >= 2
+ && (!strcmp(argv[0], "-viewer") || !strcmp(argv[0], "--viewer")))
+ {
+ viewer = argv[1];
+ argc -= 2;
+ argv += 2;
+ continue;
+ }
+
+ if (argc >= 2
+ && (!strcmp(argv[0], "-g") || !strcmp(argv[0], "--goto")))
+ {
+ dest = argv[1];
+ argc -= 2;
+ argv += 2;
+ continue;
+ }
+
+ if (argc >= 2 && !strcmp(argv[0], "--file"))
+ {
+ file = argv[1];
+ argc -= 2;
+ argv += 2;
+ continue;
+ }
+
+ if (argc >= 2
+ && (!strcmp(argv[0], "-p") || !strcmp(argv[0], "--page")))
+ {
+ initial_page = atoi(argv[1]);
+ argc -= 2;
+ argv += 2;
+ continue;
+ }
+
+ break;
+ }
+
+ /*
+ * Should either have no args left (--file seen above) or
+ * --file was not seen and there is one arg left.
+ */
+ if ( ! ((argc == 0 && file != NULL) || (argc == 1 && file == NULL)) )
+ {
+ fprintf(stderr, "%s: invalid argument(s)\n", progname);
+ usage();
+ return EXIT_FAILURE;
+ }
+
+ if (argc == 1)
+ file = argv[0];
+
+ for (i = 0; i < sizeof(pdf_viewers) / sizeof(pdf_viewers[0]); i++)
+ {
+ if (!strcmp(pdf_viewers[i].short_name, viewer))
+ {
+ viewer_index = i;
+ break;
+ }
+ }
+
+ if (viewer_index < 0)
+ {
+ fprintf(stderr, "%s: Unknown PDF viewer '%s'\n", progname, viewer);
+ usage();
+
+ return EXIT_FAILURE;
+ }
+
+ if (view_file(file, viewer_index, focus_reset, initial_page, dest))
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+}