- 追加された行はこの色です。
- 削除された行はこの色です。
[[Evince]] > [[fwdevince>Evince/fwdevince]] > Python
*fwdevince の例示ソース [#u6122dc4]
// ずっと疑問なんですが,なんで TeX Wiki に展示したがるんでしょうね.
// Github にでも置いてリンクすればいいと思うのですが.
// その方がバージョン管理も楽だし,コピペしないでダウンロードするだけでいいし. -- kmaeda (2016/01/09)
fwdevince(Evince で forward and inverse search を行うツール)の Python による例示ソースです.
[[Fedora>Linux/Fedora]] 26 の Evince 3.24.0, Python 3.6.2, dbus-python 1.2.4, PyGObject 3.24.1 で動作確認しています.
なお https://www.freedesktop.org/wiki/Software/DBusBindings/ によると dbus-pyton は Obsolete libraries で New applications should use pydbus, txdbus or GDBus/QtDBus bindings. とのことです.
なお https://www.freedesktop.org/wiki/Software/DBusBindings/ によると dbus-python は Obsolete libraries で New applications should use pydbus, txdbus or GDBus/QtDBus bindings. とのことです.
うまく動作しない場合は
-コマンドラインから python3 --version を実行して Python のバージョンを確認して Python 3.5 以降でなければ Python 3.5 以降にアップグレードする
-[[Okular]], [[zathura]], [[qpdfview]] などの Evince 以外の SyncTeX 対応ビューアで forward and inverse search を実行する
-[[TeXstudio]], [[Texmaker]], [[TeXworks]] の組み込みビューアで forward and inverse search を実行する
-fwdevince をうまく動作するように修正する
//--例えば端末から $ python -V とした時に Python 2.7.6 と表示される環境だと urllib.parse の読み込みに失敗します.その場合は python3 をインストールし,先頭行の #!/usr/bin/env python を #!/usr/bin/env python3 などと変更する.
などを試みてください.
// オリジナルが GPL なので,ライセンス表示を消してはならない.
// 最初に書いた通り置き場所も検討されるべきだが,とりあえず.
// -- kmaeda (2016/05/08)
----
-fwdevince
----
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2010 Jose Aliste
# 2011 Benjamin Kellermann
#
# 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
from gi.repository import GLib, Gio
import dbus
import argparse
import pathlib
import traceback
import sys
import urllib.parse
class EvinceForwardSearch:
def parse_args(self):
parser = argparse.ArgumentParser(description='Forward search with Evince')
parser.add_argument('pdf', nargs=1, help='PDF file')
parser.add_argument('line', nargs=1, type=int, help='Line')
parser.add_argument('tex', nargs=1, help='TeX file')
return parser.parse_args()
def run(self):
args = self.parse_args()
pdf = str(pathlib.Path(args.pdf[0]).resolve()).replace(" ", "%20")
line = int(args.line[0])
tex = str(pathlib.Path(args.tex[0]).resolve().parent / pathlib.Path('./') / pathlib.Path(args.tex[0]).resolve().name)
try:
import time
bus = dbus.SessionBus()
daemon = bus.get_object('org.gnome.evince.Daemon', '/org/gnome/evince/Daemon')
dbus_name = daemon.FindDocument('file://' + urllib.parse.quote(pdf, safe="%/:=&?~#+!$,;'@()*[]"), True, dbus_interface='org.gnome.evince.Daemon')
window = bus.get_object(dbus_name, '/org/gnome/evince/Window/0')
time.sleep(0.2)
window.SyncView(tex, (line, 1), 0, dbus_interface='org.gnome.evince.Window')
except dbus.DBusException:
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
daemon = Gio.DBusProxy.new_sync(bus, 0, None, 'org.gnome.evince.Daemon', '/org/gnome/evince/Daemon', 'org.gnome.evince.Daemon', None)
dbus_name = daemon.call_sync('FindDocument', GLib.Variant('(sb)', ('file://' + urllib.parse.quote(pdf, safe="%/:=&?~#+!$,;'@()*[]"), True)), Gio.DBusSignalFlags.NONE, -1, None)
dbus_name = dbus_name.unpack()[0]
window = Gio.DBusProxy.new_sync(bus, 0, None, dbus_name, '/org/gnome/evince/Window/0', 'org.gnome.evince.Window', None)
window.call_sync('SyncView', GLib.Variant('(s(ii)u)', (tex, (line, 1), 0)), Gio.DBusSignalFlags.NONE, -1, None)
except Exception:
traceback.print_exc()
class EvinceInverseSearch:
def parse_args(self):
parser = argparse.ArgumentParser(description='Inverse search with Evince')
parser.add_argument('pdf', nargs=1, help='PDF file')
parser.add_argument('editor', nargs=1, help='Editor command')
return parser.parse_args()
def run(self):
import dbus.mainloop.glib
from gi.repository import GObject
args = self.parse_args()
pdf = str(pathlib.Path(args.pdf[0]).resolve())
editor = args.editor[0]
import dbus.mainloop.glib
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
a = EvinceWindowProxy('file://' + pdf, editor, True)
loop = GObject.MainLoop()
loop.run()
GLib.MainLoop().run()
class EvinceWindowProxy:
"""A Dbus proxy for an Evince Window."""
daemon = None
bus = None
RUNNING = range(2)
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'
def __init__(self, uri, editor, apawn=False, logger=None):
def __init__(self, uri, editor, spawn=False, logger=None):
self._log = logger
self.uri = uri.replace(" ", "%20")
self.editor = editor
self.status = self.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(self.EV_DAEMON_NAME,
self.EV_DAEMON_PATH,
follow_name_owner_changes=True)
EvinceWindowProxy.bus.add_signal_receiver(self._on_doc_loaded,
signal_name='DocumentLoaded',
dbus_interface=self.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 uri == self.uri 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 = self.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 = self.RUNNING
self.evince = EvinceWindowProxy.bus.get_object(self.dbus_name, self.EVINCE_PATH)
self.evince.GetWindowList(dbus_interface = self.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,self.EV_WINDOW_IFACE)
self.window.connect_to_signal("SyncSource", self.on_sync_source)
else:
#That should never happen.
if self._log:
self._log.debug("GetWindowList returned empty list")
def on_sync_source(self, input_file, source_link, timestamp):
import subprocess
import re
print(input_file + ':' + str(source_link[0]))
# This is probably useless
input_file = input_file.replace("%20", " ")
# 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
#input_file = urllib.parse.unquote(input_file.encode('ascii'))
input_file = urllib.parse.unquote(input_file)
print(type(input_file), input_file)
#cmd = re.sub("%f", input_file, self.editor)
cmd = re.sub("%f", input_file.replace('file://', ''), self.editor)
cmd = re.sub("%l", str(source_link[0]), cmd)
print(cmd)
subprocess.run(cmd, shell=True)
if self.source_handler is not None:
self.source_handler(input_file, source_link, timestamp)
if __name__ == '__main__':
import sys
cmd = pathlib.Path(sys.argv[0]).name
if cmd == 'fwdevince' or cmd == 'evince_forward_search':
EvinceForwardSearch().run()
elif cmd == 'invevince' or cmd == 'bwdevince' or cmd == 'evince_inverse_search' or cmd == 'evince_backward_search':
EvinceInverseSearch().run()
else:
sys.stderr.write("rename 'fwdevince' or 'invevince'\n")
sys.exit(1)
----
$ chmod +x fwdevince
$ sudo cp -p fwdevince /usr/local/bin
$ sudo ln -s /usr/local/bin/fwdevince /usr/local/bin/invevince
fwdevince は例えば
$ fwdevince hoge.pdf 30 hoge.tex
のように実行すると Evince が起動して PDF ファイルの対応する行が赤い枠で囲まれて表示されます.
invevince は例えば [[TeXstudio]] の場合は
$ evince "hoge.pdf" &
$ invevince "hoge.pdf" "texstudio '%f' -line %l"
のように実行して Evince 上で Ctrl + 左クリックを実行すると TeXstudio が起動して TeX 文書の対応する行にジャンプします.~