From ad77048d530aefeecdd1c784accaddb929f8bdc6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 23 Jun 2023 23:38:22 +0200 Subject: qt: blackify --- qutebrowser/qt/machinery.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'qutebrowser') diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index 4e88a0634..bd5062120 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -168,8 +168,10 @@ 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 @@ -217,8 +219,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 -- cgit v1.2.3-54-g00ecf From 73fb5c4c49a9f79b1820486dc11e819f859d593a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 23 Jun 2023 23:38:54 +0200 Subject: qt: Switch to autoselection of Qt backend by default --- qutebrowser/qt/machinery.py | 6 +- tests/unit/test_qt_machinery.py | 191 ++++++++++++++++++++-------------------- 2 files changed, 99 insertions(+), 98 deletions(-) (limited to 'qutebrowser') diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index bd5062120..a5705a933 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -150,7 +150,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. @@ -174,11 +174,9 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo: ) 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) + return _autoselect_wrapper() # Values are set in init(). If you see a NameError here, it means something tried to diff --git a/tests/unit/test_qt_machinery.py b/tests/unit/test_qt_machinery.py index b4d302ae5..40631accc 100644 --- a/tests/unit/test_qt_machinery.py +++ b/tests/unit/test_qt_machinery.py @@ -230,111 +230,114 @@ def test_autoselect( assert machinery._autoselect_wrapper() == expected -@pytest.mark.parametrize( - "args, env, expected", - [ - # Defaults with no overrides - ( - None, - None, - machinery.SelectionInfo( - wrapper="PyQt5", reason=machinery.SelectionReason.default - ), - ), - ( - argparse.Namespace(qt_wrapper=None), - None, - machinery.SelectionInfo( - wrapper="PyQt5", reason=machinery.SelectionReason.default - ), - ), - ( - argparse.Namespace(qt_wrapper=None), - "", - machinery.SelectionInfo( - wrapper="PyQt5", reason=machinery.SelectionReason.default - ), - ), - # Only argument given - ( - argparse.Namespace(qt_wrapper="PyQt6"), - None, - machinery.SelectionInfo( - wrapper="PyQt6", reason=machinery.SelectionReason.cli +class TestSelectWrapper: + @pytest.mark.parametrize( + "args, env, expected", + [ + # Only argument given + ( + argparse.Namespace(qt_wrapper="PyQt6"), + None, + machinery.SelectionInfo( + wrapper="PyQt6", reason=machinery.SelectionReason.cli + ), ), - ), - ( - argparse.Namespace(qt_wrapper="PyQt5"), - None, - machinery.SelectionInfo( - wrapper="PyQt5", reason=machinery.SelectionReason.cli + ( + argparse.Namespace(qt_wrapper="PyQt5"), + None, + machinery.SelectionInfo( + wrapper="PyQt5", reason=machinery.SelectionReason.cli + ), ), - ), - ( - argparse.Namespace(qt_wrapper="PyQt5"), - "", - machinery.SelectionInfo( - wrapper="PyQt5", reason=machinery.SelectionReason.cli + ( + argparse.Namespace(qt_wrapper="PyQt5"), + "", + machinery.SelectionInfo( + wrapper="PyQt5", reason=machinery.SelectionReason.cli + ), ), - ), - # Only environment variable given - ( - None, - "PyQt6", - machinery.SelectionInfo( - wrapper="PyQt6", reason=machinery.SelectionReason.env + # Only environment variable given + ( + None, + "PyQt6", + machinery.SelectionInfo( + wrapper="PyQt6", reason=machinery.SelectionReason.env + ), ), - ), - ( - None, - "PyQt5", - machinery.SelectionInfo( - wrapper="PyQt5", reason=machinery.SelectionReason.env + ( + None, + "PyQt5", + machinery.SelectionInfo( + wrapper="PyQt5", reason=machinery.SelectionReason.env + ), ), - ), - # Both given - ( - argparse.Namespace(qt_wrapper="PyQt5"), - "PyQt6", - machinery.SelectionInfo( - wrapper="PyQt5", reason=machinery.SelectionReason.cli + # Both given + ( + argparse.Namespace(qt_wrapper="PyQt5"), + "PyQt6", + machinery.SelectionInfo( + wrapper="PyQt5", reason=machinery.SelectionReason.cli + ), ), - ), - ( - argparse.Namespace(qt_wrapper="PyQt6"), - "PyQt5", - machinery.SelectionInfo( - wrapper="PyQt6", reason=machinery.SelectionReason.cli + ( + argparse.Namespace(qt_wrapper="PyQt6"), + "PyQt5", + machinery.SelectionInfo( + wrapper="PyQt6", reason=machinery.SelectionReason.cli + ), ), - ), - ( - argparse.Namespace(qt_wrapper="PyQt6"), - "PyQt6", - machinery.SelectionInfo( - wrapper="PyQt6", reason=machinery.SelectionReason.cli + ( + argparse.Namespace(qt_wrapper="PyQt6"), + "PyQt6", + machinery.SelectionInfo( + wrapper="PyQt6", reason=machinery.SelectionReason.cli + ), ), - ), - ], -) -def test_select_wrapper( - args: Optional[argparse.Namespace], - env: Optional[str], - expected: machinery.SelectionInfo, - monkeypatch: pytest.MonkeyPatch, - undo_init: None, -): - if env is None: - monkeypatch.delenv("QUTE_QT_WRAPPER", raising=False) - else: - monkeypatch.setenv("QUTE_QT_WRAPPER", env) + ], + ) + def test_select( + self, + args: Optional[argparse.Namespace], + env: Optional[str], + expected: machinery.SelectionInfo, + monkeypatch: pytest.MonkeyPatch, + ): + if env is None: + monkeypatch.delenv("QUTE_QT_WRAPPER", raising=False) + else: + monkeypatch.setenv("QUTE_QT_WRAPPER", env) - assert machinery._select_wrapper(args) == expected + assert machinery._select_wrapper(args) == expected + + @pytest.mark.parametrize( + "args, env", + [ + (None, None), + (argparse.Namespace(qt_wrapper=None), None), + (argparse.Namespace(qt_wrapper=None), ""), + ], + ) + def test_autoselect_by_default( + self, + args: Optional[argparse.Namespace], + env: Optional[str], + monkeypatch: pytest.MonkeyPatch, + ): + """Test that the default behavior is to autoselect a wrapper. + + Autoselection itself is tested further down. + """ + if env is None: + monkeypatch.delenv("QUTE_QT_WRAPPER", raising=False) + else: + monkeypatch.setenv("QUTE_QT_WRAPPER", env) + assert machinery._select_wrapper(args).reason == machinery.SelectionReason.auto -def test_select_wrapper_after_qt_import(monkeypatch: pytest.MonkeyPatch): - monkeypatch.setitem(sys.modules, "PyQt6", None) - with pytest.warns(UserWarning, match="PyQt6 already imported"): - machinery._select_wrapper(args=None) + def test_after_qt_import(self, monkeypatch: pytest.MonkeyPatch): + monkeypatch.setitem(sys.modules, "PyQt6", None) + with pytest.warns(UserWarning, match="PyQt6 already imported"): + machinery._select_wrapper(args=None) class TestInit: -- cgit v1.2.3-54-g00ecf From 2ccd49ddf1bfa743271ad4700a2a2e9f57ea1ae5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 23 Jun 2023 23:51:34 +0200 Subject: qt: Introduce _WRAPPER_OVERRIDE for packagers --- qutebrowser/qt/machinery.py | 15 +++-- tests/unit/test_qt_machinery.py | 130 +++++++++++++++++++++++++++------------- 2 files changed, 97 insertions(+), 48 deletions(-) (limited to 'qutebrowser') diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index a5705a933..1269da4c1 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -18,11 +18,11 @@ import importlib import dataclasses from typing import Optional, Dict -# 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", @@ -78,6 +78,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" @@ -174,8 +177,10 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo: ) return SelectionInfo(wrapper=env_wrapper, reason=SelectionReason.env) - # FIXME:qt6 Make sure to still consider _DEFAULT_WRAPPER for packagers - # (rename to _WRAPPER_OVERRIDE since our sed command is broken anyways then?) + if _WRAPPER_OVERRIDE is not None: + assert _WRAPPER_OVERRIDE in WRAPPERS, _WRAPPER_OVERRIDE + return SelectionInfo(wrapper=_WRAPPER_OVERRIDE, reason=SelectionReason.override) + return _autoselect_wrapper() diff --git a/tests/unit/test_qt_machinery.py b/tests/unit/test_qt_machinery.py index 40631accc..b144af030 100644 --- a/tests/unit/test_qt_machinery.py +++ b/tests/unit/test_qt_machinery.py @@ -23,6 +23,7 @@ import html import argparse import typing from typing import Any, Optional, List, Dict, Union +import dataclasses import pytest @@ -230,84 +231,122 @@ def test_autoselect( assert machinery._autoselect_wrapper() == expected +@dataclasses.dataclass +class SelectWrapperCase: + name: str + expected: machinery.SelectionInfo + args: Optional[argparse.Namespace] = None + env: Optional[str] = None + override: Optional[str] = None + + def __str__(self): + return self.name + + class TestSelectWrapper: @pytest.mark.parametrize( - "args, env, expected", + "tc", [ # Only argument given - ( - argparse.Namespace(qt_wrapper="PyQt6"), - None, - machinery.SelectionInfo( + SelectWrapperCase( + "pyqt6-arg", + args=argparse.Namespace(qt_wrapper="PyQt6"), + expected=machinery.SelectionInfo( wrapper="PyQt6", reason=machinery.SelectionReason.cli ), ), - ( - argparse.Namespace(qt_wrapper="PyQt5"), - None, - machinery.SelectionInfo( + SelectWrapperCase( + "pyqt5-arg", + args=argparse.Namespace(qt_wrapper="PyQt5"), + expected=machinery.SelectionInfo( wrapper="PyQt5", reason=machinery.SelectionReason.cli ), ), - ( - argparse.Namespace(qt_wrapper="PyQt5"), - "", - machinery.SelectionInfo( + SelectWrapperCase( + "pyqt6-arg-empty-env", + args=argparse.Namespace(qt_wrapper="PyQt5"), + env="", + expected=machinery.SelectionInfo( wrapper="PyQt5", reason=machinery.SelectionReason.cli ), ), # Only environment variable given - ( - None, - "PyQt6", - machinery.SelectionInfo( + SelectWrapperCase( + "pyqt6-env", + env="PyQt6", + expected=machinery.SelectionInfo( wrapper="PyQt6", reason=machinery.SelectionReason.env ), ), - ( - None, - "PyQt5", - machinery.SelectionInfo( + SelectWrapperCase( + "pyqt5-env", + env="PyQt5", + expected=machinery.SelectionInfo( wrapper="PyQt5", reason=machinery.SelectionReason.env ), ), # Both given - ( - argparse.Namespace(qt_wrapper="PyQt5"), - "PyQt6", - machinery.SelectionInfo( + SelectWrapperCase( + "pyqt5-arg-pyqt6-env", + args=argparse.Namespace(qt_wrapper="PyQt5"), + env="PyQt6", + expected=machinery.SelectionInfo( wrapper="PyQt5", reason=machinery.SelectionReason.cli ), ), - ( - argparse.Namespace(qt_wrapper="PyQt6"), - "PyQt5", - machinery.SelectionInfo( + SelectWrapperCase( + "pyqt6-arg-pyqt5-env", + args=argparse.Namespace(qt_wrapper="PyQt6"), + env="PyQt5", + expected=machinery.SelectionInfo( wrapper="PyQt6", reason=machinery.SelectionReason.cli ), ), - ( - argparse.Namespace(qt_wrapper="PyQt6"), - "PyQt6", - machinery.SelectionInfo( + SelectWrapperCase( + "pyqt6-arg-pyqt6-env", + args=argparse.Namespace(qt_wrapper="PyQt6"), + env="PyQt6", + expected=machinery.SelectionInfo( wrapper="PyQt6", reason=machinery.SelectionReason.cli ), ), + # Override + SelectWrapperCase( + "override-only", + override="PyQt6", + expected=machinery.SelectionInfo( + wrapper="PyQt6", reason=machinery.SelectionReason.override + ), + ), + SelectWrapperCase( + "override-arg", + args=argparse.Namespace(qt_wrapper="PyQt5"), + override="PyQt6", + expected=machinery.SelectionInfo( + wrapper="PyQt5", reason=machinery.SelectionReason.cli + ), + ), + SelectWrapperCase( + "override-env", + env="PyQt5", + override="PyQt6", + expected=machinery.SelectionInfo( + wrapper="PyQt5", reason=machinery.SelectionReason.env + ), + ), ], + ids=str, ) - def test_select( - self, - args: Optional[argparse.Namespace], - env: Optional[str], - expected: machinery.SelectionInfo, - monkeypatch: pytest.MonkeyPatch, - ): - if env is None: + def test_select(self, tc: SelectWrapperCase, monkeypatch: pytest.MonkeyPatch): + if tc.env is None: monkeypatch.delenv("QUTE_QT_WRAPPER", raising=False) else: - monkeypatch.setenv("QUTE_QT_WRAPPER", env) + monkeypatch.setenv("QUTE_QT_WRAPPER", tc.env) - assert machinery._select_wrapper(args) == expected + if tc.override is not None: + monkeypatch.setattr(machinery, "_WRAPPER_OVERRIDE", tc.override) + + assert machinery._select_wrapper(tc.args) == tc.expected @pytest.mark.parametrize( "args, env", @@ -339,6 +378,11 @@ class TestSelectWrapper: with pytest.warns(UserWarning, match="PyQt6 already imported"): machinery._select_wrapper(args=None) + def test_invalid_override(self, monkeypatch: pytest.MonkeyPatch): + monkeypatch.setattr(machinery, "_WRAPPER_OVERRIDE", "invalid") + with pytest.raises(AssertionError, match="invalid"): + machinery._select_wrapper(args=None) + class TestInit: @pytest.fixture -- cgit v1.2.3-54-g00ecf From ffc06e58d6d6254d1758f8328f6dc43cc38651d5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 24 Jun 2023 00:28:05 +0200 Subject: qt6: Switch most tooling/linting to Qt 6 Only mypy missing now... --- .github/workflows/nightly.yml | 40 ++++++++--------- .pylintrc | 2 +- misc/qutebrowser.spec | 2 +- misc/requirements/requirements-pyqt.txt | 10 ++--- misc/requirements/requirements-pyqt.txt-raw | 6 ++- qutebrowser/browser/webkit/webkitelem.py | 5 +-- qutebrowser/browser/webkit/webkithistory.py | 5 +-- qutebrowser/browser/webkit/webkitinspector.py | 4 +- qutebrowser/browser/webkit/webkitsettings.py | 5 +-- qutebrowser/browser/webkit/webkittab.py | 4 +- qutebrowser/browser/webkit/webpage.py | 5 +-- qutebrowser/browser/webkit/webview.py | 5 +-- qutebrowser/keyinput/keyutils.py | 4 +- qutebrowser/qt/opengl.py | 2 +- scripts/dev/build_release.py | 62 +++++++++++++-------------- tests/unit/browser/webkit/test_tabhistory.py | 5 +-- tests/unit/javascript/test_js_execution.py | 7 +-- tox.ini | 31 +++++++------- 18 files changed, 98 insertions(+), 106 deletions(-) (limited to 'qutebrowser') 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 - name: macos + toxenv: build-release-qt5 + name: qt5-macos - os: windows-2019 args: --64bit branch: master - toxenv: build-release - name: windows-64bit + toxenv: build-release-qt5 + name: qt5-windows-64bit - os: windows-2019 args: --32bit branch: master - toxenv: build-release - name: windows-32bit + toxenv: build-release-qt5 + name: qt5-windows-32bit - os: macos-11 args: --debug branch: master - toxenv: build-release - name: macos-debug + toxenv: build-release-qt5 + name: qt5-macos-debug - os: windows-2019 args: --64bit --debug branch: master - toxenv: build-release - name: windows-64bit-debug + toxenv: build-release-qt5 + name: qt5-windows-64bit-debug - os: windows-2019 args: --32bit --debug branch: master - toxenv: build-release - name: windows-32bit-debug + toxenv: build-release-qt5 + name: qt5-windows-32bit-debug - os: macos-11 - toxenv: build-release-qt6 - name: qt6-macos + toxenv: build-release + name: macos - os: windows-2019 args: --64bit - toxenv: build-release-qt6 - name: qt6-windows-64bit + toxenv: build-release + name: windows-64bit - os: macos-11 args: --debug - toxenv: build-release-qt6 - name: qt6-macos-debug + toxenv: build-release + name: macos-debug - os: windows-2019 args: --64bit --debug - toxenv: build-release-qt6 - name: qt6-windows-64bit-debug + toxenv: build-release + name: windows-64bit-debug runs-on: "${{ matrix.os }}" timeout-minutes: 45 steps: diff --git a/.pylintrc b/.pylintrc index f89e3fa50..9f9a0c8a0 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, 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/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 . -# FIXME:qt6 (lint) -# pylint: disable=no-name-in-module - """QtWebKit specific part of the web element API.""" from typing import cast, TYPE_CHECKING, Iterator, List, Optional, Set from qutebrowser.qt.core import QRect, Qt +# pylint: disable=no-name-in-module from qutebrowser.qt.webkit import QWebElement, QWebSettings from qutebrowser.qt.webkitwidgets import QWebFrame +# pylint: enable=no-name-in-module from qutebrowser.config import config from qutebrowser.utils import log, utils, javascript, usertypes diff --git a/qutebrowser/browser/webkit/webkithistory.py b/qutebrowser/browser/webkit/webkithistory.py index aea648361..d89c705e6 100644 --- a/qutebrowser/browser/webkit/webkithistory.py +++ b/qutebrowser/browser/webkit/webkithistory.py @@ -15,14 +15,13 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# FIXME:qt6 (lint) -# pylint: disable=no-name-in-module - """QtWebKit specific part of history.""" import functools +# pylint: disable=no-name-in-module from qutebrowser.qt.webkit import QWebHistoryInterface +# pylint: enable=no-name-in-module from qutebrowser.utils import debug from qutebrowser.misc import debugcachestats diff --git a/qutebrowser/browser/webkit/webkitinspector.py b/qutebrowser/browser/webkit/webkitinspector.py index cb9cb5615..c181435d1 100644 --- a/qutebrowser/browser/webkit/webkitinspector.py +++ b/qutebrowser/browser/webkit/webkitinspector.py @@ -15,13 +15,13 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# FIXME:qt6 (lint) -# pylint: disable=no-name-in-module """Customized QWebInspector for QtWebKit.""" +# pylint: disable=no-name-in-module from qutebrowser.qt.webkit import QWebSettings from qutebrowser.qt.webkitwidgets import QWebInspector, QWebPage +# pylint: enable=no-name-in-module from qutebrowser.qt.widgets import QWidget from qutebrowser.browser import inspector diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index bd65be65b..a20811bae 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -15,9 +15,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# FIXME:qt6 (lint) -# pylint: disable=no-name-in-module - """Bridge from QWebSettings to our own settings. Module attributes: @@ -30,8 +27,10 @@ import os.path from qutebrowser.qt.core import QUrl from qutebrowser.qt.gui import QFont +# pylint: disable=no-name-in-module from qutebrowser.qt.webkit import QWebSettings from qutebrowser.qt.webkitwidgets import QWebPage +# pylint: enable=no-name-in-module from qutebrowser.config import config, websettings from qutebrowser.config.websettings import AttributeInfo as Attr diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index a756e1a3d..e0483a23a 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -15,8 +15,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# FIXME:qt6 (lint) -# pylint: disable=no-name-in-module """Wrapper over our (QtWebKit) WebView.""" @@ -28,8 +26,10 @@ from typing import cast, Iterable, Optional from qutebrowser.qt.core import pyqtSlot, Qt, QUrl, QPoint, QTimer, QSizeF, QSize from qutebrowser.qt.gui import QIcon from qutebrowser.qt.widgets import QWidget +# pylint: disable=no-name-in-module from qutebrowser.qt.webkitwidgets import QWebPage, QWebFrame from qutebrowser.qt.webkit import QWebSettings, QWebHistory, QWebElement +# pylint: enable=no-name-in-module from qutebrowser.qt.printsupport import QPrinter from qutebrowser.browser import browsertab, shared diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 27429f331..b3b1b7ceb 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -15,9 +15,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# FIXME:qt6 (lint) -# pylint: disable=no-name-in-module - """The main browser widgets.""" import html @@ -28,7 +25,9 @@ from qutebrowser.qt.gui import QDesktopServices from qutebrowser.qt.network import QNetworkReply, QNetworkRequest from qutebrowser.qt.widgets import QFileDialog from qutebrowser.qt.printsupport import QPrintDialog +# pylint: disable=no-name-in-module from qutebrowser.qt.webkitwidgets import QWebPage, QWebFrame +# pylint: enable=no-name-in-module from qutebrowser.config import websettings, config from qutebrowser.browser import pdfjs, shared, downloads, greasemonkey diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 7a08a0736..831b2b689 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -15,14 +15,13 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# FIXME:qt6 (lint) -# pylint: disable=no-name-in-module - """The main browser widgets.""" from qutebrowser.qt.core import pyqtSignal, Qt +# pylint: disable=no-name-in-module from qutebrowser.qt.webkit import QWebSettings from qutebrowser.qt.webkitwidgets import QWebView, QWebPage +# pylint: enable=no-name-in-module from qutebrowser.config import config, stylesheet from qutebrowser.keyinput import modeman diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index e2a15b2c0..bd6d2db26 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 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/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/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 . -# FIXME:qt6 (lint) -# pylint: disable=no-name-in-module - """Tests for webelement.tabhistory.""" import dataclasses @@ -26,7 +23,9 @@ from typing import Any import pytest pytest.importorskip('qutebrowser.qt.webkit') from qutebrowser.qt.core import QUrl, QPoint +# pylint: disable=no-name-in-module from qutebrowser.qt.webkit import QWebHistory +# pylint: enable=no-name-in-module from qutebrowser.browser.webkit import tabhistory from qutebrowser.misc.sessions import TabHistoryItem as Item diff --git a/tests/unit/javascript/test_js_execution.py b/tests/unit/javascript/test_js_execution.py index 542b56975..fd2469148 100644 --- a/tests/unit/javascript/test_js_execution.py +++ b/tests/unit/javascript/test_js_execution.py @@ -15,9 +15,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# FIXME:qt6 (lint) -# pylint: disable=no-name-in-module - """Check how Qt behaves when trying to execute JS.""" @@ -29,7 +26,7 @@ def test_simple_js_webkit(webview, js_enabled, expected): """With QtWebKit, evaluateJavaScript works when JS is on.""" # If we get there (because of the webview fixture) we can be certain # QtWebKit is available - from qutebrowser.qt.webkit import QWebSettings + from qutebrowser.qt.webkit import QWebSettings # pylint: disable=no-name-in-module webview.settings().setAttribute(QWebSettings.WebAttribute.JavascriptEnabled, js_enabled) result = webview.page().mainFrame().evaluateJavaScript('1 + 1') assert result == expected @@ -40,7 +37,7 @@ def test_element_js_webkit(webview, js_enabled, expected): """With QtWebKit, evaluateJavaScript on an element works with JS off.""" # If we get there (because of the webview fixture) we can be certain # QtWebKit is available - from qutebrowser.qt.webkit import QWebSettings + from qutebrowser.qt.webkit import QWebSettings # pylint: disable=no-name-in-module webview.settings().setAttribute(QWebSettings.WebAttribute.JavascriptEnabled, js_enabled) elem = webview.page().mainFrame().documentElement() result = elem.evaluateJavaScript('1 + 1') diff --git a/tox.ini b/tox.ini index a96c155f4..db639c129 100644 --- a/tox.ini +++ b/tox.ini @@ -11,10 +11,10 @@ minversion = 3.20 [testenv] setenv = - PYTEST_QT_API=pyqt5 - QUTE_QT_WRAPPER=PyQt5 - pyqt{62,63,64,65}: PYTEST_QT_API=pyqt6 - pyqt{62,63,64,65}: QUTE_QT_WRAPPER=PyQt6 + PYTEST_QT_API=pyqt6 + QUTE_QT_WRAPPER=PyQt6 + pyqt{515,5152}: PYTEST_QT_API=pyqt5 + pyqt{515,5152}: QUTE_QT_WRAPPER=PyQt5 cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report= py312: VIRTUALENV_PIP=23.1.2 py312: PIP_REQUIRE_VIRTUALENV=0 @@ -110,6 +110,7 @@ deps = -r{toxinidir}/misc/requirements/requirements-tests.txt -r{toxinidir}/misc/requirements/requirements-pylint.txt -r{toxinidir}/misc/requirements/requirements-pyqt.txt + -r{toxinidir}/misc/requirements/requirements-pyqt-5.txt commands = {envpython} -m pylint scripts qutebrowser --output-format=colorized --reports=no {posargs} {envpython} scripts/dev/run_pylint_on_tests.py {toxinidir} --output-format=colorized --reports=no {posargs} @@ -178,19 +179,19 @@ commands = {envpython} scripts/dev/check_doc_changes.py {posargs} {envpython} scripts/asciidoc2html.py {posargs} -[testenv:pyinstaller-{64bit,32bit}{,-qt6}] +[testenv:pyinstaller-{64bit,32bit}{,-qt5}] basepython = {env:PYTHON:python3} passenv = APPDATA HOME PYINSTALLER_DEBUG setenv = - qt6: PYINSTALLER_QT6=true + qt5: PYINSTALLER_QT5=true deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-pyinstaller.txt - !qt6: -r{toxinidir}/misc/requirements/requirements-pyqt.txt - qt6: -r{toxinidir}/misc/requirements/requirements-pyqt-6.txt + !qt5: -r{toxinidir}/misc/requirements/requirements-pyqt-6.txt + qt5: -r{toxinidir}/misc/requirements/requirements-pyqt-5.txt commands = {envbindir}/pyinstaller --noconfirm misc/qutebrowser.spec @@ -261,21 +262,21 @@ deps = commands = {envpython} -m sphinx -jauto -W --color {posargs} {toxinidir}/doc/extapi/ {toxinidir}/doc/extapi/_build/ -[testenv:build-release{,-qt6}] +[testenv:build-release{,-qt5}] basepython = {env:PYTHON:python3} passenv = * -# Override default PyQt5 from [testenv] +# Override default PyQt6 from [testenv] setenv = - qt6: QUTE_QT_WRAPPER=PyQt6 + qt5: QUTE_QT_WRAPPER=PyQt5 usedevelop = true deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-tox.txt -r{toxinidir}/misc/requirements/requirements-docs.txt - !qt6: -r{toxinidir}/misc/requirements/requirements-pyqt.txt - qt6: -r{toxinidir}/misc/requirements/requirements-pyqt-6.txt + !qt5: -r{toxinidir}/misc/requirements/requirements-pyqt.txt + qt5: -r{toxinidir}/misc/requirements/requirements-pyqt-5.txt -r{toxinidir}/misc/requirements/requirements-dev.txt -r{toxinidir}/misc/requirements/requirements-pyinstaller.txt commands = - !qt6: {envpython} {toxinidir}/scripts/dev/build_release.py {posargs} - qt6: {envpython} {toxinidir}/scripts/dev/build_release.py --qt6 {posargs} + !qt5: {envpython} {toxinidir}/scripts/dev/build_release.py {posargs} + qt5: {envpython} {toxinidir}/scripts/dev/build_release.py --qt5 {posargs} -- cgit v1.2.3-54-g00ecf From 58ee4cc21a4344ec0017b11e533f97956076df6a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 24 Jun 2023 01:03:10 +0200 Subject: qt6: Add a Qt 5 warning Closes #7742 --- qutebrowser/app.py | 8 ++++++++ qutebrowser/browser/qutescheme.py | 7 +++++++ qutebrowser/html/warning-qt5.html | 28 ++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 qutebrowser/html/warning-qt5.html (limited to 'qutebrowser') 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/html/warning-qt5.html b/qutebrowser/html/warning-qt5.html new file mode 100644 index 000000000..17af2f72c --- /dev/null +++ b/qutebrowser/html/warning-qt5.html @@ -0,0 +1,28 @@ +{% extends "styled.html" %} + +{% block content %} +

{{ title }}

+Note this warning will only appear once. Use :open +qute://warning/qt5 to show it again at a later time. + +

+ 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 %} -- cgit v1.2.3-54-g00ecf From b5d5c7f4d35e624abb30a1d585d30596637bea94 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 24 Jun 2023 12:01:14 +0200 Subject: More qt 6 tooling --- .github/workflows/ci.yml | 16 ++++++++-------- qutebrowser/qt/machinery.py | 1 + scripts/link_pyqt.py | 2 +- tox.ini | 6 +++--- 4 files changed, 13 insertions(+), 12 deletions(-) (limited to 'qutebrowser') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64dddd2f8..a6b894186 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: diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index 1269da4c1..3fdb4efb4 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -178,6 +178,7 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo: return SelectionInfo(wrapper=env_wrapper, reason=SelectionReason.env) if _WRAPPER_OVERRIDE is not None: + pass # type: ignore[unreachable] assert _WRAPPER_OVERRIDE in WRAPPERS, _WRAPPER_OVERRIDE return SelectionInfo(wrapper=_WRAPPER_OVERRIDE, reason=SelectionReason.override) 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/tox.ini b/tox.ini index db639c129..aacfc965e 100644 --- a/tox.ini +++ b/tox.ini @@ -56,10 +56,10 @@ commands = {envpython} -bb -m pytest {posargs:tests} cov: {envpython} scripts/dev/check_coverage.py {posargs} -[testenv:py-qt6] +[testenv:py-qt5] setenv = - PYTEST_QT_API=pyqt6 - QUTE_QT_WRAPPER=PyQt6 + PYTEST_QT_API=pyqt5 + QUTE_QT_WRAPPER=PyQt5 [testenv:bleeding] basepython = {env:PYTHON:python3} -- cgit v1.2.3-54-g00ecf From 342cd9585ad3b7a3dbb6a205e705b8984ea4bb72 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 24 Jun 2023 20:14:53 +0200 Subject: Fix mypy --- qutebrowser/qt/machinery.py | 3 +-- tests/unit/test_qt_machinery.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'qutebrowser') diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index 3fdb4efb4..c737fa96f 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -178,8 +178,7 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo: return SelectionInfo(wrapper=env_wrapper, reason=SelectionReason.env) if _WRAPPER_OVERRIDE is not None: - pass # type: ignore[unreachable] - assert _WRAPPER_OVERRIDE in WRAPPERS, _WRAPPER_OVERRIDE + assert _WRAPPER_OVERRIDE in WRAPPERS # type: ignore[unreachable] return SelectionInfo(wrapper=_WRAPPER_OVERRIDE, reason=SelectionReason.override) return _autoselect_wrapper() diff --git a/tests/unit/test_qt_machinery.py b/tests/unit/test_qt_machinery.py index b144af030..e455ba9ca 100644 --- a/tests/unit/test_qt_machinery.py +++ b/tests/unit/test_qt_machinery.py @@ -380,7 +380,7 @@ class TestSelectWrapper: def test_invalid_override(self, monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr(machinery, "_WRAPPER_OVERRIDE", "invalid") - with pytest.raises(AssertionError, match="invalid"): + with pytest.raises(AssertionError): machinery._select_wrapper(args=None) -- cgit v1.2.3-54-g00ecf From 7a2cee44b18a7233975177c1416877aabd21ef21 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 25 Jun 2023 23:55:19 +0200 Subject: qt6: Keep pylint import order checks disabled --- .pylintrc | 4 ++-- qutebrowser/qutebrowser.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'qutebrowser') diff --git a/.pylintrc b/.pylintrc index 9f9a0c8a0..341bbe8cb 100644 --- a/.pylintrc +++ b/.pylintrc @@ -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/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 -- cgit v1.2.3-54-g00ecf From fb4e3722bb2ba1ba38a4d1eb581d426e1156fb33 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Jun 2023 00:01:25 +0200 Subject: qt 6: Document purpose of wrappers --- qutebrowser/qt/machinery.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'qutebrowser') diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index c737fa96f..39d8661bf 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), -- cgit v1.2.3-54-g00ecf From a05579d255ece25fbdb50de9d5919d74b5c76fd7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Jun 2023 21:08:20 +0200 Subject: qt6: Update selection comment --- qutebrowser/browser/webengine/webenginetab.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'qutebrowser') 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) -- cgit v1.2.3-54-g00ecf From 37791422bf670c41ebc8e7c9f0f5fb4e7e0a213a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Jun 2023 22:18:10 +0200 Subject: qt6: Make sure KeyInfo never has ints as members This used to be possible in some situations and was handled in somewhat unexpected places (e.g. .to_qt()). Instead, we now assume that KeyInfo is always "clean", and we handle the conversion from an int to a Qt.Key elsewhere. This only seems to affect tests, since otherwise we already made sure we get a Qt.Key and Qt.KeyboardModifier(s) e.g. in .from_event(). --- qutebrowser/keyinput/keyutils.py | 26 ++++++++++---------------- tests/unit/keyinput/key_data.py | 3 ++- tests/unit/keyinput/test_keyutils.py | 33 ++++++++++++++++++++++++--------- 3 files changed, 36 insertions(+), 26 deletions(-) (limited to 'qutebrowser') diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index bd6d2db26..10f4d5378 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -347,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) @@ -367,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) @@ -486,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/tests/unit/keyinput/key_data.py b/tests/unit/keyinput/key_data.py index 3826d3ee9..5f151704a 100644 --- a/tests/unit/keyinput/key_data.py +++ b/tests/unit/keyinput/key_data.py @@ -24,6 +24,7 @@ import dataclasses from typing import Optional from qutebrowser.qt.core import Qt +from qutebrowser.keyinput import keyutils @dataclasses.dataclass(order=True) @@ -606,7 +607,7 @@ KEYS = [ Key('unknown', 'Unknown', qtest=False), # 0x0 is used by Qt for unknown keys... - Key(attribute='', name='nil', member=0x0, qtest=False), + Key(attribute='', name='nil', member=keyutils._NIL_KEY, qtest=False), ] diff --git a/tests/unit/keyinput/test_keyutils.py b/tests/unit/keyinput/test_keyutils.py index 2c0740c20..c3b6dc236 100644 --- a/tests/unit/keyinput/test_keyutils.py +++ b/tests/unit/keyinput/test_keyutils.py @@ -31,6 +31,16 @@ from qutebrowser.keyinput import keyutils from qutebrowser.utils import utils +pyqt_enum_workaround_skip = pytest.mark.skipif( + isinstance(keyutils._NIL_KEY, int), + reason="Can't create QKey for unknown keys with this PyQt version" +) +try: + OE_KEY = Qt.Key(ord('Œ')) +except ValueError: + OE_KEY = None # affected tests skipped + + @pytest.fixture(params=key_data.KEYS, ids=lambda k: k.attribute) def qt_key(request): """Get all existing keys from key_data.py. @@ -156,10 +166,14 @@ class TestKeyToString: (Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.MetaModifier | Qt.KeyboardModifier.ShiftModifier, ''), - (ord('Œ'), Qt.KeyboardModifier.NoModifier, '<Œ>'), - (ord('Œ'), Qt.KeyboardModifier.ShiftModifier, ''), - (ord('Œ'), Qt.KeyboardModifier.GroupSwitchModifier, ''), - (ord('Œ'), Qt.KeyboardModifier.GroupSwitchModifier | Qt.KeyboardModifier.ShiftModifier, ''), + + pytest.param(OE_KEY, Qt.KeyboardModifier.NoModifier, '<Œ>', + marks=pyqt_enum_workaround_skip), + pytest.param(OE_KEY, Qt.KeyboardModifier.ShiftModifier, '', + marks=pyqt_enum_workaround_skip), + pytest.param(OE_KEY, Qt.KeyboardModifier.GroupSwitchModifier, '', + marks=pyqt_enum_workaround_skip), + pytest.param(OE_KEY, Qt.KeyboardModifier.GroupSwitchModifier | Qt.KeyboardModifier.ShiftModifier, ''), (Qt.Key.Key_Shift, Qt.KeyboardModifier.ShiftModifier, ''), (Qt.Key.Key_Shift, Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.ControlModifier, ''), @@ -212,10 +226,10 @@ def test_surrogates(key, modifiers, text, expected, pyqt_enum_workaround): ([Qt.Key.Key_Shift, 0x29df6], '<𩷶>'), ([0x1f468, 0x200d, 0x1f468, 0x200d, 0x1f466], '<👨><‍><👨><‍><👦>'), ]) -def test_surrogate_sequences(keys, expected, pyqt_enum_workaround): - infos = [keyutils.KeyInfo(key) for key in keys] - with pyqt_enum_workaround(keyutils.KeyParseError): - seq = keyutils.KeySequence(*infos) +@pyqt_enum_workaround_skip +def test_surrogate_sequences(keys, expected): + infos = [keyutils.KeyInfo(Qt.Key(key)) for key in keys] + seq = keyutils.KeySequence(*infos) assert str(seq) == expected @@ -590,7 +604,8 @@ def test_key_info_to_qt(): (Qt.Key.Key_Return, False), (Qt.Key.Key_Enter, False), (Qt.Key.Key_Space, False), - (0x0, False), # Used by Qt for unknown keys + # Used by Qt for unknown keys + pytest.param(keyutils._NIL_KEY, False, marks=pyqt_enum_workaround_skip), (Qt.Key.Key_ydiaeresis, True), (Qt.Key.Key_X, True), -- cgit v1.2.3-54-g00ecf From 4e67a1727a4e3babbae1320068d15a6cd2b3f4fb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 30 Jun 2023 15:46:31 +0200 Subject: Fix lint/test issues --- qutebrowser/qt/_core_pyqtproperty.py | 2 +- tests/unit/keyinput/test_basekeyparser.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'qutebrowser') diff --git a/qutebrowser/qt/_core_pyqtproperty.py b/qutebrowser/qt/_core_pyqtproperty.py index ec57d4d1c..bdf7013e2 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 QObjectT, pyqtSignal diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index 52e0a01df..0ae0702e9 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -171,7 +171,7 @@ class TestHandle: assert not prompt_keyparser._count def test_invalid_key(self, prompt_keyparser): - keys = [Qt.Key.Key_B, 0x0] + keys = [Qt.Key.Key_B, keyutils._NIL_KEY] for key in keys: info = keyutils.KeyInfo(key, Qt.KeyboardModifier.NoModifier) prompt_keyparser.handle(info.to_event()) -- cgit v1.2.3-54-g00ecf