From fa902c5d82fd6366289fe8e9d13d72ba4dfc8a85 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 27 Sep 2017 23:04:18 +0200 Subject: [PATCH] Improve error dialogs when QtWebKit/QtWebEngine was not found --- qutebrowser/misc/backendproblem.py | 113 +++++++++++++++++++++++++++-- qutebrowser/misc/earlyinit.py | 31 -------- qutebrowser/misc/savemanager.py | 5 ++ 3 files changed, 111 insertions(+), 38 deletions(-) diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py index 15d27212f..caa21f97d 100644 --- a/qutebrowser/misc/backendproblem.py +++ b/qutebrowser/misc/backendproblem.py @@ -22,15 +22,16 @@ import os import sys import functools +import html import attr from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QApplication, QDialog, QPushButton, QHBoxLayout, - QVBoxLayout, QLabel) + QVBoxLayout, QLabel, QMessageBox) from qutebrowser.config import config -from qutebrowser.utils import usertypes, objreg, version -from qutebrowser.misc import objects +from qutebrowser.utils import usertypes, objreg, version, qtutils +from qutebrowser.misc import objects, msgbox _Result = usertypes.enum('_Result', ['quit', 'restart'], is_int=True, @@ -67,8 +68,8 @@ class _Dialog(QDialog): "

qutebrowser tried to start with the {backend} backend but " "failed because {because}.

{text}" "

Forcing the {other_backend.name} backend

" - "

This forces usage of the {other_backend.name} backend. " - "This sets the backend = '{other_setting}' setting " + "

This forces usage of the {other_backend.name} backend by " + "setting the backend = '{other_setting}' option " "(if you have a config.py file, you'll need to set " "this manually).

".format( backend=backend.name, because=because, text=text, @@ -101,6 +102,8 @@ class _Dialog(QDialog): def _change_setting(self, setting, value): """Change the given setting and restart.""" config.instance.set_obj(setting, value, save_yaml=True) + save_manager = objreg.get('save-manager') + save_manager.save_all(is_exit=True) self.done(_Result.restart) @@ -110,13 +113,15 @@ def _show_dialog(*args, **kwargs): status = dialog.exec_() - if status == _Result.quit: + if status in [_Result.quit, QDialog.Rejected]: sys.exit(usertypes.Exit.err_init) elif status == _Result.restart: # FIXME pass --backend webengine quitter = objreg.get('quitter') quitter.restart() sys.exit(usertypes.Exit.err_init) + else: + assert False, status def _handle_nouveau_graphics(): @@ -143,7 +148,7 @@ def _handle_nouveau_graphics(): "

This allows you to use the newer QtWebEngine backend (based " "on Chromium) but could have noticable performance impact " "(depending on your hardware). " - "This sets the force_software_rendering = True setting " + "This sets the force_software_rendering = True option " "(if you have a config.py file, you'll need to set this " "manually).

", buttons=[button], @@ -174,7 +179,101 @@ def _handle_wayland(): assert False +@attr.s +class BackendImports: + + """Whether backend modules could be imported.""" + + webkit_available = attr.ib(default=None) + webengine_available = attr.ib(default=None) + webkit_error = attr.ib(default=None) + webengine_error = attr.ib(default=None) + + +def _try_import_backends(): + """Check whether backends can be imported and return BackendImports.""" + results = BackendImports() + + try: + from PyQt5 import QtWebKit + from PyQt5 import QtWebKitWidgets + except ImportError as e: + results.webkit_available = False + results.webkit_error = str(e) + else: + if qtutils.is_new_qtwebkit(): + results.webkit_available = True + else: + results.webkit_available = False + results.webkit_error = "Unsupported legacy QtWebKit found" + + try: + from PyQt5 import QtWebEngineWidgets + except ImportError as e: + results.webengine_available = False + results.webengine_error = str(e) + else: + results.webengine_available = True + + assert results.webkit_available is not None + assert results.webengine_available is not None + if not results.webkit_available: + assert results.webkit_error is not None + if not results.webengine_available: + assert results.webengine_error is not None + + return results + + +def _check_backend_modules(): + """Check for the modules needed for QtWebKit/QtWebEngine.""" + imports = _try_import_backends() + + if imports.webkit_available and imports.webengine_available: + return + elif not imports.webkit_available and not imports.webengine_available: + text = ("

qutebrowser needs QtWebKit or QtWebEngine, but neither " + "could be imported!

" + "

The errors encountered were:

".format( + webkit_error=html.escape(imports.webkit_error), + webengine_error=html.escape(imports.webengine_error))) + errbox = msgbox.msgbox(parent=None, + title="No backend library found!", + text=text, + icon=QMessageBox.Critical, + plain_text=False) + errbox.exec_() + sys.exit(usertypes.Exit.err_init) + elif objects.backend == usertypes.Backend.QtWebKit: + if imports.webkit_available: + return + assert imports.webengine_available + _show_dialog( + backend=usertypes.Backend.QtWebKit, + because="QtWebKit could not be imported", + text="

The error encountered was:
{}

".format( + html.escape(imports.webkit_error)) + ) + elif objects.backend == usertypes.Backend.QtWebEngine: + if imports.webengine_available: + return + assert imports.webkit_available + _show_dialog( + backend=usertypes.Backend.QtWebEngine, + because="QtWebEngine could not be imported", + text="

The error encountered was:
{}

".format( + html.escape(imports.webengine_error)) + ) + + # Should never be reached + assert False + + def init(): + _check_backend_modules() if objects.backend == usertypes.Backend.QtWebEngine: _handle_wayland() _handle_nouveau_graphics() diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index c2ab454ac..f481f4dba 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -247,35 +247,6 @@ def check_libraries(): _check_modules(modules) -def check_backend_libraries(backend): - """Make sure the libraries needed by the given backend are available. - - Args: - backend: The backend as usertypes.Backend member. - """ - from qutebrowser.utils import usertypes - if backend == usertypes.Backend.QtWebEngine: - modules = { - 'PyQt5.QtWebEngineWidgets': - _missing_str("QtWebEngine", webengine=True), - } - else: - assert backend == usertypes.Backend.QtWebKit, backend - modules = { - 'PyQt5.QtWebKit': _missing_str("PyQt5.QtWebKit"), - 'PyQt5.QtWebKitWidgets': _missing_str("PyQt5.QtWebKitWidgets"), - } - _check_modules(modules) - - -def check_new_webkit(backend): - """Make sure we use QtWebEngine or a new QtWebKit.""" - from qutebrowser.utils import usertypes, qtutils - if backend == usertypes.Backend.QtWebKit and not qtutils.is_new_qtwebkit(): - _die("qutebrowser does not support legacy QtWebKit versions anymore, " - "see the installation docs for details.") - - def remove_inputhook(): """Remove the PyQt input hook. @@ -338,6 +309,4 @@ def init_with_backend(backend): """ assert not isinstance(backend, str), backend assert backend is not None - check_backend_libraries(backend) check_backend_ssl_support(backend) - check_new_webkit(backend) diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py index ddda5325b..02001902c 100644 --- a/qutebrowser/misc/savemanager.py +++ b/qutebrowser/misc/savemanager.py @@ -164,6 +164,11 @@ class SaveManager(QObject): self.saveables[name].save(is_exit=is_exit, explicit=explicit, silent=silent, force=force) + def save_all(self, *args, **kwargs): + """Save all saveables.""" + for saveable in self.saveables: + self.save(saveable, *args, **kwargs) + @pyqtSlot() def autosave(self): """Slot used when the configs are auto-saved."""