mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
The Headless Horseman rides!
On linux all worker processes/command line tools now use the headless QPA plugin when using Qt based code. This means that they no longer require a running X server or xvfb.
This commit is contained in:
parent
d525d9b846
commit
d1292bbc0f
@ -6,11 +6,6 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
# 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.
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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():
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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_()
|
||||
|
@ -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 = '<string>'
|
||||
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':
|
||||
|
Loading…
x
Reference in New Issue
Block a user