summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <me@the-compiler.org>2023-06-12 20:43:45 +0200
committerFlorian Bruhin <me@the-compiler.org>2023-06-12 21:42:16 +0200
commitf65a6923200cc7b738394fb8bacbbc5e356b4969 (patch)
tree62c56b8f26da214f07c645b082e31e8eefd24355
parent70e8dc63e87a57ecac2b626d496a0bc9517a632d (diff)
downloadqutebrowser-f65a6923200cc7b738394fb8bacbbc5e356b4969.tar.gz
qutebrowser-f65a6923200cc7b738394fb8bacbbc5e356b4969.zip
qt: Initial support for a --qt-wrapper argument
-rw-r--r--qutebrowser/qt/machinery.py140
-rw-r--r--qutebrowser/qutebrowser.py7
2 files changed, 110 insertions, 37 deletions
diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py
index 3ddafad65..6699d5e73 100644
--- a/qutebrowser/qt/machinery.py
+++ b/qutebrowser/qt/machinery.py
@@ -2,9 +2,13 @@
# FIXME:qt6 (lint)
# pylint: disable=missing-module-docstring
# flake8: noqa
+# pyright: reportConstantRedefinition=false
import os
+import sys
+import argparse
import importlib
+from typing import Union
# Packagers: Patch the line below to change the default wrapper for Qt 6 packages, e.g.:
# sed -i 's/_DEFAULT_WRAPPER = "PyQt5"/_DEFAULT_WRAPPER = "PyQt6"/' qutebrowser/qt/machinery.py
@@ -12,7 +16,7 @@ import importlib
# Users: Set the QUTE_QT_WRAPPER environment variable to change the default wrapper.
_DEFAULT_WRAPPER = "PyQt5"
-_WRAPPERS = [
+WRAPPERS = [
"PyQt6",
"PyQt5",
# Needs more work
@@ -36,8 +40,13 @@ class UnknownWrapper(Error):
pass
-def _autoselect_wrapper():
- for wrapper in _WRAPPERS:
+def _autoselect_wrapper() -> str:
+ """Autoselect a Qt wrapper.
+
+ This goes through all wrappers defined in WRAPPER.
+ The first one which can be imported is returned.
+ """
+ for wrapper in WRAPPERS:
try:
importlib.import_module(wrapper)
except ImportError:
@@ -45,42 +54,99 @@ def _autoselect_wrapper():
continue
return wrapper
- wrappers = ", ".join(_WRAPPERS)
+ wrappers = ", ".join(WRAPPERS)
raise Error(f"No Qt wrapper found, tried {wrappers}")
-def _select_wrapper():
+def _select_wrapper(args: Union[argparse.Namespace, None]) -> str:
+ """Select a Qt wrapper.
+
+ - If --qt-wrapper is given, use that.
+ - Otherwise, if the QUTE_QT_WRAPPER environment variable is set, use that.
+ - Otherwise, use PyQt5 (FIXME:qt6 autoselect).
+ """
+ if args is not None and args.qt_wrapper is not None:
+ assert args.qt_wrapper in WRAPPERS, args.qt_wrapper # ensured by argparse
+ return args.qt_wrapper
+
env_var = "QUTE_QT_WRAPPER"
env_wrapper = os.environ.get(env_var)
- if env_wrapper is None:
- # FIXME:qt6 Go back to the auto-detection once ready
- # return _autoselect_wrapper()
- return _DEFAULT_WRAPPER
-
- if env_wrapper not in _WRAPPERS:
- raise Error(f"Unknown wrapper {env_wrapper} set via {env_var}, "
- f"allowed: {', '.join(_WRAPPERS)}")
-
- return env_wrapper
-
-
-WRAPPER = _select_wrapper()
-USE_PYQT5 = WRAPPER == "PyQt5"
-USE_PYQT6 = WRAPPER == "PyQt6"
-USE_PYSIDE6 = WRAPPER == "PySide6"
-assert USE_PYQT5 ^ USE_PYQT6 ^ USE_PYSIDE6
-
-IS_QT5 = USE_PYQT5
-IS_QT6 = USE_PYQT6 or USE_PYSIDE6
-IS_PYQT = USE_PYQT5 or USE_PYQT6
-IS_PYSIDE = USE_PYSIDE6
-assert IS_QT5 ^ IS_QT6
-assert IS_PYQT ^ IS_PYSIDE
-
-
-if USE_PYQT5:
- PACKAGE = "PyQt5"
-elif USE_PYQT6:
- PACKAGE = "PyQt6"
-elif USE_PYSIDE6:
- PACKAGE = "PySide6"
+ if env_wrapper is not None:
+ if env_wrapper not in WRAPPERS:
+ raise Error(f"Unknown wrapper {env_wrapper} set via {env_var}, "
+ f"allowed: {', '.join(WRAPPERS)}")
+ return env_wrapper
+
+ # FIXME:qt6 Go back to the auto-detection once ready
+ # return _autoselect_wrapper()
+ return _DEFAULT_WRAPPER
+
+
+# Values are set in init(). If you see a NameError here, it means something tried to
+# import Qt (or check for its availability) before machinery.init() was called.
+WRAPPER: str
+USE_PYQT5: bool
+USE_PYQT6: bool
+USE_PYSIDE6: bool
+IS_QT5: bool
+IS_QT6: bool
+IS_PYQT: bool
+IS_PYSIDE: bool
+PACKAGE: str
+
+_initialized = False
+
+
+def init(args: Union[argparse.Namespace, None] = None) -> None:
+ """Initialize Qt wrapper globals.
+
+ There is two ways how this function can be called:
+
+ - Explicitly, during qutebrowser startup, where it gets called before
+ earlyinit.early_init() in qutebrowser.py (i.e. after we have an argument
+ parser, but before any kinds of Qt usage). This allows `args` to be passed,
+ which is used to select the Qt wrapper (if --qt-wrapper is given).
+
+ - Implicitly, when any of the qutebrowser.qt.* modules in this package is imported.
+ This should never happen during normal qutebrowser usage, but means that any
+ qutebrowser module can be imported without having to worry about machinery.init().
+ This is useful for e.g. tests or manual interactive usage of the qutebrowser code.
+ In this case, `args` will be None.
+ """
+ global WRAPPER, USE_PYQT5, USE_PYQT6, USE_PYSIDE6, IS_QT5, IS_QT6, \
+ IS_PYQT, IS_PYSIDE, PACKAGE, _initialized
+
+ if args is None:
+ # Implicit initialization can happen multiple times
+ # (all subsequent calls are a no-op)
+ if _initialized:
+ return
+ else:
+ # Explicit initialization can happen exactly once, and if it's used, there
+ # should not be any implicit initialization (qutebrowser.qt imports) before it.
+ assert not _initialized, "init() already called before application init"
+ _initialized = True
+
+ for name in WRAPPERS:
+ # If any Qt wrapper has been imported before this, all hope is lost.
+ assert name not in sys.modules, f"{name} already imported"
+
+ WRAPPER = _select_wrapper(args)
+ USE_PYQT5 = WRAPPER == "PyQt5"
+ USE_PYQT6 = WRAPPER == "PyQt6"
+ USE_PYSIDE6 = WRAPPER == "PySide6"
+ assert USE_PYQT5 ^ USE_PYQT6 ^ USE_PYSIDE6
+
+ IS_QT5 = USE_PYQT5
+ IS_QT6 = USE_PYQT6 or USE_PYSIDE6
+ IS_PYQT = USE_PYQT5 or USE_PYQT6
+ IS_PYSIDE = USE_PYSIDE6
+ assert IS_QT5 ^ IS_QT6
+ assert IS_PYQT ^ IS_PYSIDE
+
+ if USE_PYQT5:
+ PACKAGE = "PyQt5"
+ elif USE_PYQT6:
+ PACKAGE = "PyQt6"
+ elif USE_PYSIDE6:
+ PACKAGE = "PySide6"
diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py
index ab2488301..4ab91db1f 100644
--- a/qutebrowser/qutebrowser.py
+++ b/qutebrowser/qutebrowser.py
@@ -54,6 +54,7 @@ check_python_version()
import argparse # FIXME:qt6 (lint): disable=wrong-import-order
from qutebrowser.misc import earlyinit
+from qutebrowser.qt import machinery
def get_argparser():
@@ -82,6 +83,11 @@ def get_argparser():
"qutebrowser instance running.")
parser.add_argument('--backend', choices=['webkit', 'webengine'],
help="Which backend to use.")
+ parser.add_argument('--qt-wrapper', choices=machinery.WRAPPERS,
+ help="Which Qt wrapper to use. This can also be set "
+ "via the QUTE_QT_WRAPPER environment variable. "
+ "If both are set, the command line argument takes "
+ "precedence.")
parser.add_argument('--desktop-file-name',
default="org.qutebrowser.qutebrowser",
help="Set the base name of the desktop entry for this "
@@ -238,6 +244,7 @@ def main():
args = parser.parse_args(argv)
if args.json_args is not None:
args = _unpack_json_args(args)
+ machinery.init(args)
earlyinit.early_init(args)
# We do this imports late as earlyinit needs to be run first (because of
# version checking and other early initialization)