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'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__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
|
# 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
|
# them to check that they work. This can probably be made part of python
|
||||||
# setup.py check.
|
# setup.py check.
|
||||||
|
@ -28,9 +28,8 @@ class Unavailable(Exception):
|
|||||||
|
|
||||||
class SVGRasterizer(object):
|
class SVGRasterizer(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
from calibre.gui2 import must_use_qt
|
||||||
if not is_ok_to_use_qt():
|
must_use_qt()
|
||||||
raise Unavailable('Not OK to use Qt')
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config(cls, cfg):
|
def config(cls, cfg):
|
||||||
|
@ -146,9 +146,8 @@ class PDFWriter(QObject):
|
|||||||
return self.current_section
|
return self.current_section
|
||||||
|
|
||||||
def __init__(self, opts, log, cover_data=None, toc=None):
|
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
|
||||||
if not is_ok_to_use_qt():
|
must_use_qt()
|
||||||
raise Exception('Not OK to use Qt')
|
|
||||||
QObject.__init__(self)
|
QObject.__init__(self)
|
||||||
|
|
||||||
self.logger = self.log = log
|
self.logger = self.log = log
|
||||||
|
@ -36,9 +36,8 @@ def get_custom_size(opts):
|
|||||||
return custom_size
|
return custom_size
|
||||||
|
|
||||||
def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{
|
def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
from calibre.gui2 import must_use_qt
|
||||||
if not is_ok_to_use_qt():
|
must_use_qt()
|
||||||
raise Exception('Not OK to use Qt')
|
|
||||||
|
|
||||||
printer = QPrinter(QPrinter.HighResolution)
|
printer = QPrinter(QPrinter.HighResolution)
|
||||||
custom_size = get_custom_size(opts)
|
custom_size = get_custom_size(opts)
|
||||||
@ -137,10 +136,9 @@ class Page(QWebPage): # {{{
|
|||||||
class PDFWriter(QObject): # {{{
|
class PDFWriter(QObject): # {{{
|
||||||
|
|
||||||
def __init__(self, opts, log, cover_data=None, toc=None):
|
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
|
from calibre.utils.podofo import get_podofo
|
||||||
if not is_ok_to_use_qt():
|
must_use_qt()
|
||||||
raise Exception('Not OK to use Qt')
|
|
||||||
QObject.__init__(self)
|
QObject.__init__(self)
|
||||||
|
|
||||||
self.logger = self.log = log
|
self.logger = self.log = log
|
||||||
|
@ -1039,12 +1039,16 @@ def open_local_file(path):
|
|||||||
open_url(url)
|
open_url(url)
|
||||||
|
|
||||||
def must_use_qt():
|
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
|
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:
|
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:
|
if gui_thread is None:
|
||||||
gui_thread = QThread.currentThread()
|
gui_thread = QThread.currentThread()
|
||||||
if gui_thread is not QThread.currentThread():
|
if gui_thread is not QThread.currentThread():
|
||||||
|
@ -94,7 +94,8 @@ class FontKeyChooser(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
from PyQt5.Qt import QApplication
|
||||||
is_ok_to_use_qt()
|
app = QApplication([])
|
||||||
d = FontKeyChooser()
|
d = FontKeyChooser()
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
del app
|
||||||
|
@ -197,8 +197,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
|||||||
self.recipe_model.do_refresh()
|
self.recipe_model.do_refresh()
|
||||||
self.count_label.setText(
|
self.count_label.setText(
|
||||||
# NOTE: Number of news sources
|
# NOTE: Number of news sources
|
||||||
_('%s news sources') %
|
_('%s news sources') % self.recipe_model.showing_count)
|
||||||
self.recipe_model.showing_count)
|
|
||||||
|
|
||||||
self.schedule_widgets = []
|
self.schedule_widgets = []
|
||||||
for key in reversed(self.SCHEDULE_TYPES):
|
for key in reversed(self.SCHEDULE_TYPES):
|
||||||
@ -583,8 +582,9 @@ class Scheduler(QObject):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
from PyQt5.Qt import QApplication
|
||||||
is_ok_to_use_qt()
|
app = QApplication([])
|
||||||
d = SchedulerDialog(RecipeModel())
|
d = SchedulerDialog(RecipeModel())
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
del app
|
||||||
|
|
||||||
|
@ -440,9 +440,10 @@ class %(classname)s(%(base_class)s):
|
|||||||
ResizableDialog.reject(self)
|
ResizableDialog.reject(self)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
from PyQt5.Qt import QApplication
|
||||||
is_ok_to_use_qt()
|
app = QApplication([])
|
||||||
from calibre.web.feeds.recipes.model import RecipeModel
|
from calibre.web.feeds.recipes.model import RecipeModel
|
||||||
d=UserProfiles(None, RecipeModel())
|
d=UserProfiles(None, RecipeModel())
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
del app
|
||||||
|
|
||||||
|
@ -270,11 +270,10 @@ class ShortcutConfig(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
app = QApplication([])
|
||||||
from calibre.gui2.viewer.keys import SHORTCUTS
|
from calibre.gui2.viewer.keys import SHORTCUTS
|
||||||
is_ok_to_use_qt()
|
|
||||||
model = Shortcuts(SHORTCUTS, 'shortcuts/viewer')
|
model = Shortcuts(SHORTCUTS, 'shortcuts/viewer')
|
||||||
conf = ShortcutConfig(model)
|
conf = ShortcutConfig(model)
|
||||||
conf.resize(400, 500)
|
conf.resize(400, 500)
|
||||||
conf.show()
|
conf.show()
|
||||||
QApplication.instance().exec_()
|
app.exec_()
|
||||||
|
@ -22,54 +22,14 @@ import time, BaseHTTPServer, os, sys, re, SocketServer
|
|||||||
from threading import Lock
|
from threading import Lock
|
||||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||||
|
|
||||||
|
|
||||||
# Compiler {{{
|
# 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):
|
def do_compile(raw, filename=None):
|
||||||
from calibre.gui2 import must_use_qt
|
from calibre.gui2 import must_use_qt
|
||||||
must_use_qt()
|
must_use_qt()
|
||||||
from PyQt5.Qt import QWebPage, QApplication
|
from PyQt5.Qt import QWebPage, QApplication
|
||||||
import json
|
import json
|
||||||
app = QApplication([])
|
app = QApplication.instance()
|
||||||
|
|
||||||
class C(QWebPage):
|
class C(QWebPage):
|
||||||
|
|
||||||
@ -95,12 +55,6 @@ def do_compile(raw, filename=None):
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def compile_coffeescript(raw, filename=None):
|
def compile_coffeescript(raw, filename=None):
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
|
||||||
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
|
from calibre.utils.ipc.simple_worker import fork_job, WorkerError
|
||||||
try:
|
try:
|
||||||
return fork_job('calibre.utils.serve_coffee', 'do_compile',
|
return fork_job('calibre.utils.serve_coffee', 'do_compile',
|
||||||
@ -369,10 +323,8 @@ def main():
|
|||||||
cc.add_argument('--highlight', default=False, action='store_true',
|
cc.add_argument('--highlight', default=False, action='store_true',
|
||||||
help='Syntax highlight the output (requires Pygments)')
|
help='Syntax highlight the output (requires Pygments)')
|
||||||
|
|
||||||
cs.add_argument('--port', type=int, default=8000, help=
|
cs.add_argument('--port', type=int, default=8000, help='The port on which to serve. Default: %default')
|
||||||
'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('--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)')
|
' IPv4 addresses (0.0.0.0)')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.which == 'compile':
|
if args.which == 'compile':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user