]> git.lyx.org Git - lyx.git/commitdiff
Scripts for connecting LyX with evince for backward/forward search
authorJuergen Spitzmueller <spitz@lyx.org>
Tue, 18 Jul 2017 17:08:44 +0000 (19:08 +0200)
committerJuergen Spitzmueller <spitz@lyx.org>
Tue, 18 Jul 2017 17:08:44 +0000 (19:08 +0200)
The scripts have been initially developed by Benjamin Kellermann (2011)
as a derivation of gedit-synctex-plugin by José Aliste
(https://github.com/jaliste/gedit-synctex-plugin, 2010) and published on
https://ubuntuforums.org/showthread.php?t=1716268.

The work here is based on a further derivation of this work for Sublime
Text LaTeX Tools
(https://github.com/SublimeText/LaTeXTools/tree/master/evince).

Adaptations for the use with LyX as well as the initial translation of
the evince_sync bash script to python have been done by myself.

The python code (particularly evince_sync_lyx) needs audit!

3rdparty/evince_sync/README [new file with mode: 0644]
3rdparty/evince_sync/evince_backward_search [new file with mode: 0755]
3rdparty/evince_sync/evince_forward_search [new file with mode: 0755]
3rdparty/evince_sync/evince_sync_lyx [new file with mode: 0755]

diff --git a/3rdparty/evince_sync/README b/3rdparty/evince_sync/README
new file mode 100644 (file)
index 0000000..84b3b4f
--- /dev/null
@@ -0,0 +1,49 @@
+===============================================
+FORWARD AND BACKWARD SEARCH WITH LYX AND EVINCE
+===============================================
+
+For the forward and backwards (reverse) search feature in general, please refer to
+Help > Additional Features, sec. 5.6 and 5.7 in LyX.
+
+
+SETUP
+=====
+
+* Install the files evince_sync_lyx, evince_forward_search and evince_backward_search
+  in your binary directory (e.g., ~/bin).
+
+* Assure all three files are executable.
+
+* In LyX, go to Tools > Preferences ... > File Handling > File Formats > Format, select
+  the appropriate output format [e.g., PDF (pdflatex)], set "Viewer" to "Custom" and
+  enter evince_sync_lyx as custom viewer (in the field right to the combo box).
+  Hit "Apply" and "Save".
+
+* Go to Tools > Preferences ... > Output > General and enter the following PDF command
+  to Forward Search: evince_forward_search "$$o" $$n "$$f"
+  Again, hit "Apply" and "Save".
+
+Forward and Backward search should work now (backward search from within evince is triggered
+by <Shift> + <Left Click>).
+
+
+HISTORY
+=======
+
+The scripts have been initially developed by Benjamin Kellermann (2011) as a derivation
+of gedit-synctex-plugin by José Aliste (https://github.com/jaliste/gedit-synctex-plugin,
+2010) and published on https://ubuntuforums.org/showthread.php?t=1716268.
+
+The work is based on a further derivation of this work for Sublime Text LaTeX Tools
+(https://github.com/SublimeText/LaTeXTools/tree/master/evince).
+
+Adaptations for the use with LyX have been done by Jürgen Spitzmüller <spitz@lyx.org>
+in 2017. 
+
+
+CONTACT
+=======
+
+Please send bug reports and suggestions (related to LyX-evince synchronization) to
+lyx-devel@lists.lyx.org. 
+Usage questions should be addressed at lyx-users@lists.lyx.org.
diff --git a/3rdparty/evince_sync/evince_backward_search b/3rdparty/evince_sync/evince_backward_search
new file mode 100755 (executable)
index 0000000..7d00a10
--- /dev/null
@@ -0,0 +1,212 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2010 Jose Aliste <jose.aliste@gmail.com>
+#               2011 Benjamin Kellermann <Benjamin.Kellermann@tu-dresden.de>
+#
+# Modified for the use with LyX by Juergen Spitzmueller <spitz@lyx.org> 2017.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public Licence as published by the Free Software
+# Foundation; either version 2 of the Licence, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public Licence for more
+# details.
+#
+# You should have received a copy of the GNU General Public Licence along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+# Incorporates fixes from http://ubuntuforums.org/showthread.php?t=1716268
+from __future__ import print_function
+
+import dbus
+import subprocess
+import time
+import sys
+import traceback
+
+if sys.version_info < (3,):
+    from urllib import unquote as urllib_unquote
+    from urllib import quote
+
+    def unquote(s):
+        # This is to deal with source files with non-ascii names
+        # We get url-quoted UTF-8 from dbus; convert to url-quoted ascii
+        # and then unquote. If you don't first convert ot ascii, it fails.
+        # It's a bit magical, but it seems to work
+        return urllib_unquote(s.encode('ascii'))
+else:
+    from urllib.parse import unquote
+    from urllib.parse import quote
+
+RUNNING, CLOSED = range(2)
+
+EV_DAEMON_PATH = "/org/gnome/evince/Daemon"
+EV_DAEMON_NAME = "org.gnome.evince.Daemon"
+EV_DAEMON_IFACE = "org.gnome.evince.Daemon"
+
+EVINCE_PATH = "/org/gnome/evince/Evince"
+EVINCE_IFACE = "org.gnome.evince.Application"
+
+EV_WINDOW_IFACE = "org.gnome.evince.Window"
+
+
+class EvinceWindowProxy:
+
+    """A DBus proxy for an Evince Window."""
+    daemon = None
+    bus = None
+
+    def __init__(self, uri, editor, spawn=False, logger=None):
+        self._log = logger
+        self.uri = uri
+        self.uri_unquoted = unquote(uri)
+        self.editor = editor
+        self.status = CLOSED
+        self.source_handler = None
+        self.dbus_name = ''
+        self._handler = None
+
+        try:
+            if EvinceWindowProxy.bus is None:
+                EvinceWindowProxy.bus = dbus.SessionBus()
+
+            if EvinceWindowProxy.daemon is None:
+                EvinceWindowProxy.daemon = \
+                    EvinceWindowProxy.bus.get_object(
+                        EV_DAEMON_NAME,
+                        EV_DAEMON_PATH,
+                        follow_name_owner_changes=True
+                    )
+
+            EvinceWindowProxy.bus.add_signal_receiver(
+                self._on_doc_loaded,
+                signal_name='DocumentLoaded',
+                dbus_interface=EV_WINDOW_IFACE,
+                sender_keyword='sender'
+            )
+            self._get_dbus_name(False)
+        except dbus.DBusException:
+            traceback.print_exc()
+            if self._log:
+                self._log.debug("Could not connect to the Evince Daemon")
+
+    def _on_doc_loaded(self, uri, **keyargs):
+        if (
+            unquote(uri) == self.uri_unquoted and
+            self._handler is None
+        ):
+            self.handle_find_document_reply(keyargs['sender'])
+
+    def _get_dbus_name(self, spawn):
+        EvinceWindowProxy.daemon.FindDocument(
+            self.uri,
+            spawn,
+            reply_handler=self.handle_find_document_reply,
+            error_handler=self.handle_find_document_error,
+            dbus_interface=EV_DAEMON_IFACE
+        )
+
+    def handle_find_document_error(self, error):
+        if self._log:
+            self._log.debug("FindDocument DBus call has failed")
+
+    def handle_find_document_reply(self, evince_name):
+        if self._handler is not None:
+            handler = self._handler
+        else:
+            handler = self.handle_get_window_list_reply
+        if evince_name != '':
+            self.dbus_name = evince_name
+            self.status = RUNNING
+            self.evince = EvinceWindowProxy.bus.get_object(
+                self.dbus_name, EVINCE_PATH
+            )
+
+            self.evince.GetWindowList(
+                dbus_interface=EVINCE_IFACE,
+                reply_handler=handler,
+                error_handler=self.handle_get_window_list_error
+            )
+
+    def handle_get_window_list_error(self, e):
+        if self._log:
+            self._log.debug("GetWindowList DBus call has failed")
+
+    def handle_get_window_list_reply(self, window_list):
+        if len(window_list) > 0:
+            window_obj = EvinceWindowProxy.bus.get_object(
+                self.dbus_name, window_list[0]
+            )
+            self.window = dbus.Interface(window_obj, EV_WINDOW_IFACE)
+            self.window.connect_to_signal("SyncSource", self.on_sync_source)
+        else:
+            # This should never happen.
+            if self._log:
+                self._log.debug("GetWindowList returned empty list")
+
+    def on_sync_source(self, input_file, source_link, _):
+        input_file = unquote(input_file)
+        # Remove the "file://" prefix
+        input_file = input_file[7:]
+        #print("File: " + input_file + ":" + str(source_link[0]))
+        cmd = self.editor.replace('%f', input_file)
+        cmd = cmd.replace('%l', str(source_link[0]))
+        #print(cmd)
+        subprocess.call(cmd, shell=True)
+        if self.source_handler is not None:
+            self.source_handler(input_file, source_link, time)
+
+
+# This file offers backward search in any editor.
+#  evince_dbus pdf_file line_source input_file
+if __name__ == '__main__':
+    import dbus.mainloop.glib
+    import sys
+    import os
+
+    def print_usage():
+        print("""Usage:
+  evince_backward_search pdf_file "editorcmd %f %l"'
+    %f ... TeX-file to load
+    %l ... line to jump to
+E.g.:
+  evince_backward_search somepdf.pdf "gvim --servername somepdf --remote-silent '+%l<Enter>' %f"
+  evince_backward_search somepdf.pdf "emacsclient -a emacs --no-wait +%l %f"
+  evince_backward_search somepdf.pdf "scite %f '-goto:%l'"
+  evince_backward_search somepdf.pdf "lyxclient -g %f %l"
+  evince_backward_search somepdf.pdf "kate --use --line %l"
+  evince_backward_search somepdf.pdf "kile --line %l" """)
+        sys.exit(1)
+
+    if len(sys.argv) != 3:
+        print_usage()
+
+    pdf_file = os.path.abspath(sys.argv[1])
+
+    if not os.path.isfile(pdf_file):
+        print_usage()
+
+    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+    EvinceWindowProxy(
+        # The PDF file name MUST be URI-encoded
+        # RFC 1738: unreserved = alpha | digit | safe | extra
+        #           safe       = "$" | "-" | "_" | "." | "+"
+        #           extra      = "!" | "*" | "'" | "(" | ")" | ","
+        'file://' + quote(pdf_file, "/$+!*'(),@=~"),
+        sys.argv[2],
+        True
+    )
+
+    try:
+        import gobject
+        loop = gobject.MainLoop()
+    except ImportError:
+        from gi.repository import GLib
+        loop = GLib.MainLoop()
+    loop.run()
+# ex:ts=4:et:
diff --git a/3rdparty/evince_sync/evince_forward_search b/3rdparty/evince_sync/evince_forward_search
new file mode 100755 (executable)
index 0000000..bee4f2d
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2010 Jose Aliste <jose.aliste@gmail.com>
+#               2011 Benjamin Kellermann <Benjamin.Kellermann@tu-dresden.de>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public Licence as published by the Free Software
+# Foundation; either version 2 of the Licence, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public Licence for more 
+# details.
+#
+# You should have received a copy of the GNU General Public Licence along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+# This file offers forward search for evince.
+from __future__ import print_function
+
+import dbus
+import sys
+import os
+import traceback
+
+if sys.version_info < (3,):
+    from urllib import quote
+else:
+    from urllib.parse import quote
+
+if __name__ == '__main__':
+    def print_usage():
+        print('Usage: evince_forward_search pdf_file line_number tex_file')
+        sys.exit(1)
+
+    if len(sys.argv) != 4:
+        print_usage()
+    try:
+        line_number = int(sys.argv[2])
+    except ValueError:
+        print_usage()
+
+    pdf_file = os.path.abspath(sys.argv[1])
+    line = int(sys.argv[2])
+    tex_file = os.path.abspath(sys.argv[3])
+
+    try:
+        bus = dbus.SessionBus()
+        daemon = bus.get_object(
+            'org.gnome.evince.Daemon', '/org/gnome/evince/Daemon'
+        )
+        dbus_name = daemon.FindDocument(
+            # The PDF file name MUST be URI-encoded
+            # RFC 1738: unreserved = alpha | digit | safe | extra
+            #           safe       = "$" | "-" | "_" | "." | "+"
+            #           extra      = "!" | "*" | "'" | "(" | ")" | ","
+            'file://' + quote(pdf_file, "/$+!*'(),@=~"),
+            True,
+            dbus_interface="org.gnome.evince.Daemon"
+        )
+        window = bus.get_object(dbus_name, '/org/gnome/evince/Window/0')
+        window.SyncView(
+            tex_file,
+            (line_number, 1),
+            0,  # GDK_CURRENT_TIME constant
+            dbus_interface="org.gnome.evince.Window"
+        )
+    except dbus.DBusException:
+        traceback.print_exc()
diff --git a/3rdparty/evince_sync/evince_sync_lyx b/3rdparty/evince_sync/evince_sync_lyx
new file mode 100755 (executable)
index 0000000..83b8fef
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/python
+
+# Copyright (C) 2010 Jose Aliste <jose.aliste@gmail.com>
+#               2011 Benjamin Kellermann <Benjamin.Kellermann@tu-dresden.de>
+#
+# Translated from Bash to Python by Juergen Spitzmueller <spitz@lyx.org> 2017.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public Licence as published by the Free Software
+# Foundation; either version 2 of the Licence, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public Licence for more 
+# details.
+#
+# You should have received a copy of the GNU General Public Licence along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+import sys, os.path
+from subprocess import Popen, call
+
+editor_cmd = "lyxclient -g %f %l"
+
+def print_usage():
+    print("Usage: evince_sync_lyx pdf_file")
+    sys.exit(1)
+
+if len(sys.argv) != 2:
+    print_usage()
+
+pdf_file = os.path.abspath(sys.argv[1])
+
+if not os.path.isfile(pdf_file):
+    print_usage()
+
+synctex_file, ext = os.path.splitext(pdf_file)
+
+synctex_file += ".synctex.gz"
+
+SyncTeX = False
+
+if os.path.isfile(synctex_file):
+    bsproc = Popen(["evince_backward_search", pdf_file, editor_cmd])
+    SyncTeX = True
+
+vproc = Popen(['evince', pdf_file])
+vproc.wait()
+
+if SyncTeX:
+    bsproc.terminate()
+