Merge branch 'webengine-by-default'
This commit is contained in:
commit
4e57b79e91
@ -179,6 +179,7 @@
|
|||||||
|<<fonts.web.size.default_fixed,fonts.web.size.default_fixed>>|The default font size for fixed-pitch text.
|
|<<fonts.web.size.default_fixed,fonts.web.size.default_fixed>>|The default font size for fixed-pitch text.
|
||||||
|<<fonts.web.size.minimum,fonts.web.size.minimum>>|The hard minimum font size.
|
|<<fonts.web.size.minimum,fonts.web.size.minimum>>|The hard minimum font size.
|
||||||
|<<fonts.web.size.minimum_logical,fonts.web.size.minimum_logical>>|The minimum logical font size that is applied when zooming out.
|
|<<fonts.web.size.minimum_logical,fonts.web.size.minimum_logical>>|The minimum logical font size that is applied when zooming out.
|
||||||
|
|<<force_software_rendering,force_software_rendering>>|Force software rendering for QtWebEngine.
|
||||||
|<<hints.auto_follow,hints.auto_follow>>|Controls when a hint can be automatically followed without pressing Enter.
|
|<<hints.auto_follow,hints.auto_follow>>|Controls when a hint can be automatically followed without pressing Enter.
|
||||||
|<<hints.auto_follow_timeout,hints.auto_follow_timeout>>|A timeout (in milliseconds) to ignore normal-mode key bindings after a successful auto-follow.
|
|<<hints.auto_follow_timeout,hints.auto_follow_timeout>>|A timeout (in milliseconds) to ignore normal-mode key bindings after a successful auto-follow.
|
||||||
|<<hints.border,hints.border>>|CSS border value for hints.
|
|<<hints.border,hints.border>>|CSS border value for hints.
|
||||||
@ -291,18 +292,17 @@ Default: +pass:[false]+
|
|||||||
=== backend
|
=== backend
|
||||||
The backend to use to display websites.
|
The backend to use to display websites.
|
||||||
qutebrowser supports two different web rendering engines / backends, QtWebKit and QtWebEngine.
|
qutebrowser supports two different web rendering engines / backends, QtWebKit and QtWebEngine.
|
||||||
QtWebKit is based on WebKit (similar to Safari). It was discontinued by the Qt project with Qt 5.6, but picked up as a well maintained fork: https://github.com/annulen/webkit/wiki - qutebrowser only supports the fork.
|
QtWebKit was discontinued by the Qt project with Qt 5.6, but picked up as a well maintained fork: https://github.com/annulen/webkit/wiki - qutebrowser only supports the fork.
|
||||||
QtWebEngine is Qt's official successor to QtWebKit and based on the Chromium project. It's slightly more resource hungry that QtWebKit and has a couple of missing features in qutebrowser, but is generally the preferred choice.
|
QtWebEngine is Qt's official successor to QtWebKit. It's slightly more resource hungry that QtWebKit and has a couple of missing features in qutebrowser, but is generally the preferred choice.
|
||||||
|
|
||||||
Type: <<types,String>>
|
Type: <<types,String>>
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
|
|
||||||
* +auto+: Automatically select either QtWebEngine or QtWebKit
|
* +webengine+: Use QtWebEngine (based on Chromium)
|
||||||
* +webkit+: Force QtWebKit
|
* +webkit+: Use QtWebKit (based on WebKit, similar to Safari)
|
||||||
* +webengine+: Force QtWebEngine
|
|
||||||
|
|
||||||
Default: +pass:[auto]+
|
Default: +pass:[webengine]+
|
||||||
|
|
||||||
[[bindings.commands]]
|
[[bindings.commands]]
|
||||||
=== bindings.commands
|
=== bindings.commands
|
||||||
@ -2263,6 +2263,22 @@ Type: <<types,Int>>
|
|||||||
|
|
||||||
Default: +pass:[6]+
|
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: <<types,Bool>>
|
||||||
|
|
||||||
|
Valid values:
|
||||||
|
|
||||||
|
* +true+
|
||||||
|
* +false+
|
||||||
|
|
||||||
|
Default: +pass:[false]+
|
||||||
|
|
||||||
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
[[hints.auto_follow]]
|
[[hints.auto_follow]]
|
||||||
=== hints.auto_follow
|
=== hints.auto_follow
|
||||||
Controls when a hint can be automatically followed without pressing Enter.
|
Controls when a hint can be automatically followed without pressing Enter.
|
||||||
|
@ -17,7 +17,25 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""Initialization of qutebrowser and application-wide things."""
|
"""Initialization of qutebrowser and application-wide things.
|
||||||
|
|
||||||
|
The run() function will get called once early initialization (in
|
||||||
|
qutebrowser.py/earlyinit.py) is done. See the qutebrowser.py docstring for
|
||||||
|
details about early initialization.
|
||||||
|
|
||||||
|
As we need to access the config before the QApplication is created, we
|
||||||
|
initialize everything the config needs before the QApplication is created, and
|
||||||
|
then leave it in a partially initialized state (no saving, no config errors
|
||||||
|
shown yet).
|
||||||
|
|
||||||
|
We then set up the QApplication object and initialize a few more low-level
|
||||||
|
things.
|
||||||
|
|
||||||
|
After that, init() and _init_modules() take over and initialize the rest.
|
||||||
|
|
||||||
|
After all initialization is done, the qt_mainloop() function is called, which
|
||||||
|
blocks and spins the Qt mainloop.
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -44,8 +62,7 @@ import qutebrowser.resources
|
|||||||
from qutebrowser.completion import completiondelegate
|
from qutebrowser.completion import completiondelegate
|
||||||
from qutebrowser.completion.models import miscmodels
|
from qutebrowser.completion.models import miscmodels
|
||||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||||
from qutebrowser.config import (config, websettings, configexc, configfiles,
|
from qutebrowser.config import config, websettings, configfiles, configinit
|
||||||
configinit)
|
|
||||||
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
||||||
downloads)
|
downloads)
|
||||||
from qutebrowser.browser.network import proxy
|
from qutebrowser.browser.network import proxy
|
||||||
@ -54,7 +71,8 @@ from qutebrowser.browser.webkit.network import networkmanager
|
|||||||
from qutebrowser.keyinput import macros
|
from qutebrowser.keyinput import macros
|
||||||
from qutebrowser.mainwindow import mainwindow, prompt
|
from qutebrowser.mainwindow import mainwindow, prompt
|
||||||
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
|
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,
|
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
|
||||||
usertypes, standarddir, error)
|
usertypes, standarddir, error)
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
@ -188,12 +206,6 @@ def _init_icon():
|
|||||||
|
|
||||||
def _process_args(args):
|
def _process_args(args):
|
||||||
"""Open startpage etc. and process commandline args."""
|
"""Open startpage etc. and process commandline args."""
|
||||||
for opt, val in args.temp_settings:
|
|
||||||
try:
|
|
||||||
config.instance.set_str(opt, val)
|
|
||||||
except configexc.Error as e:
|
|
||||||
message.error("set: {} - {}".format(e.__class__.__name__, e))
|
|
||||||
|
|
||||||
if not args.override_restore:
|
if not args.override_restore:
|
||||||
_load_session(args.session)
|
_load_session(args.session)
|
||||||
session_manager = objreg.get('session-manager')
|
session_manager = objreg.get('session-manager')
|
||||||
@ -389,14 +401,17 @@ def _init_modules(args, crash_handler):
|
|||||||
crash_handler: The CrashHandler instance.
|
crash_handler: The CrashHandler instance.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-statements
|
# pylint: disable=too-many-statements
|
||||||
log.init.debug("Initializing prompts...")
|
|
||||||
prompt.init()
|
|
||||||
|
|
||||||
log.init.debug("Initializing save manager...")
|
log.init.debug("Initializing save manager...")
|
||||||
save_manager = savemanager.SaveManager(qApp)
|
save_manager = savemanager.SaveManager(qApp)
|
||||||
objreg.register('save-manager', save_manager)
|
objreg.register('save-manager', save_manager)
|
||||||
configinit.late_init(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...")
|
log.init.debug("Initializing network...")
|
||||||
networkmanager.init()
|
networkmanager.init()
|
||||||
|
|
||||||
@ -509,12 +524,13 @@ class Quitter:
|
|||||||
with tokenize.open(os.path.join(dirpath, fn)) as f:
|
with tokenize.open(os.path.join(dirpath, fn)) as f:
|
||||||
compile(f.read(), fn, 'exec')
|
compile(f.read(), fn, 'exec')
|
||||||
|
|
||||||
def _get_restart_args(self, pages=(), session=None):
|
def _get_restart_args(self, pages=(), session=None, override_args=None):
|
||||||
"""Get the current working directory and args to relaunch qutebrowser.
|
"""Get the current working directory and args to relaunch qutebrowser.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pages: The pages to re-open.
|
pages: The pages to re-open.
|
||||||
session: The session to load, or None.
|
session: The session to load, or None.
|
||||||
|
override_args: Argument overrides as a dict.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
An (args, cwd) tuple.
|
An (args, cwd) tuple.
|
||||||
@ -565,6 +581,9 @@ class Quitter:
|
|||||||
argdict['temp_basedir'] = False
|
argdict['temp_basedir'] = False
|
||||||
argdict['temp_basedir_restarted'] = True
|
argdict['temp_basedir_restarted'] = True
|
||||||
|
|
||||||
|
if override_args is not None:
|
||||||
|
argdict.update(override_args)
|
||||||
|
|
||||||
# Dump the data
|
# Dump the data
|
||||||
data = json.dumps(argdict)
|
data = json.dumps(argdict)
|
||||||
args += ['--json-args', data]
|
args += ['--json-args', data]
|
||||||
@ -589,7 +608,7 @@ class Quitter:
|
|||||||
if ok:
|
if ok:
|
||||||
self.shutdown(restart=True)
|
self.shutdown(restart=True)
|
||||||
|
|
||||||
def restart(self, pages=(), session=None):
|
def restart(self, pages=(), session=None, override_args=None):
|
||||||
"""Inner logic to restart qutebrowser.
|
"""Inner logic to restart qutebrowser.
|
||||||
|
|
||||||
The "better" way to restart is to pass a session (_restart usually) as
|
The "better" way to restart is to pass a session (_restart usually) as
|
||||||
@ -602,6 +621,7 @@ class Quitter:
|
|||||||
Args:
|
Args:
|
||||||
pages: A list of URLs to open.
|
pages: A list of URLs to open.
|
||||||
session: The session to load, or None.
|
session: The session to load, or None.
|
||||||
|
override_args: Argument overrides as a dict.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
True if the restart succeeded, False otherwise.
|
True if the restart succeeded, False otherwise.
|
||||||
@ -611,13 +631,19 @@ class Quitter:
|
|||||||
log.destroy.debug("sys.path: {}".format(sys.path))
|
log.destroy.debug("sys.path: {}".format(sys.path))
|
||||||
log.destroy.debug("sys.argv: {}".format(sys.argv))
|
log.destroy.debug("sys.argv: {}".format(sys.argv))
|
||||||
log.destroy.debug("frozen: {}".format(hasattr(sys, 'frozen')))
|
log.destroy.debug("frozen: {}".format(hasattr(sys, 'frozen')))
|
||||||
|
|
||||||
# Save the session if one is given.
|
# Save the session if one is given.
|
||||||
if session is not None:
|
if session is not None:
|
||||||
session_manager = objreg.get('session-manager')
|
session_manager = objreg.get('session-manager')
|
||||||
session_manager.save(session, with_private=True)
|
session_manager.save(session, with_private=True)
|
||||||
|
|
||||||
|
# Make sure we're not accepting a connection from the new process
|
||||||
|
# before we fully exited.
|
||||||
|
ipc.server.shutdown()
|
||||||
|
|
||||||
# Open a new process and immediately shutdown the existing one
|
# Open a new process and immediately shutdown the existing one
|
||||||
try:
|
try:
|
||||||
args, cwd = self._get_restart_args(pages, session)
|
args, cwd = self._get_restart_args(pages, session, override_args)
|
||||||
if cwd is None:
|
if cwd is None:
|
||||||
subprocess.Popen(args)
|
subprocess.Popen(args)
|
||||||
else:
|
else:
|
||||||
@ -705,7 +731,7 @@ class Quitter:
|
|||||||
QApplication.closeAllWindows()
|
QApplication.closeAllWindows()
|
||||||
# Shut down IPC
|
# Shut down IPC
|
||||||
try:
|
try:
|
||||||
objreg.get('ipc-server').shutdown()
|
ipc.server.shutdown()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
# Save everything
|
# Save everything
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
"""Wrapper over a QWebEngineView."""
|
"""Wrapper over a QWebEngineView."""
|
||||||
|
|
||||||
import os
|
|
||||||
import math
|
import math
|
||||||
import functools
|
import functools
|
||||||
import html as html_utils
|
import html as html_utils
|
||||||
@ -38,7 +37,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
|||||||
webenginesettings)
|
webenginesettings)
|
||||||
from qutebrowser.misc import miscwidgets
|
from qutebrowser.misc import miscwidgets
|
||||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||||
message, objreg, jinja, debug, version)
|
message, objreg, jinja, debug)
|
||||||
|
|
||||||
|
|
||||||
_qute_scheme_handler = None
|
_qute_scheme_handler = None
|
||||||
@ -50,16 +49,8 @@ def init():
|
|||||||
# won't work...
|
# won't work...
|
||||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
|
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
|
||||||
global _qute_scheme_handler
|
global _qute_scheme_handler
|
||||||
|
|
||||||
app = QApplication.instance()
|
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...")
|
log.init.debug("Initializing qute://* handler...")
|
||||||
_qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app)
|
_qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app)
|
||||||
_qute_scheme_handler.install(webenginesettings.default_profile)
|
_qute_scheme_handler.install(webenginesettings.default_profile)
|
||||||
|
@ -101,27 +101,35 @@ qt_args:
|
|||||||
https://peter.sh/experiments/chromium-command-line-switches/ for a list)
|
https://peter.sh/experiments/chromium-command-line-switches/ for a list)
|
||||||
will work.
|
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:
|
backend:
|
||||||
type:
|
type:
|
||||||
name: String
|
name: String
|
||||||
valid_values:
|
valid_values:
|
||||||
- auto: Automatically select either QtWebEngine or QtWebKit
|
- webengine: Use QtWebEngine (based on Chromium)
|
||||||
- webkit: Force QtWebKit
|
- webkit: Use QtWebKit (based on WebKit, similar to Safari)
|
||||||
- webengine: Force QtWebEngine
|
default: webengine
|
||||||
default: auto
|
|
||||||
desc: >-
|
desc: >-
|
||||||
The backend to use to display websites.
|
The backend to use to display websites.
|
||||||
|
|
||||||
qutebrowser supports two different web rendering engines / backends,
|
qutebrowser supports two different web rendering engines / backends,
|
||||||
QtWebKit and QtWebEngine.
|
QtWebKit and QtWebEngine.
|
||||||
|
|
||||||
QtWebKit is based on WebKit (similar to Safari). It was discontinued by the
|
QtWebKit was discontinued by the Qt project with Qt 5.6, but picked up as a
|
||||||
Qt project with Qt 5.6, but picked up as a well maintained fork:
|
well maintained fork: https://github.com/annulen/webkit/wiki - qutebrowser
|
||||||
https://github.com/annulen/webkit/wiki - qutebrowser only supports the fork.
|
only supports the fork.
|
||||||
|
|
||||||
QtWebEngine is Qt's official successor to QtWebKit and based on the Chromium
|
QtWebEngine is Qt's official successor to QtWebKit. It's slightly more
|
||||||
project. It's slightly more resource hungry that QtWebKit and has a couple
|
resource hungry that QtWebKit and has a couple of missing features in
|
||||||
of missing features in qutebrowser, but is generally the preferred choice.
|
qutebrowser, but is generally the preferred choice.
|
||||||
|
|
||||||
## auto_save
|
## auto_save
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ from PyQt5.QtWidgets import QMessageBox
|
|||||||
|
|
||||||
from qutebrowser.config import (config, configdata, configfiles, configtypes,
|
from qutebrowser.config import (config, configdata, configfiles, configtypes,
|
||||||
configexc)
|
configexc)
|
||||||
from qutebrowser.utils import objreg, qtutils, usertypes, log, standarddir
|
from qutebrowser.utils import objreg, usertypes, log, standarddir, message
|
||||||
from qutebrowser.misc import earlyinit, msgbox, objects
|
from qutebrowser.misc import msgbox, objects
|
||||||
|
|
||||||
|
|
||||||
# Error which happened during init, so we can show a message box.
|
# Error which happened during init, so we can show a message box.
|
||||||
@ -69,18 +69,20 @@ def early_init(args):
|
|||||||
configfiles.init()
|
configfiles.init()
|
||||||
|
|
||||||
objects.backend = get_backend(args)
|
objects.backend = get_backend(args)
|
||||||
earlyinit.init_with_backend(objects.backend)
|
|
||||||
|
for opt, val in args.temp_settings:
|
||||||
|
try:
|
||||||
|
config.instance.set_str(opt, val)
|
||||||
|
except configexc.Error as e:
|
||||||
|
message.error("set: {} - {}".format(e.__class__.__name__, e))
|
||||||
|
|
||||||
|
if (objects.backend == usertypes.Backend.QtWebEngine and
|
||||||
|
config.val.force_software_rendering):
|
||||||
|
os.environ['QT_XCB_FORCE_SOFTWARE_OPENGL'] = '1'
|
||||||
|
|
||||||
|
|
||||||
def get_backend(args):
|
def get_backend(args):
|
||||||
"""Find out what backend to use based on available libraries."""
|
"""Find out what backend to use based on available libraries."""
|
||||||
try:
|
|
||||||
import PyQt5.QtWebKit # pylint: disable=unused-variable
|
|
||||||
except ImportError:
|
|
||||||
webkit_available = False
|
|
||||||
else:
|
|
||||||
webkit_available = qtutils.is_new_qtwebkit()
|
|
||||||
|
|
||||||
str_to_backend = {
|
str_to_backend = {
|
||||||
'webkit': usertypes.Backend.QtWebKit,
|
'webkit': usertypes.Backend.QtWebKit,
|
||||||
'webengine': usertypes.Backend.QtWebEngine,
|
'webengine': usertypes.Backend.QtWebEngine,
|
||||||
@ -88,12 +90,8 @@ def get_backend(args):
|
|||||||
|
|
||||||
if args.backend is not None:
|
if args.backend is not None:
|
||||||
return str_to_backend[args.backend]
|
return str_to_backend[args.backend]
|
||||||
elif config.val.backend != 'auto':
|
|
||||||
return str_to_backend[config.val.backend]
|
|
||||||
elif webkit_available:
|
|
||||||
return usertypes.Backend.QtWebKit
|
|
||||||
else:
|
else:
|
||||||
return usertypes.Backend.QtWebEngine
|
return str_to_backend[config.val.backend]
|
||||||
|
|
||||||
|
|
||||||
def late_init(save_manager):
|
def late_init(save_manager):
|
||||||
|
318
qutebrowser/misc/backendproblem.py
Normal file
318
qutebrowser/misc/backendproblem.py
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
"""Dialogs shown when there was a problem with a backend choice."""
|
||||||
|
|
||||||
|
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, QMessageBox)
|
||||||
|
from PyQt5.QtNetwork import QSslSocket
|
||||||
|
|
||||||
|
from qutebrowser.config import config
|
||||||
|
from qutebrowser.utils import usertypes, objreg, version, qtutils, log
|
||||||
|
from qutebrowser.misc import objects, msgbox
|
||||||
|
|
||||||
|
|
||||||
|
_Result = usertypes.enum(
|
||||||
|
'_Result',
|
||||||
|
['quit', 'restart', 'restart_webkit', 'restart_webengine'],
|
||||||
|
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(
|
||||||
|
"<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</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).</p>".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)
|
||||||
|
save_manager = objreg.get('save-manager')
|
||||||
|
save_manager.save_all(is_exit=True)
|
||||||
|
|
||||||
|
if setting == 'backend' and value == 'webkit':
|
||||||
|
self.done(_Result.restart_webkit)
|
||||||
|
elif setting == 'backend' and value == 'webengine':
|
||||||
|
self.done(_Result.restart_webengine)
|
||||||
|
else:
|
||||||
|
self.done(_Result.restart)
|
||||||
|
|
||||||
|
|
||||||
|
def _show_dialog(*args, **kwargs):
|
||||||
|
"""Show a dialog for a backend problem."""
|
||||||
|
dialog = _Dialog(*args, **kwargs)
|
||||||
|
|
||||||
|
status = dialog.exec_()
|
||||||
|
quitter = objreg.get('quitter')
|
||||||
|
|
||||||
|
if status in [_Result.quit, QDialog.Rejected]:
|
||||||
|
pass
|
||||||
|
elif status == _Result.restart_webkit:
|
||||||
|
quitter.restart(override_args={'backend': 'webkit'})
|
||||||
|
elif status == _Result.restart_webengine:
|
||||||
|
quitter.restart(override_args={'backend': 'webengine'})
|
||||||
|
elif status == _Result.restart:
|
||||||
|
quitter.restart()
|
||||||
|
else:
|
||||||
|
assert False, status
|
||||||
|
|
||||||
|
sys.exit(usertypes.Exit.err_init)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_nouveau_graphics():
|
||||||
|
assert objects.backend == usertypes.Backend.QtWebEngine, objects.backend
|
||||||
|
|
||||||
|
if version.opengl_vendor() != 'nouveau':
|
||||||
|
return
|
||||||
|
|
||||||
|
if (os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or
|
||||||
|
'QT_XCB_FORCE_SOFTWARE_OPENGL' in os.environ):
|
||||||
|
return
|
||||||
|
|
||||||
|
button = _Button("Force software rendering", 'force_software_rendering',
|
||||||
|
True)
|
||||||
|
_show_dialog(
|
||||||
|
backend=usertypes.Backend.QtWebEngine,
|
||||||
|
because="you're using Nouveau graphics",
|
||||||
|
text="<p>There are two ways to fix this:</p>"
|
||||||
|
"<p><b>Forcing software rendering</b></p>"
|
||||||
|
"<p>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 <i>force_software_rendering = True</i> option "
|
||||||
|
"(if you have a <i>config.py</i> file, you'll need to set this "
|
||||||
|
"manually).</p>",
|
||||||
|
buttons=[button],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should never be reached
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_wayland():
|
||||||
|
assert objects.backend == usertypes.Backend.QtWebEngine, objects.backend
|
||||||
|
|
||||||
|
platform = QApplication.instance().platformName()
|
||||||
|
if platform not in ['wayland', 'wayland-egl']:
|
||||||
|
return
|
||||||
|
|
||||||
|
_show_dialog(
|
||||||
|
backend=usertypes.Backend.QtWebEngine,
|
||||||
|
because="you're using Wayland",
|
||||||
|
text="<p>There are two ways to fix this:</p>"
|
||||||
|
"<p><b>Set up XWayland</b></p>"
|
||||||
|
"<p>This allows you to use the newer QtWebEngine backend (based "
|
||||||
|
"on Chromium). "
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should never be reached
|
||||||
|
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."""
|
||||||
|
# pylint: disable=unused-variable
|
||||||
|
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 _handle_ssl_support(fatal=False):
|
||||||
|
"""Check for full SSL availability.
|
||||||
|
|
||||||
|
If "fatal" is given, show an error and exit.
|
||||||
|
"""
|
||||||
|
text = ("Could not initialize QtNetwork SSL support. 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. This only affects downloads.")
|
||||||
|
|
||||||
|
if QSslSocket.supportsSsl():
|
||||||
|
return
|
||||||
|
|
||||||
|
if fatal:
|
||||||
|
errbox = msgbox.msgbox(parent=None,
|
||||||
|
title="SSL error",
|
||||||
|
text="Could not initialize SSL support.",
|
||||||
|
icon=QMessageBox.Critical,
|
||||||
|
plain_text=False)
|
||||||
|
errbox.exec_()
|
||||||
|
sys.exit(usertypes.Exit.err_init)
|
||||||
|
|
||||||
|
assert not fatal
|
||||||
|
log.init.warning(text)
|
||||||
|
|
||||||
|
|
||||||
|
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 = ("<p>qutebrowser needs QtWebKit or QtWebEngine, but neither "
|
||||||
|
"could be imported!</p>"
|
||||||
|
"<p>The errors encountered were:<ul>"
|
||||||
|
"<li><b>QtWebKit:</b> {webkit_error}"
|
||||||
|
"<li><b>QtWebEngine:</b> {webengine_error}"
|
||||||
|
"</ul></p>".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="<p><b>The error encountered was:</b><br/>{}</p>".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="<p><b>The error encountered was:</b><br/>{}</p>".format(
|
||||||
|
html.escape(imports.webengine_error))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should never be reached
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
_check_backend_modules()
|
||||||
|
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||||
|
_handle_ssl_support()
|
||||||
|
_handle_wayland()
|
||||||
|
_handle_nouveau_graphics()
|
||||||
|
else:
|
||||||
|
assert objects.backend == usertypes.Backend.QtWebKit, objects.backend
|
||||||
|
_handle_ssl_support(fatal=True)
|
@ -36,10 +36,10 @@ except ImportError:
|
|||||||
import attr
|
import attr
|
||||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QObject,
|
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QObject,
|
||||||
QSocketNotifier, QTimer, QUrl)
|
QSocketNotifier, QTimer, QUrl)
|
||||||
from PyQt5.QtWidgets import QApplication, QDialog
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from qutebrowser.commands import cmdutils
|
from qutebrowser.commands import cmdutils
|
||||||
from qutebrowser.misc import earlyinit, crashdialog
|
from qutebrowser.misc import earlyinit, crashdialog, ipc
|
||||||
from qutebrowser.utils import usertypes, standarddir, log, objreg, debug, utils
|
from qutebrowser.utils import usertypes, standarddir, log, objreg, debug, utils
|
||||||
|
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ class CrashHandler(QObject):
|
|||||||
info = self._get_exception_info()
|
info = self._get_exception_info()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
objreg.get('ipc-server').ignored = True
|
ipc.server.ignored = True
|
||||||
except Exception:
|
except Exception:
|
||||||
log.destroy.exception("Error while ignoring ipc")
|
log.destroy.exception("Error while ignoring ipc")
|
||||||
|
|
||||||
|
@ -187,23 +187,6 @@ def check_ssl_support():
|
|||||||
_die("Fatal error: Your Qt is built without SSL support.")
|
_die("Fatal error: Your Qt is built without SSL support.")
|
||||||
|
|
||||||
|
|
||||||
def check_backend_ssl_support(backend):
|
|
||||||
"""Check for full SSL availability when we know the backend."""
|
|
||||||
from PyQt5.QtNetwork import QSslSocket
|
|
||||||
from qutebrowser.utils import log, usertypes
|
|
||||||
text = ("Could not initialize QtNetwork SSL support. 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. This only affects downloads.")
|
|
||||||
|
|
||||||
if not QSslSocket.supportsSsl():
|
|
||||||
if backend == usertypes.Backend.QtWebKit:
|
|
||||||
_die("Could not initialize SSL support.")
|
|
||||||
else:
|
|
||||||
assert backend == usertypes.Backend.QtWebEngine
|
|
||||||
log.init.warning(text)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_modules(modules):
|
def _check_modules(modules):
|
||||||
"""Make sure the given modules are available."""
|
"""Make sure the given modules are available."""
|
||||||
from qutebrowser.utils import log
|
from qutebrowser.utils import log
|
||||||
@ -247,35 +230,6 @@ def check_libraries():
|
|||||||
_check_modules(modules)
|
_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():
|
def remove_inputhook():
|
||||||
"""Remove the PyQt input hook.
|
"""Remove the PyQt input hook.
|
||||||
|
|
||||||
@ -328,16 +282,3 @@ def early_init(args):
|
|||||||
remove_inputhook()
|
remove_inputhook()
|
||||||
check_ssl_support()
|
check_ssl_support()
|
||||||
check_optimize_flag()
|
check_optimize_flag()
|
||||||
|
|
||||||
|
|
||||||
def init_with_backend(backend):
|
|
||||||
"""Do later stages of init when we know the backend.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
backend: The backend as usertypes.Backend member.
|
|
||||||
"""
|
|
||||||
assert not isinstance(backend, str), backend
|
|
||||||
assert backend is not None
|
|
||||||
check_backend_libraries(backend)
|
|
||||||
check_backend_ssl_support(backend)
|
|
||||||
check_new_webkit(backend)
|
|
||||||
|
@ -30,7 +30,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt
|
|||||||
from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QAbstractSocket
|
from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QAbstractSocket
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.utils import log, usertypes, error, objreg, standarddir, utils
|
from qutebrowser.utils import log, usertypes, error, standarddir, utils
|
||||||
|
|
||||||
|
|
||||||
CONNECT_TIMEOUT = 100 # timeout for connecting/disconnecting
|
CONNECT_TIMEOUT = 100 # timeout for connecting/disconnecting
|
||||||
@ -40,6 +40,10 @@ ATIME_INTERVAL = 60 * 60 * 3 * 1000 # 3 hours
|
|||||||
PROTOCOL_VERSION = 1
|
PROTOCOL_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
|
# The ipc server instance
|
||||||
|
server = None
|
||||||
|
|
||||||
|
|
||||||
def _get_socketname_windows(basedir):
|
def _get_socketname_windows(basedir):
|
||||||
"""Get a socketname to use for Windows."""
|
"""Get a socketname to use for Windows."""
|
||||||
parts = ['qutebrowser', getpass.getuser()]
|
parts = ['qutebrowser', getpass.getuser()]
|
||||||
@ -109,15 +113,15 @@ class ListenError(Error):
|
|||||||
message: The error message.
|
message: The error message.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server):
|
def __init__(self, local_server):
|
||||||
"""Constructor.
|
"""Constructor.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
server: The QLocalServer which has the error set.
|
local_server: The QLocalServer which has the error set.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.code = server.serverError()
|
self.code = local_server.serverError()
|
||||||
self.message = server.errorString()
|
self.message = local_server.errorString()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Error while listening to IPC server: {} (error {})".format(
|
return "Error while listening to IPC server: {} (error {})".format(
|
||||||
@ -482,6 +486,7 @@ def send_or_listen(args):
|
|||||||
The IPCServer instance if no running instance was detected.
|
The IPCServer instance if no running instance was detected.
|
||||||
None if an instance was running and received our request.
|
None if an instance was running and received our request.
|
||||||
"""
|
"""
|
||||||
|
global server
|
||||||
socketname = _get_socketname(args.basedir)
|
socketname = _get_socketname(args.basedir)
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@ -492,7 +497,6 @@ def send_or_listen(args):
|
|||||||
log.init.debug("Starting IPC server...")
|
log.init.debug("Starting IPC server...")
|
||||||
server = IPCServer(socketname)
|
server = IPCServer(socketname)
|
||||||
server.listen()
|
server.listen()
|
||||||
objreg.register('ipc-server', server)
|
|
||||||
return server
|
return server
|
||||||
except AddressInUseError as e:
|
except AddressInUseError as e:
|
||||||
# This could be a race condition...
|
# This could be a race condition...
|
||||||
|
@ -164,6 +164,11 @@ class SaveManager(QObject):
|
|||||||
self.saveables[name].save(is_exit=is_exit, explicit=explicit,
|
self.saveables[name].save(is_exit=is_exit, explicit=explicit,
|
||||||
silent=silent, force=force)
|
silent=silent, force=force)
|
||||||
|
|
||||||
|
def save_all(self, *args, **kwargs):
|
||||||
|
"""Save all saveables."""
|
||||||
|
for saveable in self.saveables:
|
||||||
|
self.save(saveable, *args, **kwargs)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def autosave(self):
|
def autosave(self):
|
||||||
"""Slot used when the configs are auto-saved."""
|
"""Slot used when the configs are auto-saved."""
|
||||||
|
@ -171,12 +171,15 @@ def debug_cache_stats():
|
|||||||
prefix_info = configdata.is_valid_prefix.cache_info()
|
prefix_info = configdata.is_valid_prefix.cache_info()
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
render_stylesheet_info = config._render_stylesheet.cache_info()
|
render_stylesheet_info = config._render_stylesheet.cache_info()
|
||||||
|
|
||||||
|
history_info = None
|
||||||
try:
|
try:
|
||||||
from PyQt5.QtWebKit import QWebHistoryInterface
|
from PyQt5.QtWebKit import QWebHistoryInterface
|
||||||
interface = QWebHistoryInterface.defaultInterface()
|
interface = QWebHistoryInterface.defaultInterface()
|
||||||
history_info = interface.historyContains.cache_info()
|
if interface is not None:
|
||||||
|
history_info = interface.historyContains.cache_info()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
history_info = None
|
pass
|
||||||
|
|
||||||
log.misc.debug('is_valid_prefix: {}'.format(prefix_info))
|
log.misc.debug('is_valid_prefix: {}'.format(prefix_info))
|
||||||
log.misc.debug('_render_stylesheet: {}'.format(render_stylesheet_info))
|
log.misc.debug('_render_stylesheet: {}'.format(render_stylesheet_info))
|
||||||
|
@ -17,7 +17,21 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""Early initialization and main entry point."""
|
"""Early initialization and main entry point.
|
||||||
|
|
||||||
|
qutebrowser's initialization process roughly looks like this:
|
||||||
|
|
||||||
|
- This file gets imported, either via the setuptools entry point or
|
||||||
|
__main__.py.
|
||||||
|
- At import time, we check for the correct Python version and show an error if
|
||||||
|
it's too old.
|
||||||
|
- The main() function in this file gets invoked
|
||||||
|
- Argument parsing takes place
|
||||||
|
- earlyinit.early_init() gets invoked to do various low-level initialization
|
||||||
|
and checks whether all dependencies are met.
|
||||||
|
- app.run() gets called, which takes over.
|
||||||
|
See the docstring of app.py for details.
|
||||||
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
@ -319,3 +319,18 @@ def test_qute_settings_persistence(short_tmpdir, request, quteproc_new):
|
|||||||
|
|
||||||
quteproc_new.start(args)
|
quteproc_new.start(args)
|
||||||
assert quteproc_new.get_setting('ignore_case') == 'always'
|
assert quteproc_new.get_setting('ignore_case') == 'always'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.no_xvfb
|
||||||
|
@pytest.mark.no_ci
|
||||||
|
def test_force_software_rendering(request, quteproc_new):
|
||||||
|
"""Make sure we can force software rendering with -s."""
|
||||||
|
if not request.config.webengine:
|
||||||
|
pytest.skip("Only runs with QtWebEngine")
|
||||||
|
|
||||||
|
args = (_base_args(request.config) +
|
||||||
|
['--temp-basedir', '-s', 'force_software_rendering', 'true'])
|
||||||
|
quteproc_new.start(args)
|
||||||
|
quteproc_new.open_path('chrome://gpu')
|
||||||
|
message = 'Canvas: Software only, hardware acceleration unavailable'
|
||||||
|
assert message in quteproc_new.get_content()
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
"""Tests for qutebrowser.config.configinit."""
|
"""Tests for qutebrowser.config.configinit."""
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import unittest.mock
|
import unittest.mock
|
||||||
@ -25,23 +26,18 @@ import unittest.mock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from qutebrowser import qutebrowser
|
from qutebrowser import qutebrowser
|
||||||
from qutebrowser.config import (config, configdata, configexc, configfiles,
|
from qutebrowser.config import config, configexc, configfiles, configinit
|
||||||
configinit)
|
|
||||||
from qutebrowser.utils import objreg, usertypes
|
from qutebrowser.utils import objreg, usertypes
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir,
|
def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir,
|
||||||
data_tmpdir):
|
data_tmpdir):
|
||||||
monkeypatch.setattr(configdata, 'DATA', None)
|
|
||||||
monkeypatch.setattr(configfiles, 'state', None)
|
monkeypatch.setattr(configfiles, 'state', None)
|
||||||
monkeypatch.setattr(config, 'instance', None)
|
monkeypatch.setattr(config, 'instance', None)
|
||||||
monkeypatch.setattr(config, 'key_instance', None)
|
monkeypatch.setattr(config, 'key_instance', None)
|
||||||
monkeypatch.setattr(config, 'change_filters', [])
|
monkeypatch.setattr(config, 'change_filters', [])
|
||||||
monkeypatch.setattr(configinit, '_init_errors', None)
|
monkeypatch.setattr(configinit, '_init_errors', None)
|
||||||
# Make sure we get no SSL warning
|
|
||||||
monkeypatch.setattr(configinit.earlyinit, 'check_backend_ssl_support',
|
|
||||||
lambda _backend: None)
|
|
||||||
yield
|
yield
|
||||||
try:
|
try:
|
||||||
objreg.delete('config-commands')
|
objreg.delete('config-commands')
|
||||||
@ -49,10 +45,17 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir,
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def args(fake_args):
|
||||||
|
"""Arguments needed for the config to init."""
|
||||||
|
fake_args.temp_settings = []
|
||||||
|
return fake_args
|
||||||
|
|
||||||
|
|
||||||
class TestEarlyInit:
|
class TestEarlyInit:
|
||||||
|
|
||||||
@pytest.mark.parametrize('config_py', [True, 'error', False])
|
@pytest.mark.parametrize('config_py', [True, 'error', False])
|
||||||
def test_config_py(self, init_patch, config_tmpdir, caplog, fake_args,
|
def test_config_py(self, init_patch, config_tmpdir, caplog, args,
|
||||||
config_py):
|
config_py):
|
||||||
"""Test loading with only a config.py."""
|
"""Test loading with only a config.py."""
|
||||||
config_py_file = config_tmpdir / 'config.py'
|
config_py_file = config_tmpdir / 'config.py'
|
||||||
@ -65,7 +68,7 @@ class TestEarlyInit:
|
|||||||
'utf-8', ensure=True)
|
'utf-8', ensure=True)
|
||||||
|
|
||||||
with caplog.at_level(logging.ERROR):
|
with caplog.at_level(logging.ERROR):
|
||||||
configinit.early_init(fake_args)
|
configinit.early_init(args)
|
||||||
|
|
||||||
# Check error messages
|
# Check error messages
|
||||||
expected_errors = []
|
expected_errors = []
|
||||||
@ -95,7 +98,7 @@ class TestEarlyInit:
|
|||||||
@pytest.mark.parametrize('config_py', [True, 'error', False])
|
@pytest.mark.parametrize('config_py', [True, 'error', False])
|
||||||
@pytest.mark.parametrize('invalid_yaml', ['42', 'unknown', 'wrong-type',
|
@pytest.mark.parametrize('invalid_yaml', ['42', 'unknown', 'wrong-type',
|
||||||
False])
|
False])
|
||||||
def test_autoconfig_yml(self, init_patch, config_tmpdir, caplog, fake_args,
|
def test_autoconfig_yml(self, init_patch, config_tmpdir, caplog, args,
|
||||||
load_autoconfig, config_py, invalid_yaml):
|
load_autoconfig, config_py, invalid_yaml):
|
||||||
"""Test interaction between config.py and autoconfig.yml."""
|
"""Test interaction between config.py and autoconfig.yml."""
|
||||||
# pylint: disable=too-many-locals,too-many-branches
|
# pylint: disable=too-many-locals,too-many-branches
|
||||||
@ -122,7 +125,7 @@ class TestEarlyInit:
|
|||||||
'utf-8', ensure=True)
|
'utf-8', ensure=True)
|
||||||
|
|
||||||
with caplog.at_level(logging.ERROR):
|
with caplog.at_level(logging.ERROR):
|
||||||
configinit.early_init(fake_args)
|
configinit.early_init(args)
|
||||||
|
|
||||||
# Check error messages
|
# Check error messages
|
||||||
expected_errors = []
|
expected_errors = []
|
||||||
@ -161,16 +164,47 @@ class TestEarlyInit:
|
|||||||
else:
|
else:
|
||||||
assert config.instance._values == {'colors.hints.fg': 'magenta'}
|
assert config.instance._values == {'colors.hints.fg': 'magenta'}
|
||||||
|
|
||||||
def test_invalid_change_filter(self, init_patch, fake_args):
|
def test_invalid_change_filter(self, init_patch, args):
|
||||||
config.change_filter('foobar')
|
config.change_filter('foobar')
|
||||||
with pytest.raises(configexc.NoOptionError):
|
with pytest.raises(configexc.NoOptionError):
|
||||||
configinit.early_init(fake_args)
|
configinit.early_init(args)
|
||||||
|
|
||||||
|
def test_temp_settings_valid(self, init_patch, args):
|
||||||
|
args.temp_settings = [('colors.completion.fg', 'magenta')]
|
||||||
|
configinit.early_init(args)
|
||||||
|
assert config.instance._values['colors.completion.fg'] == 'magenta'
|
||||||
|
|
||||||
|
def test_temp_settings_invalid(self, caplog, init_patch, message_mock,
|
||||||
|
args):
|
||||||
|
"""Invalid temp settings should show an error."""
|
||||||
|
args.temp_settings = [('foo', 'bar')]
|
||||||
|
|
||||||
|
with caplog.at_level(logging.ERROR):
|
||||||
|
configinit.early_init(args)
|
||||||
|
|
||||||
|
msg = message_mock.getmsg()
|
||||||
|
assert msg.level == usertypes.MessageLevel.error
|
||||||
|
assert msg.text == "set: NoOptionError - No option 'foo'"
|
||||||
|
assert 'colors.completion.fg' not in config.instance._values
|
||||||
|
|
||||||
|
def test_force_software_rendering(self, monkeypatch, init_patch, args):
|
||||||
|
"""Setting force_software_rendering should set the environment var."""
|
||||||
|
envvar = 'QT_XCB_FORCE_SOFTWARE_OPENGL'
|
||||||
|
monkeypatch.setattr(configinit.objects, 'backend',
|
||||||
|
usertypes.Backend.QtWebEngine)
|
||||||
|
monkeypatch.delenv(envvar, raising=False)
|
||||||
|
args.temp_settings = [('force_software_rendering', 'true')]
|
||||||
|
args.backend = 'webengine'
|
||||||
|
|
||||||
|
configinit.early_init(args)
|
||||||
|
|
||||||
|
assert os.environ[envvar] == '1'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('errors', [True, False])
|
@pytest.mark.parametrize('errors', [True, False])
|
||||||
def test_late_init(init_patch, monkeypatch, fake_save_manager, fake_args,
|
def test_late_init(init_patch, monkeypatch, fake_save_manager, args,
|
||||||
mocker, errors):
|
mocker, errors):
|
||||||
configinit.early_init(fake_args)
|
configinit.early_init(args)
|
||||||
if errors:
|
if errors:
|
||||||
err = configexc.ConfigErrorDesc("Error text", Exception("Exception"))
|
err = configexc.ConfigErrorDesc("Error text", Exception("Exception"))
|
||||||
errs = configexc.ConfigFileErrors("config.py", [err])
|
errs = configexc.ConfigFileErrors("config.py", [err])
|
||||||
@ -242,33 +276,23 @@ class TestQtArgs:
|
|||||||
assert configinit.qt_args(parsed) == [sys.argv[0], '--foo', '--bar']
|
assert configinit.qt_args(parsed) == [sys.argv[0], '--foo', '--bar']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('arg, confval, can_import, is_new_webkit, used', [
|
@pytest.mark.parametrize('arg, confval, used', [
|
||||||
# overridden by commandline arg
|
# overridden by commandline arg
|
||||||
('webkit', 'auto', False, False, usertypes.Backend.QtWebKit),
|
('webkit', 'webengine', usertypes.Backend.QtWebKit),
|
||||||
# overridden by config
|
# set in config
|
||||||
(None, 'webkit', False, False, usertypes.Backend.QtWebKit),
|
(None, 'webkit', usertypes.Backend.QtWebKit),
|
||||||
# WebKit available but too old
|
|
||||||
(None, 'auto', True, False, usertypes.Backend.QtWebEngine),
|
|
||||||
# WebKit available and new
|
|
||||||
(None, 'auto', True, True, usertypes.Backend.QtWebKit),
|
|
||||||
# WebKit unavailable
|
|
||||||
(None, 'auto', False, False, usertypes.Backend.QtWebEngine),
|
|
||||||
])
|
])
|
||||||
def test_get_backend(monkeypatch, fake_args, config_stub,
|
def test_get_backend(monkeypatch, args, config_stub,
|
||||||
arg, confval, can_import, is_new_webkit, used):
|
arg, confval, used):
|
||||||
real_import = __import__
|
real_import = __import__
|
||||||
|
|
||||||
def fake_import(name, *args, **kwargs):
|
def fake_import(name, *args, **kwargs):
|
||||||
if name != 'PyQt5.QtWebKit':
|
if name != 'PyQt5.QtWebKit':
|
||||||
return real_import(name, *args, **kwargs)
|
return real_import(name, *args, **kwargs)
|
||||||
if can_import:
|
|
||||||
return None
|
|
||||||
raise ImportError
|
raise ImportError
|
||||||
|
|
||||||
fake_args.backend = arg
|
args.backend = arg
|
||||||
config_stub.val.backend = confval
|
config_stub.val.backend = confval
|
||||||
monkeypatch.setattr(configinit.qtutils, 'is_new_qtwebkit',
|
|
||||||
lambda: is_new_webkit)
|
|
||||||
monkeypatch.setattr('builtins.__import__', fake_import)
|
monkeypatch.setattr('builtins.__import__', fake_import)
|
||||||
|
|
||||||
assert configinit.get_backend(fake_args) == used
|
assert configinit.get_backend(args) == used
|
||||||
|
@ -34,7 +34,7 @@ from PyQt5.QtTest import QSignalSpy
|
|||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.misc import ipc
|
from qutebrowser.misc import ipc
|
||||||
from qutebrowser.utils import objreg, standarddir, utils
|
from qutebrowser.utils import standarddir, utils
|
||||||
from helpers import stubs
|
from helpers import stubs
|
||||||
|
|
||||||
|
|
||||||
@ -45,12 +45,8 @@ pytestmark = pytest.mark.usefixtures('qapp')
|
|||||||
def shutdown_server():
|
def shutdown_server():
|
||||||
"""If ipc.send_or_listen was called, make sure to shut server down."""
|
"""If ipc.send_or_listen was called, make sure to shut server down."""
|
||||||
yield
|
yield
|
||||||
try:
|
if ipc.server is not None:
|
||||||
server = objreg.get('ipc-server')
|
ipc.server.shutdown()
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
server.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -609,13 +605,6 @@ class TestSendOrListen:
|
|||||||
return self.Args(no_err_windows=True, basedir='/basedir/for/testing',
|
return self.Args(no_err_windows=True, basedir='/basedir/for/testing',
|
||||||
command=['test'], target=None)
|
command=['test'], target=None)
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def cleanup(self):
|
|
||||||
try:
|
|
||||||
objreg.delete('ipc-server')
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def qlocalserver_mock(self, mocker):
|
def qlocalserver_mock(self, mocker):
|
||||||
m = mocker.patch('qutebrowser.misc.ipc.QLocalServer', autospec=True)
|
m = mocker.patch('qutebrowser.misc.ipc.QLocalServer', autospec=True)
|
||||||
@ -639,8 +628,7 @@ class TestSendOrListen:
|
|||||||
assert isinstance(ret_server, ipc.IPCServer)
|
assert isinstance(ret_server, ipc.IPCServer)
|
||||||
msgs = [e.message for e in caplog.records]
|
msgs = [e.message for e in caplog.records]
|
||||||
assert "Starting IPC server..." in msgs
|
assert "Starting IPC server..." in msgs
|
||||||
objreg_server = objreg.get('ipc-server')
|
assert ret_server is ipc.server
|
||||||
assert objreg_server is ret_server
|
|
||||||
|
|
||||||
with qtbot.waitSignal(ret_server.got_args):
|
with qtbot.waitSignal(ret_server.got_args):
|
||||||
ret_client = ipc.send_or_listen(args)
|
ret_client = ipc.send_or_listen(args)
|
||||||
|
Loading…
Reference in New Issue
Block a user