diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 64dddd2f8..580e532f8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -56,7 +56,8 @@ jobs:
- name: Install dependencies
run: |
[[ ${{ matrix.testenv }} == eslint ]] && npm install -g eslint
- [[ ${{ matrix.testenv }} == docs ]] && sudo apt-get update && sudo apt-get install --no-install-recommends asciidoc
+ [[ ${{ matrix.testenv }} == docs ]] && sudo apt-get update && sudo apt-get install --no-install-recommends asciidoc libegl1-mesa
+ [[ ${{ matrix.testenv }} == vulture || ${{ matrix.testenv }} == pylint ]] && sudo apt-get update && sudo apt-get install --no-install-recommends libegl1-mesa
if [[ ${{ matrix.testenv }} == shellcheck ]]; then
scversion="stable"
bindir="$HOME/.local/bin"
@@ -89,17 +90,16 @@ jobs:
fail-fast: false
matrix:
include:
- - testenv: py
+ - testenv: py-qt5
image: archlinux-webkit
- - testenv: py
+ - testenv: py-qt5
image: archlinux-webengine
- - testenv: py-qt6
+ - testenv: py-qt5
+ image: archlinux-webengine-unstable
+ - testenv: py
image: archlinux-webengine-qt6
- testenv: py
- image: archlinux-webengine-unstable
- args: ""
- # - testenv: py
- # image: archlinux-webengine-unstable-qt6 # FIXME:qt6.5 activate
+ image: archlinux-webengine-unstable-qt6
container:
image: "qutebrowser/ci:${{ matrix.image }}"
env:
@@ -115,9 +115,9 @@ jobs:
with:
persist-credentials: false
- name: Set up problem matchers
- run: "python scripts/dev/ci/problemmatchers.py py38 ${{ runner.temp }}"
+ run: "python scripts/dev/ci/problemmatchers.py tests ${{ runner.temp }}"
- name: Run tox
- run: "dbus-run-session -- tox -e ${{ matrix.testenv }} -- ${{ matrix.args }}"
+ run: "dbus-run-session -- tox -e ${{ matrix.testenv }}"
tests:
if: "!contains(github.event.head_commit.message, '[ci skip]')"
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index cabf2d8c4..68d2243a4 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -15,6 +15,7 @@ jobs:
- archlinux-webkit
- archlinux-webengine
- archlinux-webengine-unstable
+ - archlinux-webengine-unstable-qt6
- archlinux-webengine-qt6
steps:
- uses: actions/checkout@v3
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 2254abb4a..c1a8dda8a 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -16,50 +16,50 @@ jobs:
include:
- os: macos-11
branch: master
+ toxenv: build-release-qt5
+ name: qt5-macos
+ - os: windows-2019
+ args: --64bit
+ branch: master
+ toxenv: build-release-qt5
+ name: qt5-windows-64bit
+ - os: windows-2019
+ args: --32bit
+ branch: master
+ toxenv: build-release-qt5
+ name: qt5-windows-32bit
+
+ - os: macos-11
+ args: --debug
+ branch: master
+ toxenv: build-release-qt5
+ name: qt5-macos-debug
+ - os: windows-2019
+ args: --64bit --debug
+ branch: master
+ toxenv: build-release-qt5
+ name: qt5-windows-64bit-debug
+ - os: windows-2019
+ args: --32bit --debug
+ branch: master
+ toxenv: build-release-qt5
+ name: qt5-windows-32bit-debug
+
+ - os: macos-11
toxenv: build-release
name: macos
- os: windows-2019
args: --64bit
- branch: master
toxenv: build-release
name: windows-64bit
- - os: windows-2019
- args: --32bit
- branch: master
- toxenv: build-release
- name: windows-32bit
-
- os: macos-11
args: --debug
- branch: master
toxenv: build-release
name: macos-debug
- os: windows-2019
args: --64bit --debug
- branch: master
toxenv: build-release
name: windows-64bit-debug
- - os: windows-2019
- args: --32bit --debug
- branch: master
- toxenv: build-release
- name: windows-32bit-debug
-
- - os: macos-11
- toxenv: build-release-qt6
- name: qt6-macos
- - os: windows-2019
- args: --64bit
- toxenv: build-release-qt6
- name: qt6-windows-64bit
- - os: macos-11
- args: --debug
- toxenv: build-release-qt6
- name: qt6-macos-debug
- - os: windows-2019
- args: --64bit --debug
- toxenv: build-release-qt6
- name: qt6-windows-64bit-debug
runs-on: "${{ matrix.os }}"
timeout-minutes: 45
steps:
diff --git a/.pylintrc b/.pylintrc
index f89e3fa50..341bbe8cb 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,6 +1,6 @@
[MASTER]
ignore=resources.py
-extension-pkg-whitelist=PyQt5,sip
+extension-pkg-whitelist=PyQt5,PyQt6,sip
load-plugins=qute_pylint.config,
pylint.extensions.docstyle,
pylint.extensions.emptystring,
@@ -58,8 +58,8 @@ disable=locally-disabled,
missing-type-doc,
missing-param-doc,
useless-param-doc,
- wrong-import-order, # FIXME:qt6 (lint)
- ungrouped-imports, # FIXME:qt6 (lint)
+ wrong-import-order, # doesn't work with qutebrowser.qt, even with known-third-party set
+ ungrouped-imports, # ditto
[BASIC]
function-rgx=[a-z_][a-z0-9_]{2,50}$
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 666e24177..b8bce1545 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -19,6 +19,17 @@ breaking changes (such as renamed commands) can happen in minor releases.
v3.0.0 (unreleased)
-------------------
+Major changes
+~~~~~~~~~~~~~
+
+- qutebrowser now supports Qt 6 and uses it by default. Qt 5.15 is used as a
+ fallback if Qt 6 is unavailable. This behavior can be customized in three ways
+ (in order of precedence):
+ * Via `--qt-wrapper PyQt5` or `--qt-wrapper PyQt6` command-line arguments.
+ * Via the `QUTE_QT_WRAPPER` environment variable, set to `PyQt6` or `PyQt5`.
+ * For packagers wanting to provide packages specific to a Qt version,
+ patch `qutebrowser/qt/machinery.py` and set `_WRAPPER_OVERRIDE`.
+
Added
~~~~~
diff --git a/misc/qutebrowser.spec b/misc/qutebrowser.spec
index 467994bab..1eee9161d 100644
--- a/misc/qutebrowser.spec
+++ b/misc/qutebrowser.spec
@@ -82,7 +82,7 @@ def get_data_files():
def get_hidden_imports():
- imports = [] if "PYINSTALLER_QT6" in os.environ else ['PyQt5.QtOpenGL']
+ imports = ["PyQt5.QtOpenGL"] if "PYINSTALLER_QT5" in os.environ else []
for info in loader.walk_components():
imports.append(info.name)
return imports
diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt
index 029fb4a6b..26f81ab23 100644
--- a/misc/requirements/requirements-pyqt.txt
+++ b/misc/requirements/requirements-pyqt.txt
@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-PyQt5==5.15.9
-PyQt5-Qt5==5.15.2
-PyQt5-sip==12.12.1
-PyQtWebEngine==5.15.6
-PyQtWebEngine-Qt5==5.15.2
+PyQt6==6.5.1
+PyQt6-Qt6==6.5.1
+PyQt6-sip==13.5.1
+PyQt6-WebEngine==6.5.0
+PyQt6-WebEngine-Qt6==6.5.1
diff --git a/misc/requirements/requirements-pyqt.txt-raw b/misc/requirements/requirements-pyqt.txt-raw
index 9c6afbf16..68a5db685 100644
--- a/misc/requirements/requirements-pyqt.txt-raw
+++ b/misc/requirements/requirements-pyqt.txt-raw
@@ -1,2 +1,4 @@
-PyQt5
-PyQtWebEngine
+PyQt6
+PyQt6-Qt6
+PyQt6-WebEngine
+PyQt6-WebEngine-Qt6
diff --git a/misc/userscripts/add-nextcloud-bookmarks b/misc/userscripts/add-nextcloud-bookmarks
index 86f4f5bc7..2a480ccff 100755
--- a/misc/userscripts/add-nextcloud-bookmarks
+++ b/misc/userscripts/add-nextcloud-bookmarks
@@ -41,7 +41,7 @@ from json import dumps
from os import environ, path
from sys import argv, exit
-from PyQt5.QtWidgets import QApplication, QInputDialog, QLineEdit
+from PyQt6.QtWidgets import QApplication, QInputDialog, QLineEdit
from requests import get, post
from requests.auth import HTTPBasicAuth
@@ -54,7 +54,7 @@ def get_text(name, info):
None,
"add-nextcloud-bookmarks userscript",
"Please enter {}".format(info),
- QLineEdit.Password,
+ QLineEdit.EchoMode.Password,
)
else:
text, ok = QInputDialog.getText(
diff --git a/misc/userscripts/add-nextcloud-cookbook b/misc/userscripts/add-nextcloud-cookbook
index 3952bb16f..151090785 100755
--- a/misc/userscripts/add-nextcloud-cookbook
+++ b/misc/userscripts/add-nextcloud-cookbook
@@ -37,7 +37,7 @@ import configparser
from os import environ, path
from sys import argv, exit
-from PyQt5.QtWidgets import QApplication, QInputDialog, QLineEdit
+from PyQt6.QtWidgets import QApplication, QInputDialog, QLineEdit
from requests import post
from requests.auth import HTTPBasicAuth
@@ -50,7 +50,7 @@ def get_text(name, info):
None,
"add-nextcloud-cookbook userscript",
"Please enter {}".format(info),
- QLineEdit.Password,
+ QLineEdit.EchoMode.Password,
)
else:
text, ok = QInputDialog.getText(
diff --git a/misc/userscripts/qute-keepass b/misc/userscripts/qute-keepass
index 285377ffc..f88493d8e 100755
--- a/misc/userscripts/qute-keepass
+++ b/misc/userscripts/qute-keepass
@@ -42,7 +42,7 @@ you do not do this, you will get 'element not editable' errors.
If keepass takes a while to open the DB, you might want to consider reducing
the number of transform rounds in your database settings.
-Dependencies: pykeepass (in python3), PyQt5. Without pykeepass, you will get an
+Dependencies: pykeepass (in python3), PyQt6. Without pykeepass, you will get an
exit code of 100.
********************!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!******************
@@ -64,8 +64,8 @@ import shlex
import subprocess
import sys
-from PyQt5.QtCore import QUrl
-from PyQt5.QtWidgets import QApplication, QInputDialog, QLineEdit
+from PyQt6.QtCore import QUrl
+from PyQt6.QtWidgets import QApplication, QInputDialog, QLineEdit
try:
import pykeepass
@@ -152,7 +152,7 @@ def get_password():
text, ok = QInputDialog.getText(
None, "KeePass DB Password",
"Please enter your KeePass Master Password",
- QLineEdit.Password)
+ QLineEdit.EchoMode.Password)
if not ok:
stderr('Password Prompt Rejected.')
sys.exit(ExitCodes.USER_QUIT)
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index bb2ff56e7..fbfa3df12 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -367,6 +367,14 @@ def _open_special_pages(args):
os.environ.get("QTWEBENGINE_DISABLE_SANDBOX") == "1"
),
'qute://warning/sandboxing'),
+
+ ('qt5-warning-shown',
+ (
+ machinery.IS_QT5 and
+ machinery.INFO.reason == machinery.SelectionReason.auto and
+ objects.backend != usertypes.Backend.QtWebKit,
+ ),
+ 'qute://warning/qt5'),
]
if 'quickstart-done' not in general_sect:
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py
index 25834670b..0073f9bd2 100644
--- a/qutebrowser/browser/qutescheme.py
+++ b/qutebrowser/browser/qutescheme.py
@@ -22,6 +22,7 @@ Module attributes:
_HANDLERS: The handlers registered via decorators.
"""
+import sys
import html
import json
import os
@@ -583,6 +584,12 @@ def qute_warning(url: QUrl) -> _HandlerRet:
elif path == '/sandboxing':
src = jinja.render('warning-sandboxing.html',
title='Qt 6 macOS sandboxing warning')
+ elif path == '/qt5':
+ is_venv = hasattr(sys, 'real_prefix') or sys.base_prefix != sys.prefix
+ src = jinja.render('warning-qt5.html',
+ title='Switch to Qt 6',
+ is_venv=is_venv,
+ prefix=sys.prefix)
else:
raise NotFoundError("Invalid warning page {}".format(path))
return 'text/html', src
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index e55d75ecd..c2957181b 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -400,7 +400,8 @@ class WebEngineCaret(browsertab.AbstractCaret):
# https://bugreports.qt.io/browse/QTBUG-53134
# Even on Qt 5.10 selectedText() seems to work poorly, see
# https://github.com/qutebrowser/qutebrowser/issues/3523
- # FIXME:qt6 Reevaluate?
+ # With Qt 6.2-6.5, there still seem to be issues (especially with
+ # multi-line text)
self._tab.run_js_async(javascript.assemble('caret', 'getSelection'),
callback)
diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py
index ef3e3bea5..8bf5031b1 100644
--- a/qutebrowser/browser/webkit/webkitelem.py
+++ b/qutebrowser/browser/webkit/webkitelem.py
@@ -15,16 +15,15 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see
+ qutebrowser now supports Qt 6. +
++ However, in your environment, Qt 6 is not installed. Thus, qutebrowser is still using Qt 5 instead. + + Qt 5.15 based on a very old Chromium version (83 or 87, from mid/late 2020). +
+{% if is_venv %} ++ You are using a virtualenv. If you want to use Qt 6, you need to create a new + virtualenv with PyQt6 installed. + + If using mkvenv.py, rerun the script to create a + new virtualenv with Qt 6. +
+{% endif %} ++ Python installation prefix: {{ prefix }} +
+{% endblock %} diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index e2a15b2c0..10f4d5378 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -37,9 +37,7 @@ from qutebrowser.qt import machinery from qutebrowser.qt.core import Qt, QEvent from qutebrowser.qt.gui import QKeySequence, QKeyEvent if machinery.IS_QT6: - # FIXME:qt6 (lint) how come pylint isn't picking this up with both backends - # installed? - from qutebrowser.qt.core import QKeyCombination # pylint: disable=no-name-in-module + from qutebrowser.qt.core import QKeyCombination else: QKeyCombination = None # QKeyCombination was added in Qt 6 @@ -349,7 +347,7 @@ def _unset_modifier_bits( https://github.com/python/cpython/issues/105497 """ if machinery.IS_QT5: - return cast(_ModifierType, modifiers & ~mask) + return Qt.KeyboardModifiers(modifiers & ~mask) # can lose type if it's 0 else: return Qt.KeyboardModifier(modifiers.value & ~mask.value) @@ -369,11 +367,14 @@ class KeyInfo: def __post_init__(self) -> None: """Run some validation on the key/modifier values.""" - # This is mainly useful while porting from Qt 5 to 6. - # FIXME:qt6 do we want to remove or keep this (and fix the remaining - # issues) when done? - # assert isinstance(self.key, Qt.Key), self.key - # assert isinstance(self.modifiers, Qt.KeyboardModifier), self.modifiers + # This changed with Qt 6, and e.g. to_qt() relies on this. + if machinery.IS_QT5: + modifier_classes = (Qt.KeyboardModifier, Qt.KeyboardModifiers) + elif machinery.IS_QT6: + modifier_classes = Qt.KeyboardModifier + assert isinstance(self.key, Qt.Key), self.key + assert isinstance(self.modifiers, modifier_classes), self.modifiers + _assert_plain_key(self.key) _assert_plain_modifier(self.modifiers) @@ -488,16 +489,7 @@ class KeyInfo: if machinery.IS_QT5: return int(self.key) | int(self.modifiers) else: - try: - # FIXME:qt6 We might want to consider only supporting KeyInfo to be - # instanciated with a real Qt.Key, not with ints. See __post_init__. - key = Qt.Key(self.key) - except ValueError as e: - # WORKAROUND for - # https://www.riverbankcomputing.com/pipermail/pyqt/2022-April/044607.html - raise InvalidKeyError(e) - - return QKeyCombination(self.modifiers, key) + return QKeyCombination(self.modifiers, self.key) def with_stripped_modifiers(self, modifiers: Qt.KeyboardModifier) -> "KeyInfo": mods = _unset_modifier_bits(self.modifiers, modifiers) diff --git a/qutebrowser/qt/_core_pyqtproperty.py b/qutebrowser/qt/_core_pyqtproperty.py index 8ae62264f..ae6435039 100644 --- a/qutebrowser/qt/_core_pyqtproperty.py +++ b/qutebrowser/qt/_core_pyqtproperty.py @@ -5,7 +5,7 @@ https://github.com/python-qt-tools/PyQt5-stubs/blob/5.15.6.0/PyQt5-stubs/QtCore. """ # flake8: noqa -# pylint: disable=invalid-name,missing-class-docstring,too-many-arguments,redefined-builtin,unused-argument,import-error +# pylint: disable=invalid-name,missing-class-docstring,too-many-arguments,redefined-builtin,unused-argument,no-name-in-module import typing from PyQt6.QtCore import QObject, pyqtSignal diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index e626edcb4..616c7ccfc 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -3,6 +3,16 @@ """Qt wrapper selection. Contains selection logic and globals for Qt wrapper selection. + +All other files in this package are intended to be simple wrappers around Qt imports. +Depending on what is set in this module, they import from PyQt5 or PyQt6. + +The import wrappers are intended to be as thin as possible. They will not unify +API-level differences between Qt 5 and Qt 6. This is best handled by the calling code, +which has a better picture of what changed between APIs and how to best handle it. + +What they *will* do is handle simple 1:1 renames of classes, or moves between +modules (where they aim to always expose the Qt 6 API). See e.g. webenginecore.py. """ # NOTE: No qutebrowser or PyQt import should be done here (at import time), @@ -20,11 +30,11 @@ from typing import Optional, Dict from qutebrowser.utils import log -# 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 +# Packagers: Patch the line below to enforce a Qt wrapper, e.g.: +# sed -i 's/_WRAPPER_OVERRIDE = .*/_WRAPPER_OVERRIDE = "PyQt6"/' qutebrowser/qt/machinery.py # # Users: Set the QUTE_QT_WRAPPER environment variable to change the default wrapper. -_DEFAULT_WRAPPER = "PyQt5" +_WRAPPER_OVERRIDE = None WRAPPERS = [ "PyQt6", @@ -80,6 +90,9 @@ class SelectionReason(enum.Enum): #: The wrapper was faked/patched out (e.g. in tests). fake = "fake" + #: The wrapper was overridden by patching _WRAPPER_OVERRIDE. + override = "override" + #: The reason was not set. unknown = "unknown" @@ -152,7 +165,7 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo: - 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). + - Otherwise, try the wrappers in WRAPPER in order (PyQt6 -> PyQt5) """ # If any Qt wrapper has been imported before this, something strange might # be happening. @@ -170,15 +183,17 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo: if env_wrapper == "auto": return _autoselect_wrapper() elif env_wrapper not in WRAPPERS: - raise Error(f"Unknown wrapper {env_wrapper} set via {env_var}, " - f"allowed: {', '.join(WRAPPERS)}") + raise Error( + f"Unknown wrapper {env_wrapper} set via {env_var}, " + f"allowed: {', '.join(WRAPPERS)}" + ) return SelectionInfo(wrapper=env_wrapper, reason=SelectionReason.env) - # FIXME:qt6 Go back to the auto-detection once ready - # FIXME:qt6 Make sure to still consider _DEFAULT_WRAPPER for packagers - # (rename to _WRAPPER_OVERRIDE since our sed command is broken anyways then?) - # return _autoselect_wrapper() - return SelectionInfo(wrapper=_DEFAULT_WRAPPER, reason=SelectionReason.default) + if _WRAPPER_OVERRIDE is not None: + assert _WRAPPER_OVERRIDE in WRAPPERS # type: ignore[unreachable] + return SelectionInfo(wrapper=_WRAPPER_OVERRIDE, reason=SelectionReason.override) + + return _autoselect_wrapper() # Values are set in init(). If you see a NameError here, it means something tried to @@ -219,8 +234,7 @@ def _set_globals(info: SelectionInfo) -> None: Those are split into multiple global variables because that way we can teach mypy about them via --always-true and --always-false, see tox.ini. """ - global INFO, USE_PYQT5, USE_PYQT6, USE_PYSIDE6, IS_QT5, IS_QT6, \ - IS_PYQT, IS_PYSIDE, _initialized + global INFO, USE_PYQT5, USE_PYQT6, USE_PYSIDE6, IS_QT5, IS_QT6, IS_PYQT, IS_PYSIDE, _initialized assert info.wrapper is not None, info assert not _initialized diff --git a/qutebrowser/qt/opengl.py b/qutebrowser/qt/opengl.py index 0a14dffad..bc5a31c11 100644 --- a/qutebrowser/qt/opengl.py +++ b/qutebrowser/qt/opengl.py @@ -1,4 +1,4 @@ -# pylint: disable=import-error,wildcard-import,unused-import +# pylint: disable=import-error,wildcard-import,unused-import,unused-wildcard-import """Wrapped Qt imports for Qt OpenGL. diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index e778cc23a..fcca87feb 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -50,7 +50,7 @@ except ImportError: sys.exit(100) check_python_version() -import argparse # FIXME:qt6 (lint): disable=wrong-import-order +import argparse from qutebrowser.misc import earlyinit from qutebrowser.qt import machinery diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 996487693..d8f9693e7 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -136,7 +136,7 @@ def _smoke_test_run( return subprocess.run(argv, check=True, capture_output=True) -def smoke_test(executable: pathlib.Path, debug: bool, qt6: bool) -> None: +def smoke_test(executable: pathlib.Path, debug: bool, qt5: bool) -> None: """Try starting the given qutebrowser executable.""" stdout_whitelist = [] stderr_whitelist = [ @@ -176,7 +176,7 @@ def smoke_test(executable: pathlib.Path, debug: bool, qt6: bool) -> None: r'ContextResult::kTransientFailure: Failed to send ' r'.*CreateCommandBuffer\.'), ]) - if qt6: + if not qt5: stderr_whitelist.extend([ # FIXME:qt6 Qt 6.3 on macOS r'[0-9:]* WARNING: Incompatible version of OpenSSL', @@ -257,10 +257,10 @@ def verify_windows_exe(exe_path: pathlib.Path) -> None: assert pe.verify_checksum() -def patch_mac_app(qt6: bool) -> None: +def patch_mac_app(qt5: bool) -> None: """Patch .app to save some space and make it signable.""" dist_path = pathlib.Path('dist') - ver = '6' if qt6 else '5' + ver = '5' if qt5 else '6' app_path = dist_path / 'qutebrowser.app' contents_path = app_path / 'Contents' @@ -280,7 +280,7 @@ def patch_mac_app(qt6: bool) -> None: file_path.unlink() file_path.symlink_to(target) - if qt6: + if not qt5: # Symlinking QtWebEngineCore.framework does not seem to work with Qt 6. # Also, the symlinking/moving before signing doesn't seem to be required. return @@ -333,7 +333,7 @@ def _mac_bin_path(base: pathlib.Path) -> pathlib.Path: def build_mac( *, gh_token: Optional[str], - qt6: bool, + qt5: bool, skip_packaging: bool, debug: bool, ) -> List[Artifact]: @@ -348,20 +348,20 @@ def build_mac( shutil.rmtree(d, ignore_errors=True) utils.print_title("Updating 3rdparty content") - update_3rdparty.run(ace=False, pdfjs=True, legacy_pdfjs=not qt6, fancy_dmg=False, + update_3rdparty.run(ace=False, pdfjs=True, legacy_pdfjs=qt5, fancy_dmg=False, gh_token=gh_token) utils.print_title("Building .app via pyinstaller") - call_tox(f'pyinstaller-64bit{"-qt6" if qt6 else ""}', '-r', debug=debug) + call_tox(f'pyinstaller-64bit{"-qt5" if qt5 else ""}', '-r', debug=debug) utils.print_title("Patching .app") - patch_mac_app(qt6=qt6) + patch_mac_app(qt5=qt5) utils.print_title("Re-signing .app") sign_mac_app() dist_path = pathlib.Path("dist") utils.print_title("Running pre-dmg smoke test") - smoke_test(_mac_bin_path(dist_path), debug=debug, qt6=qt6) + smoke_test(_mac_bin_path(dist_path), debug=debug, qt5=qt5) if skip_packaging: return [] @@ -371,7 +371,7 @@ def build_mac( subprocess.run(['make', '-f', dmg_makefile_path], check=True) suffix = "-debug" if debug else "" - suffix += "-qt6" if qt6 else "" + suffix += "-qt5" if qt5 else "" dmg_path = dist_path / f'qutebrowser-{qutebrowser.__version__}{suffix}.dmg' pathlib.Path('qutebrowser.dmg').rename(dmg_path) @@ -383,7 +383,7 @@ def build_mac( subprocess.run(['hdiutil', 'attach', dmg_path, '-mountpoint', tmp_path], check=True) try: - smoke_test(_mac_bin_path(tmp_path), debug=debug, qt6=qt6) + smoke_test(_mac_bin_path(tmp_path), debug=debug, qt5=qt5) finally: print("Waiting 10s for dmg to be detachable...") time.sleep(10) @@ -422,7 +422,7 @@ def _get_windows_python_path(x64: bool) -> pathlib.Path: def _build_windows_single( *, x64: bool, - qt6: bool, + qt5: bool, skip_packaging: bool, debug: bool, ) -> List[Artifact]: @@ -437,9 +437,9 @@ def _build_windows_single( python = _get_windows_python_path(x64=x64) suffix = "64bit" if x64 else "32bit" - if qt6: + if qt5: # FIXME:qt6 does this regress 391623d5ec983ecfc4512c7305c4b7a293ac3872? - suffix += "-qt6" + suffix += "-qt5" call_tox(f'pyinstaller-{suffix}', '-r', python=python, debug=debug) out_pyinstaller = dist_path / "qutebrowser" @@ -450,7 +450,7 @@ def _build_windows_single( verify_windows_exe(exe_path) utils.print_title(f"Running {human_arch} smoke test") - smoke_test(exe_path, debug=debug, qt6=qt6) + smoke_test(exe_path, debug=debug, qt5=qt5) if skip_packaging: return [] @@ -463,7 +463,7 @@ def _build_windows_single( desc_arch=human_arch, desc_suffix='' if x64 else ' (only for 32-bit Windows!)', debug=debug, - qt6=qt6, + qt5=qt5, ) @@ -472,12 +472,12 @@ def build_windows( skip_packaging: bool, only_32bit: bool, only_64bit: bool, - qt6: bool, + qt5: bool, debug: bool, ) -> List[Artifact]: """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") - update_3rdparty.run(nsis=True, ace=False, pdfjs=True, legacy_pdfjs=not qt6, + update_3rdparty.run(nsis=True, ace=False, pdfjs=True, legacy_pdfjs=qt5, fancy_dmg=False, gh_token=gh_token) utils.print_title("Building Windows binaries") @@ -493,14 +493,14 @@ def build_windows( x64=True, skip_packaging=skip_packaging, debug=debug, - qt6=qt6, + qt5=qt5, ) - if not only_64bit and not qt6: + if not only_64bit and not qt5: artifacts += _build_windows_single( x64=False, skip_packaging=skip_packaging, debug=debug, - qt6=qt6, + qt5=qt5, ) return artifacts @@ -514,7 +514,7 @@ def _package_windows_single( desc_suffix: str, filename_arch: str, debug: bool, - qt6: bool, + qt5: bool, ) -> List[Artifact]: """Build the given installer/zip for windows.""" artifacts = [] @@ -532,8 +532,8 @@ def _package_windows_single( ] if debug: name_parts.append('debug') - if qt6: - name_parts.append('qt6') + if qt5: + name_parts.append('qt5') name = '-'.join(name_parts) + '.exe' artifacts.append(Artifact( @@ -552,8 +552,8 @@ def _package_windows_single( ] if debug: zip_name_parts.append('debug') - if qt6: - zip_name_parts.append('qt6') + if qt5: + zip_name_parts.append('qt5') zip_name = '-'.join(zip_name_parts) + '.zip' zip_path = dist_path / zip_name @@ -738,8 +738,8 @@ def main() -> None: help="Skip Windows 32 bit build.", dest='only_64bit') parser.add_argument('--debug', action='store_true', required=False, help="Build a debug build.") - parser.add_argument('--qt6', action='store_true', required=False, - help="Build against PyQt6") + parser.add_argument('--qt5', action='store_true', required=False, + help="Build against PyQt5") args = parser.parse_args() utils.change_cwd() @@ -768,14 +768,14 @@ def main() -> None: skip_packaging=args.skip_packaging, only_32bit=args.only_32bit, only_64bit=args.only_64bit, - qt6=args.qt6, + qt5=args.qt5, debug=args.debug, ) elif IS_MACOS: artifacts = build_mac( gh_token=gh_token, skip_packaging=args.skip_packaging, - qt6=args.qt6, + qt5=args.qt5, debug=args.debug, ) else: diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py index 240b5e6f1..215a1cfa0 100644 --- a/scripts/dev/misc_checks.py +++ b/scripts/dev/misc_checks.py @@ -27,7 +27,7 @@ import subprocess import tokenize import traceback import pathlib -from typing import List, Iterator, Optional +from typing import List, Iterator, Optional, Tuple REPO_ROOT = pathlib.Path(__file__).resolve().parents[2] sys.path.insert(0, str(REPO_ROOT)) @@ -152,6 +152,24 @@ def _check_spelling_file(path, fobj, patterns): return ok +def _check_spelling_all( + args: argparse.Namespace, + ignored: List[pathlib.Path], + patterns: List[Tuple[re.Pattern, str]], +) -> Optional[bool]: + try: + ok = True + for path in _get_files(verbose=args.verbose, ignored=ignored): + with tokenize.open(str(path)) as f: + if not _check_spelling_file(path, f, patterns): + ok = False + print() + return ok + except Exception: + traceback.print_exc() + return None + + def check_spelling(args: argparse.Namespace) -> Optional[bool]: """Check commonly misspelled words.""" # Words which I often misspell @@ -273,25 +291,13 @@ def check_spelling(args: argparse.Namespace) -> Optional[bool]: hint_data / 'ace' / 'ace.js', hint_data / 'bootstrap' / 'bootstrap.css', ] - - try: - ok = True - for path in _get_files(verbose=args.verbose, ignored=ignored): - with tokenize.open(path) as f: - if not _check_spelling_file(path, f, patterns): - ok = False - print() - return ok - except Exception: - traceback.print_exc() - return None + return _check_spelling_all(args=args, ignored=ignored, patterns=patterns) def check_pyqt_imports(args: argparse.Namespace) -> Optional[bool]: """Check for direct PyQt imports.""" ignored = [ pathlib.Path("qutebrowser", "qt"), - # FIXME:qt6 fix those too? pathlib.Path("misc", "userscripts"), pathlib.Path("scripts"), ] @@ -305,18 +311,7 @@ def check_pyqt_imports(args: argparse.Namespace) -> Optional[bool]: "Use 'import qutebrowser.qt.MODULE' instead", ) ] - # FIXME:qt6 unify this with check_spelling somehow? - try: - ok = True - for path in _get_files(verbose=args.verbose, ignored=ignored): - with tokenize.open(str(path)) as f: - if not _check_spelling_file(path, f, patterns): - ok = False - print() - return ok - except Exception: - traceback.print_exc() - return None + return _check_spelling_all(args=args, ignored=ignored, patterns=patterns) def check_vcs_conflict(args: argparse.Namespace) -> Optional[bool]: diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index 960b5a514..1e7ed0f61 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -60,7 +60,6 @@ def whitelist_generator(): # noqa: C901 yield 'qutebrowser.misc.sql.SqliteErrorCode.CONSTRAINT' yield 'qutebrowser.misc.throttle.Throttle.set_delay' yield 'qutebrowser.misc.guiprocess.GUIProcess.stderr' - yield 'qutebrowser.qt.machinery._autoselect_wrapper' # FIXME:qt6 # Qt attributes yield 'PyQt5.QtWebKit.QWebPage.ErrorPageExtensionReturn().baseUrl' diff --git a/scripts/dev/standardpaths_tester.py b/scripts/dev/standardpaths_tester.py index ff85b2a4c..bbd0a39fb 100644 --- a/scripts/dev/standardpaths_tester.py +++ b/scripts/dev/standardpaths_tester.py @@ -21,7 +21,7 @@ import os import sys -from PyQt5.QtCore import (QT_VERSION_STR, PYQT_VERSION_STR, qVersion, +from PyQt6.QtCore import (QT_VERSION_STR, PYQT_VERSION_STR, qVersion, QStandardPaths, QCoreApplication) diff --git a/scripts/keytester.py b/scripts/keytester.py index 6d994114d..861133c06 100644 --- a/scripts/keytester.py +++ b/scripts/keytester.py @@ -21,8 +21,7 @@ Use python3 -m scripts.keytester to launch it. """ -from PyQt5.QtWidgets import QApplication - +from qutebrowser.qt.widgets import QApplication from qutebrowser.misc import miscwidgets app = QApplication([]) diff --git a/scripts/link_pyqt.py b/scripts/link_pyqt.py index 4581bef41..63bdde959 100644 --- a/scripts/link_pyqt.py +++ b/scripts/link_pyqt.py @@ -125,7 +125,7 @@ def get_lib_path(executable, name, required=True): raise ValueError("Unexpected output: {!r}".format(output)) -def link_pyqt(executable, venv_path, *, version='5'): +def link_pyqt(executable, venv_path, *, version): """Symlink the systemwide PyQt/sip into the venv. Args: diff --git a/scripts/mkvenv.py b/scripts/mkvenv.py index 625cedd1a..9e9c1f4a2 100755 --- a/scripts/mkvenv.py +++ b/scripts/mkvenv.py @@ -134,8 +134,7 @@ def pyqt_versions() -> List[str]: def _is_qt6_version(version: str) -> bool: """Check if the given version is Qt 6.""" - # FIXME:qt6 Adjust once auto = Qt 6 - return version == "6" or version.startswith("6.") + return version in ["auto", "6"] or version.startswith("6.") def run_venv( @@ -228,7 +227,7 @@ def requirements_file(name: str) -> pathlib.Path: def pyqt_requirements_file(version: str) -> pathlib.Path: """Get the filename of the requirements file for the given PyQt version.""" - name = 'pyqt' if version == 'auto' else 'pyqt-{}'.format(version) + name = 'pyqt-6' if version == 'auto' else f'pyqt-{version}' return requirements_file(name) @@ -439,7 +438,7 @@ def run_qt_smoke_test_single( def run_qt_smoke_test(venv_dir: pathlib.Path, *, pyqt_version: str) -> None: """Make sure the Qt installation works.""" # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-104415 - no_debug = pyqt_version in ("6.3", "6") and sys.platform == "darwin" + no_debug = pyqt_version == "6.3" and sys.platform == "darwin" if no_debug: try: run_qt_smoke_test_single(venv_dir, debug=False, pyqt_version=pyqt_version) @@ -505,6 +504,9 @@ def install_pyqt(venv_dir, args): install_pyqt_binary(venv_dir, args.pyqt_version) if args.pyqt_snapshot: install_pyqt_shapshot(venv_dir, args.pyqt_snapshot.split(',')) + # Workaround until pyqt 6.5.2 is released on pypi + elif args.pyqt_version in ("6.5", "6", "auto"): + install_pyqt_shapshot(venv_dir, ["PyQt6-Qt6", "PyQt6-WebEngine-Qt6"]) elif args.pyqt_type == 'source': install_pyqt_source(venv_dir, args.pyqt_version) elif args.pyqt_type == 'link': diff --git a/scripts/opengl_info.py b/scripts/opengl_info.py index 5dc8f81c6..7c5ede6e7 100644 --- a/scripts/opengl_info.py +++ b/scripts/opengl_info.py @@ -18,8 +18,8 @@ """Show information about the OpenGL setup.""" -from PyQt5.QtGui import (QOpenGLContext, QOpenGLVersionProfile, - QOffscreenSurface, QGuiApplication) +from PyQt6.QtGui import QOpenGLContext, QOffscreenSurface, QGuiApplication +from PyQt6.QtOpenGL import QOpenGLVersionProfile, QOpenGLVersionFunctionsFactory app = QGuiApplication([]) @@ -38,7 +38,7 @@ print(f"GLES: {ctx.isOpenGLES()}") vp = QOpenGLVersionProfile() vp.setVersion(2, 0) -vf = ctx.versionFunctions(vp) +vf = QOpenGLVersionFunctionsFactory.get(vp, ctx) print(f"Vendor: {vf.glGetString(vf.GL_VENDOR)}") print(f"Renderer: {vf.glGetString(vf.GL_RENDERER)}") print(f"Version: {vf.glGetString(vf.GL_VERSION)}") diff --git a/tests/unit/browser/webkit/test_tabhistory.py b/tests/unit/browser/webkit/test_tabhistory.py index 047454e25..cd40af6e8 100644 --- a/tests/unit/browser/webkit/test_tabhistory.py +++ b/tests/unit/browser/webkit/test_tabhistory.py @@ -15,9 +15,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see