#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2010 Jose Aliste # 2011 Benjamin Kellermann # # Modified for the use with LyX by Juergen Spitzmueller 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' %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: