diff options
Diffstat (limited to 'qutebrowser/misc/backendproblem.py')
-rw-r--r-- | qutebrowser/misc/backendproblem.py | 304 |
1 files changed, 147 insertions, 157 deletions
diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py index 3e14719e0..4c0e184ac 100644 --- a/qutebrowser/misc/backendproblem.py +++ b/qutebrowser/misc/backendproblem.py @@ -27,12 +27,12 @@ import enum import shutil import argparse import dataclasses -from typing import Any, List, Sequence, Tuple, Optional +from typing import Any, Optional, Sequence, Tuple -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import (QDialog, QPushButton, QHBoxLayout, QVBoxLayout, QLabel, +from qutebrowser.qt.core import Qt +from qutebrowser.qt.widgets import (QDialog, QPushButton, QHBoxLayout, QVBoxLayout, QLabel, QMessageBox, QWidget) -from PyQt5.QtNetwork import QSslSocket +from qutebrowser.qt.network import QSslSocket from qutebrowser.config import config, configfiles from qutebrowser.utils import (usertypes, version, qtutils, log, utils, @@ -44,10 +44,10 @@ class _Result(enum.IntEnum): """The result code returned by the backend problem dialog.""" - quit = QDialog.Accepted + 1 - restart = QDialog.Accepted + 2 - restart_webkit = QDialog.Accepted + 3 - restart_webengine = QDialog.Accepted + 4 + quit = QDialog.DialogCode.Accepted + 1 + restart = QDialog.DialogCode.Accepted + 2 + restart_webkit = QDialog.DialogCode.Accepted + 3 + restart_webengine = QDialog.DialogCode.Accepted + 4 @dataclasses.dataclass @@ -71,27 +71,33 @@ def _other_backend(backend: usertypes.Backend) -> Tuple[usertypes.Backend, str]: return (other_backend, other_setting) -def _error_text(because: str, text: str, backend: usertypes.Backend) -> str: +def _error_text( + because: str, + text: str, + backend: usertypes.Backend, + suggest_other_backend: bool = False, +) -> str: """Get an error text for the given information.""" - other_backend, other_setting = _other_backend(backend) - if other_backend == usertypes.Backend.QtWebKit: - warning = ("<i>Note that QtWebKit hasn't been updated since " - "July 2017 (including security updates).</i>") - suffix = " (not recommended)" - else: - warning = "" - suffix = "" - return ("<b>Failed to start with the {backend} backend!</b>" - "<p>qutebrowser tried to start with the {backend} backend but " - "failed because {because}.</p>{text}" - "<p><b>Forcing the {other_backend.name} backend{suffix}</b></p>" - "<p>This forces usage of the {other_backend.name} backend by " - "setting the <i>backend = '{other_setting}'</i> option " - "(if you have a <i>config.py</i> file, you'll need to set " - "this manually). {warning}</p>".format( - backend=backend.name, because=because, text=text, - other_backend=other_backend, other_setting=other_setting, - warning=warning, suffix=suffix)) + text = (f"<b>Failed to start with the {backend.name} backend!</b>" + f"<p>qutebrowser tried to start with the {backend.name} backend but " + f"failed because {because}.</p>{text}") + + if suggest_other_backend: + other_backend, other_setting = _other_backend(backend) + if other_backend == usertypes.Backend.QtWebKit: + warning = ("<i>Note that QtWebKit hasn't been updated since " + "July 2017 (including security updates).</i>") + suffix = " (not recommended)" + else: + warning = "" + suffix = "" + + text += (f"<p><b>Forcing the {other_backend.name} backend{suffix}</b></p>" + f"<p>This forces usage of the {other_backend.name} backend by " + f"setting the <i>backend = '{other_setting}'</i> option " + f"(if you have a <i>config.py</i> file, you'll need to set " + f"this manually). {warning}</p>") + return text class _Dialog(QDialog): @@ -101,17 +107,18 @@ class _Dialog(QDialog): def __init__(self, *, because: str, text: str, backend: usertypes.Backend, + suggest_other_backend: bool = True, buttons: Sequence[_Button] = None, parent: QWidget = None) -> None: super().__init__(parent) vbox = QVBoxLayout(self) - other_backend, other_setting = _other_backend(backend) - text = _error_text(because, text, backend) + text = _error_text(because, text, backend, + suggest_other_backend=suggest_other_backend) label = QLabel(text) label.setWordWrap(True) - label.setTextFormat(Qt.RichText) + label.setTextFormat(Qt.TextFormat.RichText) vbox.addWidget(label) hbox = QHBoxLayout() @@ -121,13 +128,15 @@ class _Dialog(QDialog): quit_button.clicked.connect(lambda: self.done(_Result.quit)) hbox.addWidget(quit_button) - backend_text = "Force {} backend".format(other_backend.name) - if other_backend == usertypes.Backend.QtWebKit: - backend_text += ' (not recommended)' - backend_button = QPushButton(backend_text) - backend_button.clicked.connect(functools.partial( - self._change_setting, 'backend', other_setting)) - hbox.addWidget(backend_button) + if suggest_other_backend: + other_backend, other_setting = _other_backend(backend) + backend_text = "Force {} backend".format(other_backend.name) + if other_backend == usertypes.Backend.QtWebKit: + backend_text += ' (not recommended)' + backend_button = QPushButton(backend_text) + backend_button.clicked.connect(functools.partial( + self._change_setting, 'backend', other_setting)) + hbox.addWidget(backend_button) for button in buttons: btn = QPushButton(button.text) @@ -181,7 +190,7 @@ class _BackendProblemChecker: status = dialog.exec() self._save_manager.save_all(is_exit=True) - if status in [_Result.quit, QDialog.Rejected]: + if status in [_Result.quit, QDialog.DialogCode.Rejected]: pass elif status == _Result.restart_webkit: quitter.instance.restart(override_args={'backend': 'webkit'}) @@ -194,95 +203,25 @@ class _BackendProblemChecker: sys.exit(usertypes.Exit.err_init) - def _xwayland_options(self) -> Tuple[str, List[_Button]]: - """Get buttons/text for a possible XWayland solution.""" - buttons = [] - text = "<p>You can work around this in one of the following ways:</p>" - - if 'DISPLAY' in os.environ: - # XWayland is available, but QT_QPA_PLATFORM=wayland is set - buttons.append( - _Button("Force XWayland", 'qt.force_platform', 'xcb')) - text += ("<p><b>Force Qt to use XWayland</b></p>" - "<p>This allows you to use the newer QtWebEngine backend " - "(based on Chromium). " - "This sets the <i>qt.force_platform = 'xcb'</i> option " - "(if you have a <i>config.py</i> file, you'll need to " - "set this manually).</p>") - else: - text += ("<p><b>Set up XWayland</b></p>" - "<p>This allows you to use the newer QtWebEngine backend " - "(based on Chromium). ") - - return text, buttons - - def _handle_wayland_webgl(self) -> None: - """On older graphic hardware, WebGL on Wayland causes segfaults. - - See https://github.com/qutebrowser/qutebrowser/issues/5313 - """ - self._assert_backend(usertypes.Backend.QtWebEngine) - - if os.environ.get('QUTE_SKIP_WAYLAND_WEBGL_CHECK'): - return - - platform = objects.qapp.platformName() - if platform not in ['wayland', 'wayland-egl']: - return - - # Only Qt 5.14 should be affected - if not qtutils.version_check('5.14', compiled=False): - return - if qtutils.version_check('5.15', compiled=False): - return - - # Newer graphic hardware isn't affected - opengl_info = version.opengl_info() - if (opengl_info is None or - opengl_info.gles or - opengl_info.version is None or - opengl_info.version >= (4, 3)): - return - - # If WebGL is turned off, we're fine - if not config.val.content.webgl: - return - - text, buttons = self._xwayland_options() - - buttons.append(_Button("Turn off WebGL (recommended)", - 'content.webgl', - False)) - text += ("<p><b>Disable WebGL (recommended)</b></p>" - "This sets the <i>content.webgl = False</i> option " - "(if you have a <i>config.py</i> file, you'll need to " - "set this manually).</p>") - - self._show_dialog(backend=usertypes.Backend.QtWebEngine, - because=("of frequent crashes with Qt 5.14 on " - "Wayland with older graphics hardware"), - text=text, - buttons=buttons) - def _try_import_backends(self) -> _BackendImports: """Check whether backends can be imported and return BackendImports.""" # pylint: disable=unused-import results = _BackendImports() try: - from PyQt5 import QtWebKit - from PyQt5.QtWebKit import qWebKitVersion - from PyQt5 import QtWebKitWidgets + from qutebrowser.qt import webkit, webkitwidgets except (ImportError, ValueError) as e: results.webkit_error = str(e) + assert results.webkit_error else: if not qtutils.is_new_qtwebkit(): results.webkit_error = "Unsupported legacy QtWebKit found" try: - from PyQt5 import QtWebEngineWidgets + from qutebrowser.qt import webenginecore, webenginewidgets except (ImportError, ValueError) as e: results.webengine_error = str(e) + assert results.webengine_error return results @@ -294,25 +233,14 @@ class _BackendProblemChecker: if QSslSocket.supportsSsl(): return - if qtutils.version_check('5.12.4'): - version_text = ("If you use OpenSSL 1.0 with a PyQt package from " - "PyPI (e.g. on Ubuntu 16.04), you will need to " - "build OpenSSL 1.1 from sources and set " - "LD_LIBRARY_PATH accordingly.") - else: - version_text = ("If you use OpenSSL 1.1 with a PyQt package from " - "PyPI (e.g. on Archlinux or Debian Stretch), you " - "need to set LD_LIBRARY_PATH to the path of " - "OpenSSL 1.0 or use Qt >= 5.12.4.") - - text = ("Could not initialize QtNetwork SSL support. {} This only " - "affects downloads and :adblock-update.".format(version_text)) + text = ("Could not initialize QtNetwork SSL support. This only " + "affects downloads and :adblock-update.") if fatal: errbox = msgbox.msgbox(parent=None, title="SSL error", text="Could not initialize SSL support.", - icon=QMessageBox.Critical, + icon=QMessageBox.Icon.Critical, plain_text=False) errbox.exec() sys.exit(usertypes.Exit.err_init) @@ -338,7 +266,7 @@ class _BackendProblemChecker: errbox = msgbox.msgbox(parent=None, title="No backend library found!", text=text, - icon=QMessageBox.Critical, + icon=QMessageBox.Icon.Critical, plain_text=False) errbox.exec() sys.exit(usertypes.Exit.err_init) @@ -363,26 +291,6 @@ class _BackendProblemChecker: raise utils.Unreachable - def _handle_cache_nuking(self) -> None: - """Nuke the QtWebEngine cache if the Qt version changed. - - WORKAROUND for https://bugreports.qt.io/browse/QTBUG-72532 - """ - if not configfiles.state.qt_version_changed: - return - - # Only nuke the cache in cases where we know there are problems. - # It seems these issues started with Qt 5.12. - # They should be fixed with Qt 5.12.5: - # https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/265408 - if qtutils.version_check('5.12.5', compiled=False): - return - - log.init.info("Qt version changed, nuking QtWebEngine cache") - cache_dir = os.path.join(standarddir.cache(), 'webengine') - if os.path.exists(cache_dir): - shutil.rmtree(cache_dir) - def _handle_serviceworker_nuking(self) -> None: """Nuke the service workers directory if the Qt version changed. @@ -391,13 +299,7 @@ class _BackendProblemChecker: https://bugreports.qt.io/browse/QTBUG-82105 https://bugreports.qt.io/browse/QTBUG-93744 """ - if ('serviceworker_workaround' not in configfiles.state['general'] and - qtutils.version_check('5.14', compiled=False)): - # Nuke the service worker directory once for every install with Qt - # 5.14, given that it seems to cause a variety of segfaults. - configfiles.state['general']['serviceworker_workaround'] = '514' - reason = 'Qt 5.14' - elif configfiles.state.qt_version_changed: + if configfiles.state.qt_version_changed: reason = 'Qt version changed' elif configfiles.state.qtwe_version_changed: reason = 'QtWebEngine version changed' @@ -422,6 +324,93 @@ class _BackendProblemChecker: shutil.move(service_worker_dir, bak_dir) + def _confirm_chromium_version_changes(self) -> None: + """Ask if there are Chromium downgrades or a Qt 5 -> 6 upgrade.""" + versions = version.qtwebengine_versions(avoid_init=True) + change = configfiles.state.chromium_version_changed + if change == configfiles.VersionChange.major: + # FIXME:qt6 Remove this before the release, as it typically should + # not concern users? + text = ( + "Chromium/QtWebEngine upgrade detected:<br>" + f"You are <b>upgrading to QtWebEngine {versions.webengine}</b> but " + "used Qt 5 for the last qutebrowser launch.<br><br>" + "Data managed by Chromium will be upgraded. This is a <b>one-way " + "operation:</b> If you open qutebrowser with Qt 5 again later, any " + "Chromium data will be invalid and discarded.<br><br>" + "This affects page data such as cookies, but not data managed by " + "qutebrowser, such as your configuration or <tt>:open</tt> history." + ) + elif change == configfiles.VersionChange.downgrade: + text = ( + "Chromium/QtWebEngine downgrade detected:<br>" + f"You are <b>downgrading to QtWebEngine {versions.webengine}</b>." + "<br><br>" + "Data managed by Chromium will be discarded if you continue.<br><br>" + "This affects page data such as cookies, but not data managed by " + "qutebrowser, such as your configuration or <tt>:open</tt> history." + ) + else: + return + + box = msgbox.msgbox( + parent=None, + title="QtWebEngine version change", + text=text, + icon=QMessageBox.Icon.Warning, + plain_text=False, + buttons=QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Abort, + ) + response = box.exec() + if response != QMessageBox.StandardButton.Ok: + sys.exit(usertypes.Exit.err_init) + + def _check_webengine_version(self) -> None: + versions = version.qtwebengine_versions(avoid_init=True) + if versions.webengine < utils.VersionNumber(5, 15, 2): + text = ( + "QtWebEngine >= 5.15.2 is required for qutebrowser, but " + f"{versions.webengine} is installed.") + errbox = msgbox.msgbox(parent=None, + title="QtWebEngine too old", + text=text, + icon=QMessageBox.Icon.Critical, + plain_text=False) + errbox.exec() + sys.exit(usertypes.Exit.err_init) + + def _check_software_rendering(self) -> None: + """Avoid crashing software rendering settings. + + WORKAROUND for https://bugreports.qt.io/browse/QTBUG-103372 + Fixed with QtWebEngine 6.3.1. + """ + self._assert_backend(usertypes.Backend.QtWebEngine) + versions = version.qtwebengine_versions(avoid_init=True) + + if versions.webengine != utils.VersionNumber(6, 3): + return + + if os.environ.get('QT_QUICK_BACKEND') != 'software': + return + + text = ("You can instead force software rendering on the Chromium level (sets " + "<tt>qt.force_software_rendering</tt> to <tt>chromium</tt> instead of " + "<tt>qt-quick</tt>).") + + button = _Button("Force Chromium software rendering", + 'qt.force_software_rendering', + 'chromium') + self._show_dialog( + backend=usertypes.Backend.QtWebEngine, + suggest_other_backend=False, + because="a Qt 6.3.0 bug causes instant crashes with Qt Quick software rendering", + text=text, + buttons=[button], + ) + + raise utils.Unreachable + def _assert_backend(self, backend: usertypes.Backend) -> None: assert objects.backend == backend, objects.backend @@ -429,10 +418,11 @@ class _BackendProblemChecker: """Run all checks.""" self._check_backend_modules() if objects.backend == usertypes.Backend.QtWebEngine: + self._check_webengine_version() self._handle_ssl_support() - self._handle_wayland_webgl() - self._handle_cache_nuking() self._handle_serviceworker_nuking() + self._check_software_rendering() + self._confirm_chromium_version_changes() else: self._assert_backend(usertypes.Backend.QtWebKit) self._handle_ssl_support(fatal=True) |