From 093f34183c1b2a51c6c6e38d20c1c9e19a3ae909 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 27 Sep 2017 22:39:49 +0200 Subject: [PATCH] Add improved checks for Nouveau/Wayland for QtWebEngine Closes #2368 Closes #2932 See #2335 --- doc/help/settings.asciidoc | 17 ++ qutebrowser/app.py | 12 +- qutebrowser/browser/webengine/webenginetab.py | 12 +- qutebrowser/config/configdata.yml | 9 + qutebrowser/misc/backendproblem.py | 182 ++++++++++++++++++ 5 files changed, 218 insertions(+), 14 deletions(-) create mode 100644 qutebrowser/misc/backendproblem.py diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 36159b2db..c2a7f55fb 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -179,6 +179,7 @@ |<>|The default font size for fixed-pitch text. |<>|The hard minimum font size. |<>|The minimum logical font size that is applied when zooming out. +|<>|Force software rendering for QtWebEngine. |<>|Controls when a hint can be automatically followed without pressing Enter. |<>|A timeout (in milliseconds) to ignore normal-mode key bindings after a successful auto-follow. |<>|CSS border value for hints. @@ -2262,6 +2263,22 @@ Type: <> Default: +pass:[6]+ +[[force_software_rendering]] +=== force_software_rendering +Force software rendering for QtWebEngine. +This is needed for QtWebEngine to work with Nouveau drivers. + +Type: <> + +Valid values: + + * +true+ + * +false+ + +Default: +pass:[false]+ + +This setting is only available with the QtWebEngine backend. + [[hints.auto_follow]] === hints.auto_follow Controls when a hint can be automatically followed without pressing Enter. diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 85f0e9410..b8649b7fb 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -54,7 +54,8 @@ from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.keyinput import macros from qutebrowser.mainwindow import mainwindow, prompt from qutebrowser.misc import (readline, ipc, savemanager, sessions, - crashsignal, earlyinit, sql, cmdhistory) + crashsignal, earlyinit, sql, cmdhistory, + backendproblem) from qutebrowser.utils import (log, version, message, utils, urlutils, objreg, usertypes, standarddir, error) # pylint: disable=unused-import @@ -389,14 +390,17 @@ def _init_modules(args, crash_handler): crash_handler: The CrashHandler instance. """ # pylint: disable=too-many-statements - log.init.debug("Initializing prompts...") - prompt.init() - log.init.debug("Initializing save manager...") save_manager = savemanager.SaveManager(qApp) objreg.register('save-manager', save_manager) configinit.late_init(save_manager) + log.init.debug("Checking backend requirements...") + backendproblem.init() + + log.init.debug("Initializing prompts...") + prompt.init() + log.init.debug("Initializing network...") networkmanager.init() diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 5c2533422..707ea10c3 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -38,7 +38,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, webenginesettings) from qutebrowser.misc import miscwidgets from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, - message, objreg, jinja, debug, version) + message, objreg, jinja, debug) _qute_scheme_handler = None @@ -50,16 +50,8 @@ def init(): # won't work... # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html global _qute_scheme_handler + app = QApplication.instance() - - software_rendering = (os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or - 'QT_XCB_FORCE_SOFTWARE_OPENGL' in os.environ) - if version.opengl_vendor() == 'nouveau' and not software_rendering: - # FIXME:qtwebengine display something more sophisticated here - raise browsertab.WebTabError( - "QtWebEngine is not supported with Nouveau graphics (unless " - "QT_XCB_FORCE_SOFTWARE_OPENGL is set as environment variable).") - log.init.debug("Initializing qute://* handler...") _qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app) _qute_scheme_handler.install(webenginesettings.default_profile) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index c597fb188..9a3574282 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -101,6 +101,15 @@ qt_args: https://peter.sh/experiments/chromium-command-line-switches/ for a list) will work. +force_software_rendering: + type: Bool + default: false + backend: QtWebEngine + desc: >- + Force software rendering for QtWebEngine. + + This is needed for QtWebEngine to work with Nouveau drivers. + backend: type: name: String diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py new file mode 100644 index 000000000..15d27212f --- /dev/null +++ b/qutebrowser/misc/backendproblem.py @@ -0,0 +1,182 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2017 Florian Bruhin (The Compiler) +# +# 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 . + +"""Dialogs shown when there was a problem with a backend choice.""" + +import os +import sys +import functools + +import attr +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import (QApplication, QDialog, QPushButton, QHBoxLayout, + QVBoxLayout, QLabel) + +from qutebrowser.config import config +from qutebrowser.utils import usertypes, objreg, version +from qutebrowser.misc import objects + + +_Result = usertypes.enum('_Result', ['quit', 'restart'], is_int=True, + start=QDialog.Accepted + 1) + + +@attr.s +class _Button: + + """A button passed to BackendProblemDialog.""" + + text = attr.ib() + setting = attr.ib() + value = attr.ib() + default = attr.ib(default=False) + + +class _Dialog(QDialog): + + """A dialog which gets shown if there are issues with the backend.""" + + def __init__(self, because, text, backend, buttons=None, parent=None): + super().__init__(parent) + vbox = QVBoxLayout(self) + + other_backend = { + usertypes.Backend.QtWebKit: usertypes.Backend.QtWebEngine, + usertypes.Backend.QtWebEngine: usertypes.Backend.QtWebKit, + }[backend] + other_setting = other_backend.name.lower()[2:] + + label = QLabel( + "Failed to start with the {backend} backend!" + "

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 " + "(if you have a config.py file, you'll need to set " + "this manually).

".format( + backend=backend.name, because=because, text=text, + other_backend=other_backend, other_setting=other_setting), + wordWrap=True) + label.setTextFormat(Qt.RichText) + vbox.addWidget(label) + + hbox = QHBoxLayout() + buttons = [] if buttons is None else buttons + + quit_button = QPushButton("Quit") + quit_button.clicked.connect(lambda: self.done(_Result.quit)) + hbox.addWidget(quit_button) + + backend_button = QPushButton("Force {} backend".format( + other_backend.name)) + backend_button.clicked.connect(functools.partial( + self._change_setting, 'backend', other_setting)) + hbox.addWidget(backend_button) + + for button in buttons: + btn = QPushButton(button.text, default=button.default) + btn.clicked.connect(functools.partial( + self._change_setting, button.setting, button.value)) + hbox.addWidget(btn) + + vbox.addLayout(hbox) + + def _change_setting(self, setting, value): + """Change the given setting and restart.""" + config.instance.set_obj(setting, value, save_yaml=True) + self.done(_Result.restart) + + +def _show_dialog(*args, **kwargs): + """Show a dialog for a backend problem.""" + dialog = _Dialog(*args, **kwargs) + + status = dialog.exec_() + + if status == _Result.quit: + 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) + + +def _handle_nouveau_graphics(): + force_sw_var = 'QT_XCB_FORCE_SOFTWARE_OPENGL' + + if version.opengl_vendor() != 'nouveau': + return + + if (os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or + force_sw_var in os.environ): + return + + if config.force_software_rendering: + os.environ[force_sw_var] = '1' + return + + button = _Button("Force software rendering", 'force_software_rendering', + True) + _show_dialog( + backend=usertypes.Backend.QtWebEngine, + because="you're using Nouveau graphics", + text="

There are two ways to fix this:

" + "

Forcing software rendering

" + "

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 " + "(if you have a config.py file, you'll need to set this " + "manually).

", + buttons=[button], + ) + + # Should never be reached + assert False + + +def _handle_wayland(): + if QApplication.instance().platformName() not in ['wayland', 'wayland-egl']: + return + if os.environ.get('DISPLAY'): + # When DISPLAY is set but with the wayland/wayland-egl platform plugin, + # QtWebEngine will do the right hting. + return + + _show_dialog( + backend=usertypes.Backend.QtWebEngine, + because="you're using Wayland", + text="

There are two ways to fix this:

" + "

Set up XWayland

" + "

This allows you to use the newer QtWebEngine backend (based " + "on Chromium). " + ) + + # Should never be reached + assert False + + +def init(): + if objects.backend == usertypes.Backend.QtWebEngine: + _handle_wayland() + _handle_nouveau_graphics() + else: + assert objects.backend == usertypes.Backend.QtWebKit, objects.backend