qutebrowser/qutebrowser/utils/earlyinit.py

250 lines
9.4 KiB
Python
Raw Normal View History

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).
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
import signal
try:
2014-08-26 19:10:14 +02:00
import tkinter # pylint: disable=import-error
except ImportError:
2014-08-26 19:10:14 +02:00
tkinter = None
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>",
"Check your package manager for similiarly named packages or "
"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-05-13 19:24:43 +02:00
# Now we initialize the faulthandler as early as possible, so we theoretically
# could catch segfaults occuring later.
2014-05-13 18:01:10 +02:00
def init_faulthandler():
"""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-05-13 18:01:10 +02:00
"""
2014-08-01 00:55:18 +02:00
if sys.__stderr__ is None:
# 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
#
# 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-08-01 00:55:18 +02:00
faulthandler.enable(sys.__stderr__)
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-05-13 19:24:43 +02:00
# Now the faulthandler is enabled we fix the Qt harfbuzzing library, before
2014-06-16 13:54:47 +02:00
# importing QtWidgets.
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
IMPORTANT: This needs to be done before QWidgets is imported in any way!
2014-06-04 14:15:33 +02:00
Args:
args: The argparse namespace.
2014-05-13 18:01:10 +02:00
"""
from qutebrowser.utils.log import init as logger
from PyQt5.QtCore import qVersion
if 'PyQt5.QtWidgets' in sys.modules:
logger.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':
if qVersion() == '5.3.0':
logger.debug("Using new harfbuzz engine (auto)")
os.environ['QT_HARFBUZZ'] = 'new'
else:
logger.debug("Using old harfbuzz engine (auto)")
os.environ['QT_HARFBUZZ'] = 'old'
elif args.harfbuzz in ('old', 'new'):
# 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...
logger.debug("Using {} harfbuzz engine (forced)".format(args.harfbuzz))
os.environ['QT_HARFBUZZ'] = args.harfbuzz
else:
logger.debug("Using system harfbuzz engine")
2014-05-13 18:01:10 +02:00
2014-05-13 19:24:43 +02:00
# At this point we can safely import Qt stuff, but we can't be sure it's
# actually available.
# Here we check if QtCore is available, and if not, print a message to the
# console.
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()
root.withdraw()
2014-08-26 19:10:14 +02:00
tkinter.messagebox.showerror("qutebrowser: Fatal error!", text)
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-05-13 19:24:43 +02:00
# 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.
2014-06-16 11:26:09 +02:00
def check_qt_version():
"""Check if the Qt version is recent enough."""
2014-06-23 07:45:04 +02:00
import operator
2014-06-16 11:26:09 +02:00
from PyQt5.QtCore import qVersion
from qutebrowser.utils import qt as qtutils
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-05-13 18:01:10 +02:00
def check_pyqt_webkit():
"""Check if PyQt WebKit is installed."""
2014-05-09 15:32:01 +02:00
try:
2014-05-13 19:24:43 +02:00
import PyQt5.QtWebKit # pylint: disable=unused-variable
2014-05-09 15:32:01 +02:00
except ImportError:
2014-06-27 12:25:27 +02:00
text = _missing_str("QtWebKit",
debian="apt-get install python3-pyqt5.qtwebkit",
arch="pacman -S qt5-webkit")
_die(text)
2014-06-16 11:20:26 +02:00
2014-06-16 12:11:10 +02:00
2014-06-16 11:20:26 +02:00
def check_pkg_resources():
"""Check if pkg_resources is installed."""
try:
import pkg_resources # pylint: disable=unused-variable
except ImportError:
2014-06-27 12:25:27 +02:00
text = _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.")
_die(text)
def check_pypeg2():
"""Check if pypeg2 is installed."""
try:
import pypeg2 # pylint: disable=unused-variable
except ImportError:
text = _missing_str("pypeg2",
2014-08-15 18:58:46 +02:00
debian="No package available, install via pip.",
arch="Install python-pypeg2 from the AUR",
windows="Install via pip.",
pip="pypeg2 --allow-external pypeg2 "
"--allow-unverified pypeg2")
2014-06-27 12:25:27 +02:00
_die(text)