2014-06-19 09:04:37 +02:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
|
2014-05-09 15:32:01 +02:00
|
|
|
# This file is part of qutebrowser.
|
|
|
|
#
|
|
|
|
# qutebrowser is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# qutebrowser 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 License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2014-05-13 19:24:43 +02:00
|
|
|
"""Things which need to be done really early (e.g. before importing Qt).
|
|
|
|
|
2014-07-28 20:41:42 +02:00
|
|
|
At this point we can be sure we have all python 3.4 features available.
|
2014-05-13 19:24:43 +02:00
|
|
|
"""
|
2014-05-09 15:32:01 +02:00
|
|
|
|
2014-05-13 18:01:10 +02:00
|
|
|
import os
|
2014-05-09 15:32:01 +02:00
|
|
|
import sys
|
2014-05-13 19:46:57 +02:00
|
|
|
import faulthandler
|
|
|
|
import traceback
|
2014-05-14 08:52:40 +02:00
|
|
|
import signal
|
2014-09-16 06:39:39 +02:00
|
|
|
import operator
|
|
|
|
import importlib
|
2014-07-27 22:57:50 +02:00
|
|
|
try:
|
2014-08-26 19:10:14 +02:00
|
|
|
import tkinter # pylint: disable=import-error
|
2014-07-27 22:57:50 +02:00
|
|
|
except ImportError:
|
2014-08-26 19:10:14 +02:00
|
|
|
tkinter = None
|
2014-09-16 06:39:39 +02:00
|
|
|
# NOTE: No qutebrowser or PyQt import should be done here, as some early
|
|
|
|
# initialisation needs to take place before that!
|
2014-05-09 15:32:01 +02:00
|
|
|
|
|
|
|
|
2014-06-27 12:25:27 +02:00
|
|
|
def _missing_str(name, debian=None, arch=None, windows=None, pip=None):
|
|
|
|
"""Get an error string for missing packages.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: The name of the package.
|
|
|
|
debian: String to be displayed for Debian.
|
|
|
|
arch: String to be displayed for Archlinux.
|
|
|
|
windows: String to be displayed for Windows.
|
|
|
|
pip: pypi package name.
|
|
|
|
"""
|
2014-08-15 18:58:46 +02:00
|
|
|
blocks = ["Fatal error: <b>{}</b> is required to run qutebrowser but "
|
|
|
|
"could not be imported! Maybe it's not installed?".format(name)]
|
2014-06-27 12:25:27 +02:00
|
|
|
if debian is not None:
|
2014-08-15 18:58:46 +02:00
|
|
|
lines = ["<b>On Debian/Ubuntu:</b>"]
|
|
|
|
lines += debian.splitlines()
|
|
|
|
blocks.append('<br />'.join(lines))
|
2014-06-27 12:25:27 +02:00
|
|
|
if arch is not None:
|
2014-08-15 18:58:46 +02:00
|
|
|
lines = ["<b>On Archlinux:</b>"]
|
|
|
|
lines += arch.splitlines()
|
|
|
|
blocks.append('<br />'.join(lines))
|
2014-06-27 12:25:27 +02:00
|
|
|
if windows is not None:
|
2014-08-15 18:58:46 +02:00
|
|
|
lines = ["<b>On Windows:</b>"]
|
|
|
|
lines += windows.splitlines()
|
|
|
|
blocks.append('<br />'.join(lines))
|
|
|
|
lines = ["<b>For other distributions:</b>",
|
2014-11-19 22:46:52 +01:00
|
|
|
"Check your package manager for similarly named packages or "
|
2014-08-15 18:58:46 +02:00
|
|
|
"install via pip."]
|
|
|
|
blocks.append('<br />'.join(lines))
|
2014-06-27 12:25:27 +02:00
|
|
|
if pip is not None:
|
2014-08-15 18:58:46 +02:00
|
|
|
lines = ["<b>Using pip:</b>"]
|
|
|
|
lines.append("pip3 install {}".format(pip))
|
|
|
|
blocks.append('<br />'.join(lines))
|
|
|
|
return '<br /><br />'.join(blocks)
|
2014-06-27 12:25:27 +02:00
|
|
|
|
|
|
|
|
|
|
|
def _die(message, exception=True):
|
|
|
|
"""Display an error message using Qt and quit.
|
|
|
|
|
|
|
|
We import the imports here as we want to do other stuff before the imports.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
message: The message to display.
|
|
|
|
exception: Whether to print an exception with --debug.
|
|
|
|
"""
|
|
|
|
from PyQt5.QtWidgets import QApplication, QMessageBox
|
2014-08-15 18:58:46 +02:00
|
|
|
from PyQt5.QtCore import Qt
|
2014-06-27 12:25:27 +02:00
|
|
|
if '--debug' in sys.argv and exception:
|
|
|
|
print(file=sys.stderr)
|
|
|
|
traceback.print_exc()
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!",
|
|
|
|
message)
|
2014-08-15 18:58:46 +02:00
|
|
|
msgbox.setTextFormat(Qt.RichText)
|
2014-06-27 12:25:27 +02:00
|
|
|
msgbox.resize(msgbox.sizeHint())
|
|
|
|
msgbox.exec_()
|
|
|
|
app.quit()
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
2014-10-14 20:47:06 +02:00
|
|
|
def init_faulthandler(fileobj=sys.__stderr__):
|
2014-05-13 18:01:10 +02:00
|
|
|
"""Enable faulthandler module if available.
|
|
|
|
|
2014-05-13 19:24:43 +02:00
|
|
|
This print a nice traceback on segfauls.
|
2014-08-01 00:55:18 +02:00
|
|
|
|
|
|
|
We use sys.__stderr__ instead of sys.stderr here so this will still work
|
|
|
|
when sys.stderr got replaced, e.g. by "Python Tools for Visual Studio".
|
2014-10-14 20:47:06 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
fobj: An opened file object to write the traceback to.
|
2014-05-13 18:01:10 +02:00
|
|
|
"""
|
2014-10-14 20:47:06 +02:00
|
|
|
if fileobj is None:
|
2014-08-01 00:55:18 +02:00
|
|
|
# When run with pythonw.exe, sys.__stderr__ can be None:
|
2014-05-13 19:24:43 +02:00
|
|
|
# https://docs.python.org/3/library/sys.html#sys.__stderr__
|
2014-05-13 18:01:10 +02:00
|
|
|
# If we'd enable faulthandler in that case, we just get a weird
|
2014-05-13 19:24:43 +02:00
|
|
|
# exception, so we don't enable faulthandler if we have no stdout.
|
2014-05-13 18:01:10 +02:00
|
|
|
#
|
2014-05-15 12:20:03 +02:00
|
|
|
# Later when we have our data dir available we re-enable faulthandler
|
|
|
|
# to write to a file so we can display a crash to the user at the next
|
|
|
|
# start.
|
2014-05-13 18:01:10 +02:00
|
|
|
return
|
2014-10-14 20:47:06 +02:00
|
|
|
faulthandler.enable(fileobj)
|
2014-05-14 08:52:40 +02:00
|
|
|
if hasattr(faulthandler, 'register') and hasattr(signal, 'SIGUSR1'):
|
2014-05-13 18:01:10 +02:00
|
|
|
# If available, we also want a traceback on SIGUSR1.
|
2014-05-14 08:59:24 +02:00
|
|
|
faulthandler.register(signal.SIGUSR1) # pylint: disable=no-member
|
2014-05-13 18:01:10 +02:00
|
|
|
|
|
|
|
|
2014-06-04 14:15:33 +02:00
|
|
|
def fix_harfbuzz(args):
|
2014-05-13 18:01:10 +02:00
|
|
|
"""Fix harfbuzz issues.
|
|
|
|
|
2014-06-16 13:54:47 +02:00
|
|
|
This switches to the most stable harfbuzz font rendering engine available
|
|
|
|
on the platform instead of using the system wide one.
|
2014-05-13 18:01:10 +02:00
|
|
|
|
|
|
|
This fixes crashes on various sites.
|
2014-06-16 13:54:47 +02:00
|
|
|
|
|
|
|
- On Qt 5.2 (and probably earlier) the new engine probably has more
|
|
|
|
crashes and is also experimental.
|
|
|
|
|
|
|
|
e.g. https://bugreports.qt-project.org/browse/QTBUG-36099
|
|
|
|
|
|
|
|
- On Qt 5.3.0 there's a bug that affects a lot of websites:
|
|
|
|
https://bugreports.qt-project.org/browse/QTBUG-39278
|
|
|
|
So the new engine will be more stable.
|
|
|
|
|
2014-07-04 14:37:31 +02:00
|
|
|
- On Qt 5.3.1 this bug is fixed and the old engine will be the more stable
|
|
|
|
one again.
|
2014-06-04 14:15:33 +02:00
|
|
|
|
2014-06-04 23:55:34 +02:00
|
|
|
IMPORTANT: This needs to be done before QWidgets is imported in any way!
|
|
|
|
|
2014-09-02 20:44:58 +02:00
|
|
|
WORKAROUND (remove this when we bump the requirements to 5.3.1)
|
|
|
|
|
2014-06-04 14:15:33 +02:00
|
|
|
Args:
|
|
|
|
args: The argparse namespace.
|
2014-05-13 18:01:10 +02:00
|
|
|
"""
|
2014-08-26 20:15:41 +02:00
|
|
|
from qutebrowser.utils import log
|
2014-06-05 00:07:32 +02:00
|
|
|
from PyQt5.QtCore import qVersion
|
2014-06-04 23:55:34 +02:00
|
|
|
if 'PyQt5.QtWidgets' in sys.modules:
|
2014-08-26 20:15:41 +02:00
|
|
|
log.init.warning("Harfbuzz fix attempted but QtWidgets is already "
|
|
|
|
"imported!")
|
2014-06-04 14:15:33 +02:00
|
|
|
if sys.platform.startswith('linux') and args.harfbuzz == 'auto':
|
2014-06-05 00:07:32 +02:00
|
|
|
if qVersion() == '5.3.0':
|
2014-08-26 20:15:41 +02:00
|
|
|
log.init.debug("Using new harfbuzz engine (auto)")
|
2014-06-05 00:07:32 +02:00
|
|
|
os.environ['QT_HARFBUZZ'] = 'new'
|
|
|
|
else:
|
2014-08-26 20:15:41 +02:00
|
|
|
log.init.debug("Using old harfbuzz engine (auto)")
|
2014-06-05 00:07:32 +02:00
|
|
|
os.environ['QT_HARFBUZZ'] = 'old'
|
2014-06-06 17:12:54 +02:00
|
|
|
elif args.harfbuzz in ('old', 'new'):
|
2014-06-05 00:07:32 +02:00
|
|
|
# forced harfbuzz variant
|
|
|
|
# FIXME looking at the Qt code, 'new' isn't a valid value, but leaving
|
|
|
|
# it empty and using new yields different behaviour...
|
2014-10-01 22:23:27 +02:00
|
|
|
# (probably irrelevant when workaround gets removed)
|
2014-08-26 20:15:41 +02:00
|
|
|
log.init.debug("Using {} harfbuzz engine (forced)".format(
|
|
|
|
args.harfbuzz))
|
2014-06-05 00:07:32 +02:00
|
|
|
os.environ['QT_HARFBUZZ'] = args.harfbuzz
|
|
|
|
else:
|
2014-08-26 20:15:41 +02:00
|
|
|
log.init.debug("Using system harfbuzz engine")
|
2014-05-13 18:01:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def check_pyqt_core():
|
|
|
|
"""Check if PyQt core is installed."""
|
2014-05-09 15:32:01 +02:00
|
|
|
try:
|
|
|
|
import PyQt5.QtCore # pylint: disable=unused-variable
|
|
|
|
except ImportError:
|
2014-06-27 12:25:27 +02:00
|
|
|
text = _missing_str('PyQt5',
|
|
|
|
debian="apt-get install python3-pyqt5 "
|
|
|
|
"python3-pyqt5.qtwebkit",
|
2014-08-15 18:58:46 +02:00
|
|
|
arch="pacman -S python-pyqt5 qt5-webkit<br />"
|
2014-06-27 12:25:27 +02:00
|
|
|
"or install the qutebrowser package from AUR",
|
|
|
|
windows="Use the installer by Riverbank computing "
|
2014-08-15 18:58:46 +02:00
|
|
|
"or the standalone qutebrowser exe.<br />"
|
2014-06-27 12:25:27 +02:00
|
|
|
"http://www.riverbankcomputing.co.uk/"
|
|
|
|
"software/pyqt/download5")
|
2014-08-15 18:58:46 +02:00
|
|
|
text = text.replace('<b>', '')
|
|
|
|
text = text.replace('</b>', '')
|
|
|
|
text = text.replace('<br />', '\n')
|
2014-08-26 19:10:14 +02:00
|
|
|
if tkinter:
|
|
|
|
root = tkinter.Tk()
|
2014-07-27 22:57:50 +02:00
|
|
|
root.withdraw()
|
2014-08-26 19:10:14 +02:00
|
|
|
tkinter.messagebox.showerror("qutebrowser: Fatal error!", text)
|
2014-07-27 22:57:50 +02:00
|
|
|
else:
|
|
|
|
print(text, file=sys.stderr)
|
2014-05-09 15:32:01 +02:00
|
|
|
if '--debug' in sys.argv:
|
2014-05-13 19:46:57 +02:00
|
|
|
print(file=sys.stderr)
|
2014-05-09 15:32:01 +02:00
|
|
|
traceback.print_exc()
|
|
|
|
sys.exit(1)
|
2014-05-13 18:01:10 +02:00
|
|
|
|
|
|
|
|
2014-06-16 11:26:09 +02:00
|
|
|
def check_qt_version():
|
|
|
|
"""Check if the Qt version is recent enough."""
|
|
|
|
from PyQt5.QtCore import qVersion
|
2014-08-26 20:25:11 +02:00
|
|
|
from qutebrowser.utils import qtutils
|
2014-08-26 19:23:06 +02:00
|
|
|
if qtutils.version_check('5.2.0', operator.lt):
|
2014-06-27 12:25:27 +02:00
|
|
|
text = ("Fatal error: Qt and PyQt >= 5.2.0 are required, but {} is "
|
|
|
|
"installed.".format(qVersion()))
|
|
|
|
_die(text, exception=False)
|
2014-06-16 11:26:09 +02:00
|
|
|
|
2014-06-16 12:11:10 +02:00
|
|
|
|
2014-09-16 06:21:40 +02:00
|
|
|
def check_libraries():
|
|
|
|
"""Check if all needed Python libraries are installed."""
|
|
|
|
modules = {
|
|
|
|
'PyQt5.QtWebKit':
|
|
|
|
_missing_str("QtWebKit",
|
|
|
|
debian="apt-get install python3-pyqt5.qtwebkit",
|
|
|
|
arch="pacman -S qt5-webkit"),
|
|
|
|
'pkg_resources':
|
|
|
|
_missing_str("pkg_resources",
|
|
|
|
debian="apt-get install python3-pkg-resources",
|
|
|
|
arch="pacman -S python-setuptools",
|
|
|
|
windows="Run python -m ensurepip "
|
|
|
|
"(python >= 3.4) or scripts/ez_setup.py."),
|
|
|
|
'pypeg2':
|
|
|
|
_missing_str("pypeg2",
|
|
|
|
debian="No package available, do 'apt-get install "
|
|
|
|
"python3-pip' and then install via pip3.",
|
|
|
|
arch="Install python-pypeg2 from the AUR",
|
|
|
|
windows="Install via pip.",
|
2014-11-09 21:48:33 +01:00
|
|
|
pip="pypeg2"),
|
2014-09-16 06:21:40 +02:00
|
|
|
'jinja2':
|
|
|
|
_missing_str("jinja2",
|
|
|
|
debian="apt-get install python3-jinja2",
|
2015-01-10 03:23:56 +01:00
|
|
|
arch="Install python-jinja from community",
|
2014-09-16 06:21:40 +02:00
|
|
|
windows="Install from http://www.lfd.uci.edu/"
|
|
|
|
"~gohlke/pythonlibs/#jinja2 or via pip.",
|
|
|
|
pip="jinja2"),
|
|
|
|
'pygments':
|
|
|
|
_missing_str("pygments",
|
|
|
|
debian="apt-get install python3-pygments",
|
2015-01-10 03:23:56 +01:00
|
|
|
arch="Install python-pygments from community",
|
2014-09-16 06:21:40 +02:00
|
|
|
windows="Install from http://www.lfd.uci.edu/"
|
|
|
|
"~gohlke/pythonlibs/#pygments or via pip.",
|
|
|
|
pip="pygments"),
|
|
|
|
}
|
|
|
|
for name, text in modules.items():
|
|
|
|
try:
|
|
|
|
importlib.import_module(name)
|
|
|
|
except ImportError:
|
|
|
|
_die(text)
|
2014-09-16 06:39:39 +02:00
|
|
|
|
|
|
|
|
|
|
|
def remove_inputhook():
|
|
|
|
"""Remove the PyQt input hook.
|
|
|
|
|
|
|
|
Doing this means we can't use the interactive shell anymore (which we don't
|
|
|
|
anyways), but we can use pdb instead."""
|
|
|
|
from PyQt5.QtCore import pyqtRemoveInputHook
|
|
|
|
pyqtRemoveInputHook()
|
|
|
|
|
|
|
|
|
|
|
|
def init_log(args):
|
|
|
|
"""Initialize logging.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
args: The argparse namespace.
|
|
|
|
"""
|
|
|
|
from qutebrowser.utils import log
|
|
|
|
log.init_log(args)
|
|
|
|
log.init.debug("Log initialized.")
|
|
|
|
|
|
|
|
|
|
|
|
def earlyinit(args):
|
|
|
|
"""Do all needed early initialisation.
|
|
|
|
|
|
|
|
Note that it's vital the other earlyinit functions get called in the right
|
|
|
|
order!
|
|
|
|
|
|
|
|
Args:
|
|
|
|
args: The argparse namespace.
|
|
|
|
"""
|
|
|
|
# First we initialize the faulthandler as early as possible, so we
|
|
|
|
# theoretically could catch segfaults occuring later during earlyinit.
|
|
|
|
init_faulthandler()
|
|
|
|
# Here we check if QtCore is available, and if not, print a message to the
|
|
|
|
# console or via Tk.
|
|
|
|
check_pyqt_core()
|
|
|
|
# Now the faulthandler is enabled we fix the Qt harfbuzzing library, before
|
|
|
|
# importing QtWidgets.
|
|
|
|
fix_harfbuzz(args)
|
|
|
|
# Now we can be sure QtCore is available, so we can print dialogs on
|
|
|
|
# errors, so people only using the GUI notice them as well.
|
|
|
|
check_qt_version()
|
|
|
|
remove_inputhook()
|
|
|
|
check_libraries()
|
|
|
|
init_log(args)
|