diff --git a/setup/qt5-migrate.py b/setup/qt5-migrate.py index 1b7e65bdf8..21544e183a 100644 --- a/setup/qt5-migrate.py +++ b/setup/qt5-migrate.py @@ -6,11 +6,6 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -# QT5XX: Implement a minimal QPA plugin to allow headless operation, see the -# minimal example in the Qt source code and see -# https://github.com/ariya/phantomjs/pull/173 for info on how to enable fonts -# with fontconfig (probably needed for PDF output and SVG rendering) - # QT5XX: Add a import checker that looks for all from PyQt5.* imports and runs # them to check that they work. This can probably be made part of python # setup.py check. diff --git a/src/calibre/ebooks/oeb/transforms/rasterize.py b/src/calibre/ebooks/oeb/transforms/rasterize.py index 29e9109592..4c1b8c5901 100644 --- a/src/calibre/ebooks/oeb/transforms/rasterize.py +++ b/src/calibre/ebooks/oeb/transforms/rasterize.py @@ -28,9 +28,8 @@ class Unavailable(Exception): class SVGRasterizer(object): def __init__(self): - from calibre.gui2 import is_ok_to_use_qt - if not is_ok_to_use_qt(): - raise Unavailable('Not OK to use Qt') + from calibre.gui2 import must_use_qt + must_use_qt() @classmethod def config(cls, cfg): diff --git a/src/calibre/ebooks/pdf/render/from_html.py b/src/calibre/ebooks/pdf/render/from_html.py index a571a05656..d931703420 100644 --- a/src/calibre/ebooks/pdf/render/from_html.py +++ b/src/calibre/ebooks/pdf/render/from_html.py @@ -146,9 +146,8 @@ class PDFWriter(QObject): return self.current_section def __init__(self, opts, log, cover_data=None, toc=None): - from calibre.gui2 import is_ok_to_use_qt - if not is_ok_to_use_qt(): - raise Exception('Not OK to use Qt') + from calibre.gui2 import must_use_qt + must_use_qt() QObject.__init__(self) self.logger = self.log = log diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index 07039b5779..9ba9caf7f1 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -36,9 +36,8 @@ def get_custom_size(opts): return custom_size def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{ - from calibre.gui2 import is_ok_to_use_qt - if not is_ok_to_use_qt(): - raise Exception('Not OK to use Qt') + from calibre.gui2 import must_use_qt + must_use_qt() printer = QPrinter(QPrinter.HighResolution) custom_size = get_custom_size(opts) @@ -137,10 +136,9 @@ class Page(QWebPage): # {{{ class PDFWriter(QObject): # {{{ def __init__(self, opts, log, cover_data=None, toc=None): - from calibre.gui2 import is_ok_to_use_qt + from calibre.gui2 import must_use_qt from calibre.utils.podofo import get_podofo - if not is_ok_to_use_qt(): - raise Exception('Not OK to use Qt') + must_use_qt() QObject.__init__(self) self.logger = self.log = log diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 99392a152a..0899d506c2 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -1039,12 +1039,16 @@ def open_local_file(path): open_url(url) def must_use_qt(): + ''' This function should be called if you want to use Qt for some non-GUI + task like rendering HTML/SVG or using a headless browser. It will raise a + RuntimeError if using Qt is not possible, which will happen if the current + thread is not the main GUI thread. On linux, it uses a special QPA headless + plugin, so that the X server does not need to be running. ''' global gui_thread, _store_app - if (islinux or isbsd) and ':' not in os.environ.get('DISPLAY', ''): - raise RuntimeError('X server required. If you are running on a' - ' headless machine, use xvfb') if _store_app is None and QApplication.instance() is None: - _store_app = QApplication([]) + if islinux or isbsd: + args = sys.argv[:1] + ['-platformpluginpath', sys.extensions_location, '-platform', 'headless'] + _store_app = QApplication(args) if gui_thread is None: gui_thread = QThread.currentThread() if gui_thread is not QThread.currentThread(): diff --git a/src/calibre/gui2/convert/font_key.py b/src/calibre/gui2/convert/font_key.py index a39d6e9ded..f389cbf895 100644 --- a/src/calibre/gui2/convert/font_key.py +++ b/src/calibre/gui2/convert/font_key.py @@ -94,7 +94,8 @@ class FontKeyChooser(QDialog, Ui_Dialog): if __name__ == '__main__': - from calibre.gui2 import is_ok_to_use_qt - is_ok_to_use_qt() + from PyQt5.Qt import QApplication + app = QApplication([]) d = FontKeyChooser() d.exec_() + del app diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index 34568451a1..8c020ba7ee 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -197,8 +197,7 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.recipe_model.do_refresh() self.count_label.setText( # NOTE: Number of news sources - _('%s news sources') % - self.recipe_model.showing_count) + _('%s news sources') % self.recipe_model.showing_count) self.schedule_widgets = [] for key in reversed(self.SCHEDULE_TYPES): @@ -583,8 +582,9 @@ class Scheduler(QObject): break if __name__ == '__main__': - from calibre.gui2 import is_ok_to_use_qt - is_ok_to_use_qt() + from PyQt5.Qt import QApplication + app = QApplication([]) d = SchedulerDialog(RecipeModel()) d.exec_() + del app diff --git a/src/calibre/gui2/dialogs/user_profiles.py b/src/calibre/gui2/dialogs/user_profiles.py index 4ba0431a52..7d0404fc8f 100644 --- a/src/calibre/gui2/dialogs/user_profiles.py +++ b/src/calibre/gui2/dialogs/user_profiles.py @@ -440,9 +440,10 @@ class %(classname)s(%(base_class)s): ResizableDialog.reject(self) if __name__ == '__main__': - from calibre.gui2 import is_ok_to_use_qt - is_ok_to_use_qt() + from PyQt5.Qt import QApplication + app = QApplication([]) from calibre.web.feeds.recipes.model import RecipeModel d=UserProfiles(None, RecipeModel()) d.exec_() + del app diff --git a/src/calibre/gui2/shortcuts.py b/src/calibre/gui2/shortcuts.py index 45ae55b92d..c51789cabc 100644 --- a/src/calibre/gui2/shortcuts.py +++ b/src/calibre/gui2/shortcuts.py @@ -270,11 +270,10 @@ class ShortcutConfig(QWidget): if __name__ == '__main__': - from calibre.gui2 import is_ok_to_use_qt + app = QApplication([]) from calibre.gui2.viewer.keys import SHORTCUTS - is_ok_to_use_qt() model = Shortcuts(SHORTCUTS, 'shortcuts/viewer') conf = ShortcutConfig(model) conf.resize(400, 500) conf.show() - QApplication.instance().exec_() + app.exec_() diff --git a/src/calibre/utils/serve_coffee.py b/src/calibre/utils/serve_coffee.py index 2698282155..5f40792a1c 100644 --- a/src/calibre/utils/serve_coffee.py +++ b/src/calibre/utils/serve_coffee.py @@ -22,54 +22,14 @@ import time, BaseHTTPServer, os, sys, re, SocketServer from threading import Lock from SimpleHTTPServer import SimpleHTTPRequestHandler - # Compiler {{{ -try: - from PyQt5.Qt import QJSEngine, QJSValue - - class Compiler(QJSEngine): - - ''' - You can use this class in any thread, but make sure you instantiate it in - the main thread. Alternatively, construct a QCoreApplication in the main - thread, after which you can instantiate this class and use it in any - thread. - ''' - - def __init__(self): - from PyQt5.Qt import QCoreApplication - if QCoreApplication.instance() is None: - self.__app_ = QCoreApplication([]) - - QJSEngine.__init__(self) - res = self.evaluate(CS_JS, 'coffee-script.js') - if res.isError(): - raise Exception('Failed to run the coffee script compiler: %s'% - res.toString()) - self.lock = Lock() - - def __call__(self, raw, filename=None): - with self.lock: - if not isinstance(raw, unicode): - raw = raw.decode('utf-8') - if not filename: - filename = '' - go = self.globalObject() - go.setProperty('coffee_src', QJSValue(raw)) - res = self.evaluate('this.CoffeeScript.compile(this.coffee_src)', - filename) - if res.isError(): - return '', [res.toString()] - return res.toString(), [] -except ImportError: - pass def do_compile(raw, filename=None): from calibre.gui2 import must_use_qt must_use_qt() from PyQt5.Qt import QWebPage, QApplication import json - app = QApplication([]) + app = QApplication.instance() class C(QWebPage): @@ -95,19 +55,13 @@ def do_compile(raw, filename=None): # }}} def compile_coffeescript(raw, filename=None): - from calibre.gui2 import is_ok_to_use_qt + from calibre.utils.ipc.simple_worker import fork_job, WorkerError try: - if is_ok_to_use_qt(): - raise NameError('The WebKit based compiler is much faster than QJSEngine') - return Compiler()(raw, filename) - except NameError: - from calibre.utils.ipc.simple_worker import fork_job, WorkerError - try: - return fork_job('calibre.utils.serve_coffee', 'do_compile', - args=(raw,), no_output=True)['result'] - except WorkerError as err: - print (err.orig_tb) - raise + return fork_job('calibre.utils.serve_coffee', 'do_compile', + args=(raw,), no_output=True)['result'] + except WorkerError as err: + print (err.orig_tb) + raise def check_coffeescript(filename): with open(filename, 'rb') as f: @@ -369,10 +323,8 @@ def main(): cc.add_argument('--highlight', default=False, action='store_true', help='Syntax highlight the output (requires Pygments)') - cs.add_argument('--port', type=int, default=8000, help= - 'The port on which to serve. Default: %default') - cs.add_argument('--host', default='0.0.0.0', help= - 'The IP address on which to listen. Default is to listen on all' + cs.add_argument('--port', type=int, default=8000, help='The port on which to serve. Default: %default') + cs.add_argument('--host', default='0.0.0.0', help='The IP address on which to listen. Default is to listen on all' ' IPv4 addresses (0.0.0.0)') args = parser.parse_args() if args.which == 'compile':