Use the new event loop based Listener

This means one less thread in the main and viewer UIs and no more polling for
messages.
This commit is contained in:
Kovid Goyal 2020-10-20 12:45:24 +05:30
parent cb88451596
commit 9aeaacfb01
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 163 additions and 316 deletions

View File

@ -34,24 +34,11 @@ def option_parser_for(cmd, args=()):
return cmd_option_parser
def send_message(msg=''):
prints('Notifying calibre of the change')
from calibre.utils.ipc import RC
t = RC(print_error=False)
t.start()
t.join(3)
if t.done:
t.conn.send('refreshdb:' + msg)
t.conn.close()
def run_cmd(cmd, opts, args, dbctx):
m = module_for_cmd(cmd)
if dbctx.is_remote and getattr(m, 'no_remote', False):
raise SystemExit(_('The {} command is not supported with remote (server based) libraries').format(cmd))
ret = m.main(opts, args, dbctx)
# if not dbctx.is_remote and not opts.dont_notify_gui and not getattr(m, 'readonly', False):
# send_message()
return ret

View File

@ -5,7 +5,6 @@
import os
import re
import socket
import sys
import time
import traceback
@ -22,10 +21,10 @@ from calibre.gui2 import (
Application, choose_dir, error_dialog, gprefs, initialize_file_icon_provider,
question_dialog, setup_gui_option_parser
)
from calibre.gui2.listener import send_message_in_process
from calibre.gui2.main_window import option_parser as _option_parser
from calibre.gui2.splash_screen import SplashScreen
from calibre.utils.config import dynamic, prefs
from calibre.utils.ipc import RC, gui_socket_address
from calibre.utils.lock import SingleInstance
from calibre.utils.monotonic import monotonic
from polyglot.builtins import as_bytes, environ_item, range, unicode_type
@ -211,10 +210,10 @@ class GuiRunner(QObject):
'''Make sure an event loop is running before starting the main work of
initialization'''
def __init__(self, opts, args, actions, listener, app, gui_debug=None):
def __init__(self, opts, args, actions, app, gui_debug=None):
self.startup_time = monotonic()
self.timed_print('Starting up...')
self.opts, self.args, self.listener, self.app = opts, args, listener, app
self.opts, self.args, self.app = opts, args, app
self.gui_debug = gui_debug
self.actions = actions
self.main = None
@ -234,7 +233,7 @@ class GuiRunner(QObject):
self.splash_screen.show_message(_('Initializing user interface...'))
try:
with gprefs: # Only write gui.json after initialization is complete
main.initialize(self.library_path, db, self.listener, self.actions)
main.initialize(self.library_path, db, self.actions)
finally:
self.timed_print('main UI initialized...')
if self.splash_screen is not None:
@ -364,8 +363,8 @@ def run_in_debug_mode():
stderr=subprocess.STDOUT, stdin=lopen(os.devnull, 'rb'))
def run_gui(opts, args, listener, app, gui_debug=None):
with listener, SingleInstance('db') as si:
def run_gui(opts, args, app, gui_debug=None):
with SingleInstance('db') as si:
if not si:
ext = '.exe' if iswindows else ''
error_dialog(None, _('Cannot start calibre'), _(
@ -375,10 +374,10 @@ def run_gui(opts, args, listener, app, gui_debug=None):
' program is running, try restarting your computer.').format(
'calibre-server' + ext, 'calibredb' + ext), show=True)
return 1
run_gui_(opts, args, listener, app, gui_debug)
run_gui_(opts, args, app, gui_debug)
def run_gui_(opts, args, listener, app, gui_debug=None):
def run_gui_(opts, args, app, gui_debug=None):
initialize_file_icon_provider()
app.load_builtin_fonts(scan_for_fonts=True)
if not dynamic.get('welcome_wizard_was_run', False):
@ -390,7 +389,7 @@ def run_gui_(opts, args, listener, app, gui_debug=None):
actions = tuple(Main.create_application_menubar())
else:
actions = tuple(Main.get_menubar_actions())
runner = GuiRunner(opts, args, actions, listener, app, gui_debug=gui_debug)
runner = GuiRunner(opts, args, actions, app, gui_debug=gui_debug)
ret = app.exec_()
if getattr(runner.main, 'run_wizard_b4_shutdown', False):
from calibre.gui2.wizard import wizard
@ -421,80 +420,42 @@ def run_gui_(opts, args, listener, app, gui_debug=None):
singleinstance_name = 'GUI'
def cant_start(msg=_('If you are sure it is not running')+', ',
det_msg=_('Timed out waiting for response from running calibre'),
listener_failed=False):
base = '<p>%s</p><p>%s %s'
where = __appname__ + ' '+_('may be running in the system tray, in the')+' '
if ismacos:
where += _('upper right region of the screen.')
else:
where += _('lower right region of the screen.')
if iswindows or islinux:
what = _('try rebooting your computer.')
else:
if listener_failed:
path = gui_socket_address()
else:
from calibre.utils.lock import singleinstance_path
path = singleinstance_path(singleinstance_name)
what = _('try deleting the file: "%s"') % path
info = base%(where, msg, what)
error_dialog(None, _('Cannot start ')+__appname__,
'<p>'+(_('%s is already running.')%__appname__)+'</p>'+info, det_msg=det_msg, show=True)
raise SystemExit(1)
def send_message(msg):
try:
send_message_in_process(msg)
except Exception as err:
print(_('Failed to contact running instance of calibre'), file=sys.stderr, flush=True)
print(err, file=sys.stderr, flush=True)
error_dialog(None, _('Contacting calibre failed'), _(
'Failed to contact running instance of calibre, try restarting calibre'),
det_msg=str(err), show=True)
return False
return True
def build_pipe(print_error=True):
t = RC(print_error=print_error)
t.start()
t.join(3.0)
if t.is_alive():
cant_start()
raise SystemExit(1)
return t
def shutdown_other(rc=None):
if rc is None:
rc = build_pipe(print_error=False)
if rc.conn is None:
prints(_('No running calibre found'))
return # No running instance found
rc.conn.send(b'shutdown:')
prints(_('Shutdown command sent, waiting for shutdown...'))
for i in range(50):
with SingleInstance(singleinstance_name) as si:
if si:
return
time.sleep(0.1)
prints(_('Failed to shutdown running calibre instance'))
raise SystemExit(1)
def shutdown_other():
if send_message('shutdown:'):
print(_('Shutdown command sent, waiting for shutdown...'), flush=True)
for i in range(50):
with SingleInstance(singleinstance_name) as si:
if si:
return
time.sleep(0.1)
raise SystemExit(_('Failed to shutdown running calibre instance'))
def communicate(opts, args):
t = build_pipe()
if opts.shutdown_running_calibre:
shutdown_other(t)
shutdown_other()
else:
if len(args) > 1:
args[1:] = [os.path.abspath(x) if os.path.exists(x) else x for x in args[1:]]
import json
t.conn.send(b'launched:'+as_bytes(json.dumps(args)))
t.conn.close()
if not send_message(b'launched:'+as_bytes(json.dumps(args))):
raise SystemExit(_('Failed to contact running instance of calibre'))
raise SystemExit(0)
def create_listener():
if islinux:
from calibre.utils.ipc.server import LinuxListener as Listener
else:
from multiprocessing.connection import Listener
return Listener(address=gui_socket_address())
def restart_after_quit():
if iswindows:
# detach the stdout/stderr/stdin handles
@ -547,36 +508,8 @@ def main(args=sys.argv):
def run_main(app, opts, args, gui_debug, si):
if si:
try:
listener = create_listener()
except socket.error:
if iswindows or islinux:
cant_start(det_msg=traceback.format_exc(), listener_failed=True)
if os.path.exists(gui_socket_address()):
os.remove(gui_socket_address())
try:
listener = create_listener()
except socket.error:
cant_start(det_msg=traceback.format_exc(), listener_failed=True)
else:
return run_gui(opts, args, listener, app,
gui_debug=gui_debug)
else:
return run_gui(opts, args, listener, app,
gui_debug=gui_debug)
otherinstance = False
try:
listener = create_listener()
except socket.error: # Good singleinstance is correct (on UNIX)
otherinstance = True
else:
# On windows only singleinstance can be trusted
otherinstance = True if iswindows else False
if not otherinstance and not opts.shutdown_running_calibre:
return run_gui(opts, args, listener, app, gui_debug=gui_debug)
return run_gui(opts, args, app, gui_debug=gui_debug)
communicate(opts, args)
return 0

View File

@ -6,10 +6,9 @@
import json
import os
import shutil
from PyQt5.Qt import (
QHBoxLayout, QIcon, QLabel, QProgressBar, QPushButton, QSize, QUrl, QVBoxLayout,
QWidget, pyqtSignal, QApplication
QApplication, QHBoxLayout, QIcon, QLabel, QProgressBar, QPushButton, QSize, QUrl,
QVBoxLayout, QWidget, pyqtSignal
)
from PyQt5.QtWebEngineWidgets import QWebEngineProfile, QWebEngineView
@ -20,11 +19,11 @@ from calibre.gui2 import (
Application, choose_save_file, error_dialog, gprefs, info_dialog, set_app_uid
)
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.listener import send_message_in_process
from calibre.gui2.main_window import MainWindow
from calibre.ptempfile import PersistentTemporaryDirectory, reset_base_dir
from calibre.utils.ipc import RC
from polyglot.binary import as_base64_bytes, from_base64_bytes
from polyglot.builtins import string_or_bytes
from polyglot.binary import from_base64_bytes, as_base64_bytes
class DownloadItem(QWidget):
@ -207,22 +206,20 @@ class Main(MainWindow):
shutil.copyfile(path, name)
os.remove(path)
return
t = RC(print_error=False)
t.start()
t.join(3.0)
if t.conn is None:
error_dialog(self, _('No running calibre'), _(
'No running calibre instance found. Please start calibre before trying to'
' download books.'), show=True)
return
tags = self.data['tags']
if isinstance(tags, string_or_bytes):
tags = list(filter(None, [x.strip() for x in tags.split(',')]))
data = json.dumps({'path': path, 'tags': tags})
if not isinstance(data, bytes):
data = data.encode('utf-8')
t.conn.send(b'web-store:' + data)
t.conn.close()
try:
send_message_in_process(b'web-store:' + data)
except Exception as err:
error_dialog(self, _('Could not contact calibre'), _(
'No running calibre instance found. Please start calibre before trying to'
' download books.'), det_msg=str(err), show=True)
return
info_dialog(self, _('Download completed'), _(
'Download of {0} has been completed, the book was added to'

View File

@ -15,7 +15,6 @@ from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.utils import join_with_timeout
from calibre.utils.filenames import atomic_rename, format_permissions
from calibre.utils.ipc import RC
from polyglot.queue import LifoQueue, Empty
@ -78,12 +77,8 @@ def save_container(container, path):
def send_message(msg=''):
if msg:
t = RC(print_error=False)
t.start()
t.join(3)
if t.done:
t.conn.send('bookedited:'+msg)
t.conn.close()
from calibre.gui2.listener import send_message_in_process
send_message_in_process('bookedited:'+msg)
def find_first_existing_ancestor(path):

View File

@ -9,83 +9,60 @@ __docformat__ = 'restructuredtext en'
'''The main GUI'''
import collections, os, sys, textwrap, time, gc, errno, re
from threading import Thread
import apsw
import collections
import errno
import gc
import os
import re
import sys
import textwrap
import time
from collections import OrderedDict
from io import BytesIO
import apsw
from PyQt5.Qt import (
Qt, QTimer, QAction, QMenu, QIcon, pyqtSignal, QUrl, QFont, QDialog,
QApplication, QSystemTrayIcon)
QAction, QApplication, QDialog, QFont, QIcon, QMenu, QSystemTrayIcon, Qt, QTimer,
QUrl, pyqtSignal
)
from calibre import prints, force_unicode, detect_ncpus
from calibre import detect_ncpus, force_unicode, prints
from calibre.constants import (
__appname__, ismacos, iswindows, filesystem_encoding, DEBUG, config_dir)
from calibre.utils.config import prefs, dynamic
from calibre.utils.ipc.pool import Pool
DEBUG, __appname__, config_dir, filesystem_encoding, ismacos, iswindows
)
from calibre.customize.ui import available_store_plugins, interface_actions
from calibre.db.legacy import LibraryDatabase
from calibre.customize.ui import interface_actions, available_store_plugins
from calibre.gui2 import (error_dialog, GetMetadata, open_url,
gprefs, max_available_height, config, info_dialog, Dispatcher,
question_dialog, warning_dialog)
from calibre.gui2.cover_flow import CoverFlowMixin
from calibre.gui2 import (
Dispatcher, GetMetadata, config, error_dialog, gprefs, info_dialog,
max_available_height, open_url, question_dialog, warning_dialog
)
from calibre.gui2.auto_add import AutoAdder
from calibre.gui2.changes import handle_changes
from calibre.gui2.widgets import ProgressIndicator
from calibre.gui2.update import UpdateMixin
from calibre.gui2.main_window import MainWindow
from calibre.gui2.layout import MainWindowMixin
from calibre.gui2.cover_flow import CoverFlowMixin
from calibre.gui2.dbus_export.widgets import factory
from calibre.gui2.device import DeviceMixin
from calibre.gui2.email import EmailMixin
from calibre.gui2.dialogs.message_box import JobError
from calibre.gui2.ebook_download import EbookDownloadMixin
from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton
from calibre.gui2.init import LibraryViewMixin, LayoutMixin
from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
from calibre.gui2.email import EmailMixin
from calibre.gui2.init import LayoutMixin, LibraryViewMixin
from calibre.gui2.job_indicator import Pointer
from calibre.gui2.jobs import JobManager, JobsButton, JobsDialog
from calibre.gui2.keyboard import Manager
from calibre.gui2.layout import MainWindowMixin
from calibre.gui2.listener import Listener
from calibre.gui2.main_window import MainWindow
from calibre.gui2.open_with import register_keyboard_shortcuts
from calibre.gui2.proceed import ProceedQuestion
from calibre.gui2.search_box import SavedSearchBoxMixin, SearchBoxMixin
from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
from calibre.gui2.tag_browser.ui import TagBrowserMixin
from calibre.gui2.keyboard import Manager
from calibre.gui2.auto_add import AutoAdder
from calibre.gui2.proceed import ProceedQuestion
from calibre.gui2.dialogs.message_box import JobError
from calibre.gui2.job_indicator import Pointer
from calibre.gui2.dbus_export.widgets import factory
from calibre.gui2.open_with import register_keyboard_shortcuts
from calibre.gui2.update import UpdateMixin
from calibre.gui2.widgets import ProgressIndicator
from calibre.library import current_library_name
from calibre.srv.library_broker import GuiLibraryBroker
from polyglot.builtins import unicode_type, string_or_bytes
from polyglot.queue import Queue, Empty
class Listener(Thread): # {{{
def __init__(self, listener):
Thread.__init__(self)
self.daemon = True
self.listener, self.queue = listener, Queue()
self._run = True
self.start()
def run(self):
if self.listener is None:
return
while self._run:
try:
conn = self.listener.accept()
msg = conn.recv()
self.queue.put(msg)
except:
continue
def close(self):
self._run = False
try:
if self.listener is not None:
self.listener.close()
except:
import traceback
traceback.print_exc()
# }}}
from calibre.utils.config import dynamic, prefs
from calibre.utils.ipc.pool import Pool
from polyglot.builtins import string_or_bytes, unicode_type
from polyglot.queue import Empty, Queue
def get_gui():
@ -93,11 +70,11 @@ def get_gui():
def add_quick_start_guide(library_view, refresh_cover_browser=None):
from calibre.ebooks.metadata.meta import get_metadata
from calibre.ebooks.covers import calibre_cover2
from calibre.utils.zipfile import safe_replace
from calibre.utils.localization import get_lang, canonicalize_lang
from calibre.ebooks.metadata.meta import get_metadata
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.localization import canonicalize_lang, get_lang
from calibre.utils.zipfile import safe_replace
l = canonicalize_lang(get_lang()) or 'eng'
gprefs['quick_start_guide_added'] = True
imgbuf = BytesIO(calibre_cover2(_('Quick Start Guide'), ''))
@ -215,7 +192,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
else:
stmap[st.name] = st
def initialize(self, library_path, db, listener, actions, show_gui=True):
def initialize(self, library_path, db, actions, show_gui=True):
opts = self.opts
self.preferences_action, self.quit_action = actions
self.library_path = library_path
@ -226,10 +203,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
t.setInterval(1000), t.timeout.connect(self.handle_changes_from_server_debounced), t.setSingleShot(True)
self._spare_pool = None
self.must_restart_before_config = False
self.listener = Listener(listener)
self.check_messages_timer = QTimer()
self.check_messages_timer.timeout.connect(self.another_instance_wants_to_talk)
self.check_messages_timer.start(1000)
for ac in self.iactions.values():
try:
@ -424,6 +397,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.hide_windows()
self.auto_adder = AutoAdder(gprefs['auto_add_path'], self)
self.listener = Listener(parent=self)
self.listener.message_received.connect(self.message_from_another_instance)
QTimer.singleShot(0, self.listener.start_listening)
# Collect cycles now
gc.collect()
@ -626,11 +603,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
if files:
self.iactions['Add Books'].add_filesystem_book(files)
def another_instance_wants_to_talk(self):
try:
msg = self.listener.queue.get_nowait()
except Empty:
return
def message_from_another_instance(self, msg):
if isinstance(msg, bytes):
msg = msg.decode('utf-8', 'replace')
if msg.startswith('launched:'):
@ -651,10 +624,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.show_windows()
self.raise_()
self.activateWindow()
elif msg.startswith('refreshdb:'):
m = self.library_view.model()
m.db.new_api.reload_from_db()
self.refresh_all()
elif msg.startswith('shutdown:'):
self.quit(confirm_quit=False)
elif msg.startswith('bookedited:'):
@ -735,7 +704,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
det_msg=traceback.format_exc()
)
if repair:
from calibre.gui2.dialogs.restore_library import repair_library_at
from calibre.gui2.dialogs.restore_library import (
repair_library_at
)
if repair_library_at(newloc, parent=self):
db = LibraryDatabase(newloc, default_prefs=default_prefs)
else:
@ -1005,7 +976,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
action.shutting_down()
if write_settings:
self.write_settings()
self.check_messages_timer.stop()
if getattr(self, 'update_checker', None):
self.update_checker.shutdown()
self.listener.close()

View File

@ -6,19 +6,18 @@
import json
import os
import sys
from threading import Thread
from PyQt5.Qt import QIcon, QObject, Qt, QTimer, pyqtSignal
from PyQt5.QtWebEngineCore import QWebEngineUrlScheme
from contextlib import closing
from calibre import as_unicode, prints
from calibre.constants import FAKE_PROTOCOL, VIEWER_APP_UID, islinux, iswindows
from calibre.constants import FAKE_PROTOCOL, VIEWER_APP_UID, islinux
from calibre.gui2 import Application, error_dialog, setup_gui_option_parser
from calibre.gui2.viewer.ui import EbookViewer, is_float
from calibre.gui2.viewer.config import get_session_pref, vprefs
from calibre.gui2.viewer.ui import EbookViewer, is_float
from calibre.gui2.listener import send_message_in_process
from calibre.ptempfile import reset_base_dir
from calibre.utils.config import JSONConfig
from calibre.utils.ipc import RC, viewer_socket_address
from calibre.utils.ipc import viewer_socket_address
singleinstance_name = 'calibre_viewer'
@ -97,62 +96,16 @@ class EventAccumulator(QObject):
self.events = []
def listen(listener, msg_from_anotherinstance):
while True:
def send_message_to_viewer_instance(args, open_at):
if len(args) > 1:
msg = json.dumps((os.path.abspath(args[1]), open_at))
try:
conn = listener.accept()
except Exception:
break
try:
msg_from_anotherinstance.emit(conn.recv())
conn.close()
except Exception as e:
prints('Failed to read message from other instance with error: %s' % as_unicode(e))
def create_listener():
addr = viewer_socket_address()
if islinux:
from calibre.utils.ipc.server import LinuxListener as Listener
else:
from multiprocessing.connection import Listener
if not iswindows:
# On macOS (and BSDs, I am guessing), following a crash, the
# listener socket file sticks around and needs to be explicitly
# removed. It is safe to do this since we are already guaranteed to
# be the owner of the socket by singleinstance()
try:
os.remove(addr)
except Exception:
pass
return Listener(address=addr)
def ensure_single_instance(args, open_at):
try:
from calibre.utils.lock import singleinstance
si = singleinstance(singleinstance_name)
except Exception:
import traceback
error_dialog(None, _('Cannot start viewer'), _(
'Failed to start viewer, could not insure only a single instance of the viewer is running. Click "Show Details" for more information'),
det_msg=traceback.format_exc(), show=True)
raise SystemExit(1)
if not si:
if len(args) > 1:
t = RC(print_error=True, socket_address=viewer_socket_address())
t.start()
t.join(3.0)
if t.is_alive() or t.conn is None:
error_dialog(None, _('Connect to viewer failed'), _(
'Unable to connect to existing E-book viewer window, try restarting the viewer.'), show=True)
raise SystemExit(1)
t.conn.send((os.path.abspath(args[1]), open_at))
t.conn.close()
prints('Opened book in existing viewer instance')
raise SystemExit(0)
listener = create_listener()
return listener
send_message_in_process(msg, address=viewer_socket_address())
except Exception as err:
error_dialog(None, _('Connect to viewer failed'), _(
'Unable to connect to existing E-book viewer window, try restarting the viewer.'), det_msg=str(err), show=True)
raise SystemExit(1)
print('Opened book in existing viewer instance')
def option_parser():
@ -187,6 +140,31 @@ View an e-book.
return parser
def run_gui(app, opts, args, internal_book_data, listener=None):
acc = EventAccumulator(app)
app.file_event_hook = acc
app.load_builtin_fonts()
app.setWindowIcon(QIcon(I('viewer.png')))
migrate_previous_viewer_prefs()
main = EbookViewer(
open_at=opts.open_at, continue_reading=opts.continue_reading, force_reload=opts.force_reload,
calibre_book_data=internal_book_data)
main.set_exception_handler()
if len(args) > 1:
acc.events.append(os.path.abspath(args[-1]))
acc.got_file.connect(main.handle_commandline_arg)
main.show()
if listener is not None:
listener.message_received.connect(main.message_from_other_instance, type=Qt.QueuedConnection)
QTimer.singleShot(0, acc.flush)
if opts.raise_window:
main.raise_()
if opts.full_screen:
main.set_full_screen(True)
app.exec_()
def main(args=sys.argv):
# Ensure viewer can continue to function if GUI is closed
os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None)
@ -223,42 +201,25 @@ def main(args=sys.argv):
oat.startswith('epubcfi(/') or is_float(oat) or oat.startswith('ref:')):
raise SystemExit('Not a valid --open-at value: {}'.format(opts.open_at))
listener = None
if get_session_pref('singleinstance', False):
try:
listener = ensure_single_instance(args, opts.open_at)
except Exception as e:
import traceback
error_dialog(None, _('Failed to start viewer'), as_unicode(e), det_msg=traceback.format_exc(), show=True)
raise SystemExit(1)
acc = EventAccumulator(app)
app.file_event_hook = acc
app.load_builtin_fonts()
app.setWindowIcon(QIcon(I('viewer.png')))
migrate_previous_viewer_prefs()
main = EbookViewer(
open_at=opts.open_at, continue_reading=opts.continue_reading, force_reload=opts.force_reload,
calibre_book_data=internal_book_data)
main.set_exception_handler()
if len(args) > 1:
acc.events.append(os.path.abspath(args[-1]))
acc.got_file.connect(main.handle_commandline_arg)
main.show()
main.msg_from_anotherinstance.connect(main.another_instance_wants_to_talk, type=Qt.QueuedConnection)
if listener is not None:
t = Thread(name='ConnListener', target=listen, args=(listener, main.msg_from_anotherinstance))
t.daemon = True
t.start()
QTimer.singleShot(0, acc.flush)
if opts.raise_window:
main.raise_()
if opts.full_screen:
main.set_full_screen(True)
app.exec_()
if listener is not None:
listener.close()
from calibre.utils.lock import SingleInstance
from calibre.gui2.listener import Listener
with SingleInstance(singleinstance_name) as si:
if si:
try:
listener = Listener(address=viewer_socket_address(), parent=app)
listener.start_listening()
except Exception as err:
error_dialog(None, _('Failed to start listener'), _(
'Could not start the listener used for single instance viewers. Try rebooting your computer.'),
det_msg=str(err), show=True)
else:
with closing(listener):
run_gui(app, opts, args, internal_book_data, listener=listener)
else:
send_message_to_viewer_instance(args, opts.open_at)
else:
run_gui(app, opts, args, internal_book_data)
if __name__ == '__main__':

View File

@ -248,13 +248,17 @@ class EbookViewer(MainWindow):
else:
prints('Cannot read from:', arg, file=sys.stderr)
def another_instance_wants_to_talk(self, msg):
def message_from_other_instance(self, msg):
try:
msg = json.loads(msg)
path, open_at = msg
except Exception:
except Exception as err:
print('Invalid message from other instance', file=sys.stderr)
print(err, file=sys.stderr)
return
self.load_ebook(path, open_at=open_at)
self.raise_()
self.activateWindow()
# }}}
# Fullscreen {{{