Merge remote-tracking branch 'origin/pr/7789'

This commit is contained in:
Florian Bruhin 2023-07-22 12:36:31 +02:00
commit 273230eb07
42 changed files with 462 additions and 334 deletions

View File

@ -56,7 +56,8 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
[[ ${{ matrix.testenv }} == eslint ]] && npm install -g eslint [[ ${{ 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 if [[ ${{ matrix.testenv }} == shellcheck ]]; then
scversion="stable" scversion="stable"
bindir="$HOME/.local/bin" bindir="$HOME/.local/bin"
@ -89,17 +90,16 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- testenv: py - testenv: py-qt5
image: archlinux-webkit image: archlinux-webkit
- testenv: py - testenv: py-qt5
image: archlinux-webengine image: archlinux-webengine
- testenv: py-qt6 - testenv: py-qt5
image: archlinux-webengine-unstable
- testenv: py
image: archlinux-webengine-qt6 image: archlinux-webengine-qt6
- testenv: py - testenv: py
image: archlinux-webengine-unstable image: archlinux-webengine-unstable-qt6
args: ""
# - testenv: py
# image: archlinux-webengine-unstable-qt6 # FIXME:qt6.5 activate
container: container:
image: "qutebrowser/ci:${{ matrix.image }}" image: "qutebrowser/ci:${{ matrix.image }}"
env: env:
@ -115,9 +115,9 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Set up problem matchers - 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 - name: Run tox
run: "dbus-run-session -- tox -e ${{ matrix.testenv }} -- ${{ matrix.args }}" run: "dbus-run-session -- tox -e ${{ matrix.testenv }}"
tests: tests:
if: "!contains(github.event.head_commit.message, '[ci skip]')" if: "!contains(github.event.head_commit.message, '[ci skip]')"

View File

@ -15,6 +15,7 @@ jobs:
- archlinux-webkit - archlinux-webkit
- archlinux-webengine - archlinux-webengine
- archlinux-webengine-unstable - archlinux-webengine-unstable
- archlinux-webengine-unstable-qt6
- archlinux-webengine-qt6 - archlinux-webengine-qt6
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -16,50 +16,50 @@ jobs:
include: include:
- os: macos-11 - os: macos-11
branch: master 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 toxenv: build-release
name: macos name: macos
- os: windows-2019 - os: windows-2019
args: --64bit args: --64bit
branch: master
toxenv: build-release toxenv: build-release
name: windows-64bit name: windows-64bit
- os: windows-2019
args: --32bit
branch: master
toxenv: build-release
name: windows-32bit
- os: macos-11 - os: macos-11
args: --debug args: --debug
branch: master
toxenv: build-release toxenv: build-release
name: macos-debug name: macos-debug
- os: windows-2019 - os: windows-2019
args: --64bit --debug args: --64bit --debug
branch: master
toxenv: build-release toxenv: build-release
name: windows-64bit-debug 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 }}" runs-on: "${{ matrix.os }}"
timeout-minutes: 45 timeout-minutes: 45
steps: steps:

View File

@ -1,6 +1,6 @@
[MASTER] [MASTER]
ignore=resources.py ignore=resources.py
extension-pkg-whitelist=PyQt5,sip extension-pkg-whitelist=PyQt5,PyQt6,sip
load-plugins=qute_pylint.config, load-plugins=qute_pylint.config,
pylint.extensions.docstyle, pylint.extensions.docstyle,
pylint.extensions.emptystring, pylint.extensions.emptystring,
@ -58,8 +58,8 @@ disable=locally-disabled,
missing-type-doc, missing-type-doc,
missing-param-doc, missing-param-doc,
useless-param-doc, useless-param-doc,
wrong-import-order, # FIXME:qt6 (lint) wrong-import-order, # doesn't work with qutebrowser.qt, even with known-third-party set
ungrouped-imports, # FIXME:qt6 (lint) ungrouped-imports, # ditto
[BASIC] [BASIC]
function-rgx=[a-z_][a-z0-9_]{2,50}$ function-rgx=[a-z_][a-z0-9_]{2,50}$

View File

@ -19,6 +19,17 @@ breaking changes (such as renamed commands) can happen in minor releases.
v3.0.0 (unreleased) 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 Added
~~~~~ ~~~~~

View File

@ -82,7 +82,7 @@ def get_data_files():
def get_hidden_imports(): 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(): for info in loader.walk_components():
imports.append(info.name) imports.append(info.name)
return imports return imports

View File

@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.15.9 PyQt6==6.5.1
PyQt5-Qt5==5.15.2 PyQt6-Qt6==6.5.1
PyQt5-sip==12.12.1 PyQt6-sip==13.5.1
PyQtWebEngine==5.15.6 PyQt6-WebEngine==6.5.0
PyQtWebEngine-Qt5==5.15.2 PyQt6-WebEngine-Qt6==6.5.1

View File

@ -1,2 +1,4 @@
PyQt5 PyQt6
PyQtWebEngine PyQt6-Qt6
PyQt6-WebEngine
PyQt6-WebEngine-Qt6

View File

@ -41,7 +41,7 @@ from json import dumps
from os import environ, path from os import environ, path
from sys import argv, exit 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 import get, post
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
@ -54,7 +54,7 @@ def get_text(name, info):
None, None,
"add-nextcloud-bookmarks userscript", "add-nextcloud-bookmarks userscript",
"Please enter {}".format(info), "Please enter {}".format(info),
QLineEdit.Password, QLineEdit.EchoMode.Password,
) )
else: else:
text, ok = QInputDialog.getText( text, ok = QInputDialog.getText(

View File

@ -37,7 +37,7 @@ import configparser
from os import environ, path from os import environ, path
from sys import argv, exit 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 import post
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
@ -50,7 +50,7 @@ def get_text(name, info):
None, None,
"add-nextcloud-cookbook userscript", "add-nextcloud-cookbook userscript",
"Please enter {}".format(info), "Please enter {}".format(info),
QLineEdit.Password, QLineEdit.EchoMode.Password,
) )
else: else:
text, ok = QInputDialog.getText( text, ok = QInputDialog.getText(

View File

@ -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 If keepass takes a while to open the DB, you might want to consider reducing
the number of transform rounds in your database settings. 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. exit code of 100.
********************!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!****************** ********************!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!******************
@ -64,8 +64,8 @@ import shlex
import subprocess import subprocess
import sys import sys
from PyQt5.QtCore import QUrl from PyQt6.QtCore import QUrl
from PyQt5.QtWidgets import QApplication, QInputDialog, QLineEdit from PyQt6.QtWidgets import QApplication, QInputDialog, QLineEdit
try: try:
import pykeepass import pykeepass
@ -152,7 +152,7 @@ def get_password():
text, ok = QInputDialog.getText( text, ok = QInputDialog.getText(
None, "KeePass DB Password", None, "KeePass DB Password",
"Please enter your KeePass Master Password", "Please enter your KeePass Master Password",
QLineEdit.Password) QLineEdit.EchoMode.Password)
if not ok: if not ok:
stderr('Password Prompt Rejected.') stderr('Password Prompt Rejected.')
sys.exit(ExitCodes.USER_QUIT) sys.exit(ExitCodes.USER_QUIT)

View File

@ -367,6 +367,14 @@ def _open_special_pages(args):
os.environ.get("QTWEBENGINE_DISABLE_SANDBOX") == "1" os.environ.get("QTWEBENGINE_DISABLE_SANDBOX") == "1"
), ),
'qute://warning/sandboxing'), '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: if 'quickstart-done' not in general_sect:

View File

@ -22,6 +22,7 @@ Module attributes:
_HANDLERS: The handlers registered via decorators. _HANDLERS: The handlers registered via decorators.
""" """
import sys
import html import html
import json import json
import os import os
@ -583,6 +584,12 @@ def qute_warning(url: QUrl) -> _HandlerRet:
elif path == '/sandboxing': elif path == '/sandboxing':
src = jinja.render('warning-sandboxing.html', src = jinja.render('warning-sandboxing.html',
title='Qt 6 macOS sandboxing warning') 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: else:
raise NotFoundError("Invalid warning page {}".format(path)) raise NotFoundError("Invalid warning page {}".format(path))
return 'text/html', src return 'text/html', src

View File

@ -400,7 +400,8 @@ class WebEngineCaret(browsertab.AbstractCaret):
# https://bugreports.qt.io/browse/QTBUG-53134 # https://bugreports.qt.io/browse/QTBUG-53134
# Even on Qt 5.10 selectedText() seems to work poorly, see # Even on Qt 5.10 selectedText() seems to work poorly, see
# https://github.com/qutebrowser/qutebrowser/issues/3523 # 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'), self._tab.run_js_async(javascript.assemble('caret', 'getSelection'),
callback) callback)

View File

@ -15,16 +15,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# FIXME:qt6 (lint)
# pylint: disable=no-name-in-module
"""QtWebKit specific part of the web element API.""" """QtWebKit specific part of the web element API."""
from typing import cast, TYPE_CHECKING, Iterator, List, Optional, Set from typing import cast, TYPE_CHECKING, Iterator, List, Optional, Set
from qutebrowser.qt.core import QRect, Qt from qutebrowser.qt.core import QRect, Qt
# pylint: disable=no-name-in-module
from qutebrowser.qt.webkit import QWebElement, QWebSettings from qutebrowser.qt.webkit import QWebElement, QWebSettings
from qutebrowser.qt.webkitwidgets import QWebFrame from qutebrowser.qt.webkitwidgets import QWebFrame
# pylint: enable=no-name-in-module
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import log, utils, javascript, usertypes from qutebrowser.utils import log, utils, javascript, usertypes

View File

@ -15,14 +15,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# FIXME:qt6 (lint)
# pylint: disable=no-name-in-module
"""QtWebKit specific part of history.""" """QtWebKit specific part of history."""
import functools import functools
# pylint: disable=no-name-in-module
from qutebrowser.qt.webkit import QWebHistoryInterface from qutebrowser.qt.webkit import QWebHistoryInterface
# pylint: enable=no-name-in-module
from qutebrowser.utils import debug from qutebrowser.utils import debug
from qutebrowser.misc import debugcachestats from qutebrowser.misc import debugcachestats

View File

@ -15,13 +15,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# FIXME:qt6 (lint)
# pylint: disable=no-name-in-module
"""Customized QWebInspector for QtWebKit.""" """Customized QWebInspector for QtWebKit."""
# pylint: disable=no-name-in-module
from qutebrowser.qt.webkit import QWebSettings from qutebrowser.qt.webkit import QWebSettings
from qutebrowser.qt.webkitwidgets import QWebInspector, QWebPage from qutebrowser.qt.webkitwidgets import QWebInspector, QWebPage
# pylint: enable=no-name-in-module
from qutebrowser.qt.widgets import QWidget from qutebrowser.qt.widgets import QWidget
from qutebrowser.browser import inspector from qutebrowser.browser import inspector

View File

@ -15,9 +15,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# FIXME:qt6 (lint)
# pylint: disable=no-name-in-module
"""Bridge from QWebSettings to our own settings. """Bridge from QWebSettings to our own settings.
Module attributes: Module attributes:
@ -30,8 +27,10 @@ import os.path
from qutebrowser.qt.core import QUrl from qutebrowser.qt.core import QUrl
from qutebrowser.qt.gui import QFont from qutebrowser.qt.gui import QFont
# pylint: disable=no-name-in-module
from qutebrowser.qt.webkit import QWebSettings from qutebrowser.qt.webkit import QWebSettings
from qutebrowser.qt.webkitwidgets import QWebPage from qutebrowser.qt.webkitwidgets import QWebPage
# pylint: enable=no-name-in-module
from qutebrowser.config import config, websettings from qutebrowser.config import config, websettings
from qutebrowser.config.websettings import AttributeInfo as Attr from qutebrowser.config.websettings import AttributeInfo as Attr

View File

@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# FIXME:qt6 (lint)
# pylint: disable=no-name-in-module
"""Wrapper over our (QtWebKit) WebView.""" """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.core import pyqtSlot, Qt, QUrl, QPoint, QTimer, QSizeF, QSize
from qutebrowser.qt.gui import QIcon from qutebrowser.qt.gui import QIcon
from qutebrowser.qt.widgets import QWidget from qutebrowser.qt.widgets import QWidget
# pylint: disable=no-name-in-module
from qutebrowser.qt.webkitwidgets import QWebPage, QWebFrame from qutebrowser.qt.webkitwidgets import QWebPage, QWebFrame
from qutebrowser.qt.webkit import QWebSettings, QWebHistory, QWebElement from qutebrowser.qt.webkit import QWebSettings, QWebHistory, QWebElement
# pylint: enable=no-name-in-module
from qutebrowser.qt.printsupport import QPrinter from qutebrowser.qt.printsupport import QPrinter
from qutebrowser.browser import browsertab, shared from qutebrowser.browser import browsertab, shared

View File

@ -15,9 +15,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# FIXME:qt6 (lint)
# pylint: disable=no-name-in-module
"""The main browser widgets.""" """The main browser widgets."""
import html import html
@ -28,7 +25,9 @@ from qutebrowser.qt.gui import QDesktopServices
from qutebrowser.qt.network import QNetworkReply, QNetworkRequest from qutebrowser.qt.network import QNetworkReply, QNetworkRequest
from qutebrowser.qt.widgets import QFileDialog from qutebrowser.qt.widgets import QFileDialog
from qutebrowser.qt.printsupport import QPrintDialog from qutebrowser.qt.printsupport import QPrintDialog
# pylint: disable=no-name-in-module
from qutebrowser.qt.webkitwidgets import QWebPage, QWebFrame from qutebrowser.qt.webkitwidgets import QWebPage, QWebFrame
# pylint: enable=no-name-in-module
from qutebrowser.config import websettings, config from qutebrowser.config import websettings, config
from qutebrowser.browser import pdfjs, shared, downloads, greasemonkey from qutebrowser.browser import pdfjs, shared, downloads, greasemonkey

View File

@ -15,14 +15,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# FIXME:qt6 (lint)
# pylint: disable=no-name-in-module
"""The main browser widgets.""" """The main browser widgets."""
from qutebrowser.qt.core import pyqtSignal, Qt from qutebrowser.qt.core import pyqtSignal, Qt
# pylint: disable=no-name-in-module
from qutebrowser.qt.webkit import QWebSettings from qutebrowser.qt.webkit import QWebSettings
from qutebrowser.qt.webkitwidgets import QWebView, QWebPage from qutebrowser.qt.webkitwidgets import QWebView, QWebPage
# pylint: enable=no-name-in-module
from qutebrowser.config import config, stylesheet from qutebrowser.config import config, stylesheet
from qutebrowser.keyinput import modeman from qutebrowser.keyinput import modeman

View File

@ -0,0 +1,28 @@
{% extends "styled.html" %}
{% block content %}
<h1>{{ title }}</h1>
<span class="note">Note this warning will only appear once. Use <span class="mono">:open
qute://warning/qt5</span> to show it again at a later time.</span>
<p>
qutebrowser <b>now supports Qt 6</b>.
</p>
<p>
However, in your environment, <b>Qt 6 is not installed</b>. 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).
</p>
{% if is_venv %}
<p>
You are using a virtualenv. If you want to use Qt 6, you need to create a new
virtualenv with PyQt6 installed.
If using <span class="mono">mkvenv.py</span>, <b>rerun the script</b> to create a
new virtualenv with Qt 6.
</p>
{% endif %}
<p>
<span class="note">Python installation prefix: <span class="mono">{{ prefix }}</span></span>
</p>
{% endblock %}

View File

@ -37,9 +37,7 @@ from qutebrowser.qt import machinery
from qutebrowser.qt.core import Qt, QEvent from qutebrowser.qt.core import Qt, QEvent
from qutebrowser.qt.gui import QKeySequence, QKeyEvent from qutebrowser.qt.gui import QKeySequence, QKeyEvent
if machinery.IS_QT6: if machinery.IS_QT6:
# FIXME:qt6 (lint) how come pylint isn't picking this up with both backends from qutebrowser.qt.core import QKeyCombination
# installed?
from qutebrowser.qt.core import QKeyCombination # pylint: disable=no-name-in-module
else: else:
QKeyCombination = None # QKeyCombination was added in Qt 6 QKeyCombination = None # QKeyCombination was added in Qt 6
@ -349,7 +347,7 @@ def _unset_modifier_bits(
https://github.com/python/cpython/issues/105497 https://github.com/python/cpython/issues/105497
""" """
if machinery.IS_QT5: if machinery.IS_QT5:
return cast(_ModifierType, modifiers & ~mask) return Qt.KeyboardModifiers(modifiers & ~mask) # can lose type if it's 0
else: else:
return Qt.KeyboardModifier(modifiers.value & ~mask.value) return Qt.KeyboardModifier(modifiers.value & ~mask.value)
@ -369,11 +367,14 @@ class KeyInfo:
def __post_init__(self) -> None: def __post_init__(self) -> None:
"""Run some validation on the key/modifier values.""" """Run some validation on the key/modifier values."""
# This is mainly useful while porting from Qt 5 to 6. # This changed with Qt 6, and e.g. to_qt() relies on this.
# FIXME:qt6 do we want to remove or keep this (and fix the remaining if machinery.IS_QT5:
# issues) when done? modifier_classes = (Qt.KeyboardModifier, Qt.KeyboardModifiers)
# assert isinstance(self.key, Qt.Key), self.key elif machinery.IS_QT6:
# assert isinstance(self.modifiers, Qt.KeyboardModifier), self.modifiers 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_key(self.key)
_assert_plain_modifier(self.modifiers) _assert_plain_modifier(self.modifiers)
@ -488,16 +489,7 @@ class KeyInfo:
if machinery.IS_QT5: if machinery.IS_QT5:
return int(self.key) | int(self.modifiers) return int(self.key) | int(self.modifiers)
else: else:
try: return QKeyCombination(self.modifiers, self.key)
# 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)
def with_stripped_modifiers(self, modifiers: Qt.KeyboardModifier) -> "KeyInfo": def with_stripped_modifiers(self, modifiers: Qt.KeyboardModifier) -> "KeyInfo":
mods = _unset_modifier_bits(self.modifiers, modifiers) mods = _unset_modifier_bits(self.modifiers, modifiers)

View File

@ -5,7 +5,7 @@ https://github.com/python-qt-tools/PyQt5-stubs/blob/5.15.6.0/PyQt5-stubs/QtCore.
""" """
# flake8: noqa # 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 import typing
from PyQt6.QtCore import QObject, pyqtSignal from PyQt6.QtCore import QObject, pyqtSignal

View File

@ -3,6 +3,16 @@
"""Qt wrapper selection. """Qt wrapper selection.
Contains selection logic and globals for 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), # 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 from qutebrowser.utils import log
# Packagers: Patch the line below to change the default wrapper for Qt 6 packages, e.g.: # Packagers: Patch the line below to enforce a Qt wrapper, e.g.:
# sed -i 's/_DEFAULT_WRAPPER = "PyQt5"/_DEFAULT_WRAPPER = "PyQt6"/' qutebrowser/qt/machinery.py # 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. # Users: Set the QUTE_QT_WRAPPER environment variable to change the default wrapper.
_DEFAULT_WRAPPER = "PyQt5" _WRAPPER_OVERRIDE = None
WRAPPERS = [ WRAPPERS = [
"PyQt6", "PyQt6",
@ -80,6 +90,9 @@ class SelectionReason(enum.Enum):
#: The wrapper was faked/patched out (e.g. in tests). #: The wrapper was faked/patched out (e.g. in tests).
fake = "fake" fake = "fake"
#: The wrapper was overridden by patching _WRAPPER_OVERRIDE.
override = "override"
#: The reason was not set. #: The reason was not set.
unknown = "unknown" unknown = "unknown"
@ -152,7 +165,7 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo:
- If --qt-wrapper is given, use that. - If --qt-wrapper is given, use that.
- Otherwise, if the QUTE_QT_WRAPPER environment variable is set, 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 # If any Qt wrapper has been imported before this, something strange might
# be happening. # be happening.
@ -170,15 +183,17 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo:
if env_wrapper == "auto": if env_wrapper == "auto":
return _autoselect_wrapper() return _autoselect_wrapper()
elif env_wrapper not in WRAPPERS: elif env_wrapper not in WRAPPERS:
raise Error(f"Unknown wrapper {env_wrapper} set via {env_var}, " raise Error(
f"allowed: {', '.join(WRAPPERS)}") f"Unknown wrapper {env_wrapper} set via {env_var}, "
f"allowed: {', '.join(WRAPPERS)}"
)
return SelectionInfo(wrapper=env_wrapper, reason=SelectionReason.env) return SelectionInfo(wrapper=env_wrapper, reason=SelectionReason.env)
# FIXME:qt6 Go back to the auto-detection once ready if _WRAPPER_OVERRIDE is not None:
# FIXME:qt6 Make sure to still consider _DEFAULT_WRAPPER for packagers assert _WRAPPER_OVERRIDE in WRAPPERS # type: ignore[unreachable]
# (rename to _WRAPPER_OVERRIDE since our sed command is broken anyways then?) return SelectionInfo(wrapper=_WRAPPER_OVERRIDE, reason=SelectionReason.override)
# 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 # 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 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. about them via --always-true and --always-false, see tox.ini.
""" """
global INFO, USE_PYQT5, USE_PYQT6, USE_PYSIDE6, IS_QT5, IS_QT6, \ global INFO, USE_PYQT5, USE_PYQT6, USE_PYSIDE6, IS_QT5, IS_QT6, IS_PYQT, IS_PYSIDE, _initialized
IS_PYQT, IS_PYSIDE, _initialized
assert info.wrapper is not None, info assert info.wrapper is not None, info
assert not _initialized assert not _initialized

View File

@ -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. """Wrapped Qt imports for Qt OpenGL.

View File

@ -50,7 +50,7 @@ except ImportError:
sys.exit(100) sys.exit(100)
check_python_version() check_python_version()
import argparse # FIXME:qt6 (lint): disable=wrong-import-order import argparse
from qutebrowser.misc import earlyinit from qutebrowser.misc import earlyinit
from qutebrowser.qt import machinery from qutebrowser.qt import machinery

View File

@ -136,7 +136,7 @@ def _smoke_test_run(
return subprocess.run(argv, check=True, capture_output=True) 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.""" """Try starting the given qutebrowser executable."""
stdout_whitelist = [] stdout_whitelist = []
stderr_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'ContextResult::kTransientFailure: Failed to send '
r'.*CreateCommandBuffer\.'), r'.*CreateCommandBuffer\.'),
]) ])
if qt6: if not qt5:
stderr_whitelist.extend([ stderr_whitelist.extend([
# FIXME:qt6 Qt 6.3 on macOS # FIXME:qt6 Qt 6.3 on macOS
r'[0-9:]* WARNING: Incompatible version of OpenSSL', 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() 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.""" """Patch .app to save some space and make it signable."""
dist_path = pathlib.Path('dist') dist_path = pathlib.Path('dist')
ver = '6' if qt6 else '5' ver = '5' if qt5 else '6'
app_path = dist_path / 'qutebrowser.app' app_path = dist_path / 'qutebrowser.app'
contents_path = app_path / 'Contents' contents_path = app_path / 'Contents'
@ -280,7 +280,7 @@ def patch_mac_app(qt6: bool) -> None:
file_path.unlink() file_path.unlink()
file_path.symlink_to(target) file_path.symlink_to(target)
if qt6: if not qt5:
# Symlinking QtWebEngineCore.framework does not seem to work with Qt 6. # Symlinking QtWebEngineCore.framework does not seem to work with Qt 6.
# Also, the symlinking/moving before signing doesn't seem to be required. # Also, the symlinking/moving before signing doesn't seem to be required.
return return
@ -333,7 +333,7 @@ def _mac_bin_path(base: pathlib.Path) -> pathlib.Path:
def build_mac( def build_mac(
*, *,
gh_token: Optional[str], gh_token: Optional[str],
qt6: bool, qt5: bool,
skip_packaging: bool, skip_packaging: bool,
debug: bool, debug: bool,
) -> List[Artifact]: ) -> List[Artifact]:
@ -348,20 +348,20 @@ def build_mac(
shutil.rmtree(d, ignore_errors=True) shutil.rmtree(d, ignore_errors=True)
utils.print_title("Updating 3rdparty content") 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) gh_token=gh_token)
utils.print_title("Building .app via pyinstaller") 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") utils.print_title("Patching .app")
patch_mac_app(qt6=qt6) patch_mac_app(qt5=qt5)
utils.print_title("Re-signing .app") utils.print_title("Re-signing .app")
sign_mac_app() sign_mac_app()
dist_path = pathlib.Path("dist") dist_path = pathlib.Path("dist")
utils.print_title("Running pre-dmg smoke test") 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: if skip_packaging:
return [] return []
@ -371,7 +371,7 @@ def build_mac(
subprocess.run(['make', '-f', dmg_makefile_path], check=True) subprocess.run(['make', '-f', dmg_makefile_path], check=True)
suffix = "-debug" if debug else "" 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' dmg_path = dist_path / f'qutebrowser-{qutebrowser.__version__}{suffix}.dmg'
pathlib.Path('qutebrowser.dmg').rename(dmg_path) pathlib.Path('qutebrowser.dmg').rename(dmg_path)
@ -383,7 +383,7 @@ def build_mac(
subprocess.run(['hdiutil', 'attach', dmg_path, subprocess.run(['hdiutil', 'attach', dmg_path,
'-mountpoint', tmp_path], check=True) '-mountpoint', tmp_path], check=True)
try: try:
smoke_test(_mac_bin_path(tmp_path), debug=debug, qt6=qt6) smoke_test(_mac_bin_path(tmp_path), debug=debug, qt5=qt5)
finally: finally:
print("Waiting 10s for dmg to be detachable...") print("Waiting 10s for dmg to be detachable...")
time.sleep(10) time.sleep(10)
@ -422,7 +422,7 @@ def _get_windows_python_path(x64: bool) -> pathlib.Path:
def _build_windows_single( def _build_windows_single(
*, x64: bool, *, x64: bool,
qt6: bool, qt5: bool,
skip_packaging: bool, skip_packaging: bool,
debug: bool, debug: bool,
) -> List[Artifact]: ) -> List[Artifact]:
@ -437,9 +437,9 @@ def _build_windows_single(
python = _get_windows_python_path(x64=x64) python = _get_windows_python_path(x64=x64)
suffix = "64bit" if x64 else "32bit" suffix = "64bit" if x64 else "32bit"
if qt6: if qt5:
# FIXME:qt6 does this regress 391623d5ec983ecfc4512c7305c4b7a293ac3872? # FIXME:qt6 does this regress 391623d5ec983ecfc4512c7305c4b7a293ac3872?
suffix += "-qt6" suffix += "-qt5"
call_tox(f'pyinstaller-{suffix}', '-r', python=python, debug=debug) call_tox(f'pyinstaller-{suffix}', '-r', python=python, debug=debug)
out_pyinstaller = dist_path / "qutebrowser" out_pyinstaller = dist_path / "qutebrowser"
@ -450,7 +450,7 @@ def _build_windows_single(
verify_windows_exe(exe_path) verify_windows_exe(exe_path)
utils.print_title(f"Running {human_arch} smoke test") 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: if skip_packaging:
return [] return []
@ -463,7 +463,7 @@ def _build_windows_single(
desc_arch=human_arch, desc_arch=human_arch,
desc_suffix='' if x64 else ' (only for 32-bit Windows!)', desc_suffix='' if x64 else ' (only for 32-bit Windows!)',
debug=debug, debug=debug,
qt6=qt6, qt5=qt5,
) )
@ -472,12 +472,12 @@ def build_windows(
skip_packaging: bool, skip_packaging: bool,
only_32bit: bool, only_32bit: bool,
only_64bit: bool, only_64bit: bool,
qt6: bool, qt5: bool,
debug: bool, debug: bool,
) -> List[Artifact]: ) -> List[Artifact]:
"""Build windows executables/setups.""" """Build windows executables/setups."""
utils.print_title("Updating 3rdparty content") 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) fancy_dmg=False, gh_token=gh_token)
utils.print_title("Building Windows binaries") utils.print_title("Building Windows binaries")
@ -493,14 +493,14 @@ def build_windows(
x64=True, x64=True,
skip_packaging=skip_packaging, skip_packaging=skip_packaging,
debug=debug, debug=debug,
qt6=qt6, qt5=qt5,
) )
if not only_64bit and not qt6: if not only_64bit and not qt5:
artifacts += _build_windows_single( artifacts += _build_windows_single(
x64=False, x64=False,
skip_packaging=skip_packaging, skip_packaging=skip_packaging,
debug=debug, debug=debug,
qt6=qt6, qt5=qt5,
) )
return artifacts return artifacts
@ -514,7 +514,7 @@ def _package_windows_single(
desc_suffix: str, desc_suffix: str,
filename_arch: str, filename_arch: str,
debug: bool, debug: bool,
qt6: bool, qt5: bool,
) -> List[Artifact]: ) -> List[Artifact]:
"""Build the given installer/zip for windows.""" """Build the given installer/zip for windows."""
artifacts = [] artifacts = []
@ -532,8 +532,8 @@ def _package_windows_single(
] ]
if debug: if debug:
name_parts.append('debug') name_parts.append('debug')
if qt6: if qt5:
name_parts.append('qt6') name_parts.append('qt5')
name = '-'.join(name_parts) + '.exe' name = '-'.join(name_parts) + '.exe'
artifacts.append(Artifact( artifacts.append(Artifact(
@ -552,8 +552,8 @@ def _package_windows_single(
] ]
if debug: if debug:
zip_name_parts.append('debug') zip_name_parts.append('debug')
if qt6: if qt5:
zip_name_parts.append('qt6') zip_name_parts.append('qt5')
zip_name = '-'.join(zip_name_parts) + '.zip' zip_name = '-'.join(zip_name_parts) + '.zip'
zip_path = dist_path / zip_name zip_path = dist_path / zip_name
@ -738,8 +738,8 @@ def main() -> None:
help="Skip Windows 32 bit build.", dest='only_64bit') help="Skip Windows 32 bit build.", dest='only_64bit')
parser.add_argument('--debug', action='store_true', required=False, parser.add_argument('--debug', action='store_true', required=False,
help="Build a debug build.") help="Build a debug build.")
parser.add_argument('--qt6', action='store_true', required=False, parser.add_argument('--qt5', action='store_true', required=False,
help="Build against PyQt6") help="Build against PyQt5")
args = parser.parse_args() args = parser.parse_args()
utils.change_cwd() utils.change_cwd()
@ -768,14 +768,14 @@ def main() -> None:
skip_packaging=args.skip_packaging, skip_packaging=args.skip_packaging,
only_32bit=args.only_32bit, only_32bit=args.only_32bit,
only_64bit=args.only_64bit, only_64bit=args.only_64bit,
qt6=args.qt6, qt5=args.qt5,
debug=args.debug, debug=args.debug,
) )
elif IS_MACOS: elif IS_MACOS:
artifacts = build_mac( artifacts = build_mac(
gh_token=gh_token, gh_token=gh_token,
skip_packaging=args.skip_packaging, skip_packaging=args.skip_packaging,
qt6=args.qt6, qt5=args.qt5,
debug=args.debug, debug=args.debug,
) )
else: else:

View File

@ -27,7 +27,7 @@ import subprocess
import tokenize import tokenize
import traceback import traceback
import pathlib import pathlib
from typing import List, Iterator, Optional from typing import List, Iterator, Optional, Tuple
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2] REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT)) sys.path.insert(0, str(REPO_ROOT))
@ -152,6 +152,24 @@ def _check_spelling_file(path, fobj, patterns):
return ok 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]: def check_spelling(args: argparse.Namespace) -> Optional[bool]:
"""Check commonly misspelled words.""" """Check commonly misspelled words."""
# Words which I often misspell # Words which I often misspell
@ -273,25 +291,13 @@ def check_spelling(args: argparse.Namespace) -> Optional[bool]:
hint_data / 'ace' / 'ace.js', hint_data / 'ace' / 'ace.js',
hint_data / 'bootstrap' / 'bootstrap.css', hint_data / 'bootstrap' / 'bootstrap.css',
] ]
return _check_spelling_all(args=args, ignored=ignored, patterns=patterns)
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
def check_pyqt_imports(args: argparse.Namespace) -> Optional[bool]: def check_pyqt_imports(args: argparse.Namespace) -> Optional[bool]:
"""Check for direct PyQt imports.""" """Check for direct PyQt imports."""
ignored = [ ignored = [
pathlib.Path("qutebrowser", "qt"), pathlib.Path("qutebrowser", "qt"),
# FIXME:qt6 fix those too?
pathlib.Path("misc", "userscripts"), pathlib.Path("misc", "userscripts"),
pathlib.Path("scripts"), pathlib.Path("scripts"),
] ]
@ -305,18 +311,7 @@ def check_pyqt_imports(args: argparse.Namespace) -> Optional[bool]:
"Use 'import qutebrowser.qt.MODULE' instead", "Use 'import qutebrowser.qt.MODULE' instead",
) )
] ]
# FIXME:qt6 unify this with check_spelling somehow? return _check_spelling_all(args=args, ignored=ignored, patterns=patterns)
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_vcs_conflict(args: argparse.Namespace) -> Optional[bool]: def check_vcs_conflict(args: argparse.Namespace) -> Optional[bool]:

View File

@ -60,7 +60,6 @@ def whitelist_generator(): # noqa: C901
yield 'qutebrowser.misc.sql.SqliteErrorCode.CONSTRAINT' yield 'qutebrowser.misc.sql.SqliteErrorCode.CONSTRAINT'
yield 'qutebrowser.misc.throttle.Throttle.set_delay' yield 'qutebrowser.misc.throttle.Throttle.set_delay'
yield 'qutebrowser.misc.guiprocess.GUIProcess.stderr' yield 'qutebrowser.misc.guiprocess.GUIProcess.stderr'
yield 'qutebrowser.qt.machinery._autoselect_wrapper' # FIXME:qt6
# Qt attributes # Qt attributes
yield 'PyQt5.QtWebKit.QWebPage.ErrorPageExtensionReturn().baseUrl' yield 'PyQt5.QtWebKit.QWebPage.ErrorPageExtensionReturn().baseUrl'

View File

@ -21,7 +21,7 @@
import os import os
import sys 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) QStandardPaths, QCoreApplication)

View File

@ -21,8 +21,7 @@
Use python3 -m scripts.keytester to launch it. Use python3 -m scripts.keytester to launch it.
""" """
from PyQt5.QtWidgets import QApplication from qutebrowser.qt.widgets import QApplication
from qutebrowser.misc import miscwidgets from qutebrowser.misc import miscwidgets
app = QApplication([]) app = QApplication([])

View File

@ -125,7 +125,7 @@ def get_lib_path(executable, name, required=True):
raise ValueError("Unexpected output: {!r}".format(output)) 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. """Symlink the systemwide PyQt/sip into the venv.
Args: Args:

View File

@ -134,8 +134,7 @@ def pyqt_versions() -> List[str]:
def _is_qt6_version(version: str) -> bool: def _is_qt6_version(version: str) -> bool:
"""Check if the given version is Qt 6.""" """Check if the given version is Qt 6."""
# FIXME:qt6 Adjust once auto = Qt 6 return version in ["auto", "6"] or version.startswith("6.")
return version == "6" or version.startswith("6.")
def run_venv( def run_venv(
@ -228,7 +227,7 @@ def requirements_file(name: str) -> pathlib.Path:
def pyqt_requirements_file(version: str) -> pathlib.Path: def pyqt_requirements_file(version: str) -> pathlib.Path:
"""Get the filename of the requirements file for the given PyQt version.""" """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) 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: def run_qt_smoke_test(venv_dir: pathlib.Path, *, pyqt_version: str) -> None:
"""Make sure the Qt installation works.""" """Make sure the Qt installation works."""
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-104415 # 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: if no_debug:
try: try:
run_qt_smoke_test_single(venv_dir, debug=False, pyqt_version=pyqt_version) 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) install_pyqt_binary(venv_dir, args.pyqt_version)
if args.pyqt_snapshot: if args.pyqt_snapshot:
install_pyqt_shapshot(venv_dir, args.pyqt_snapshot.split(',')) 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': elif args.pyqt_type == 'source':
install_pyqt_source(venv_dir, args.pyqt_version) install_pyqt_source(venv_dir, args.pyqt_version)
elif args.pyqt_type == 'link': elif args.pyqt_type == 'link':

View File

@ -18,8 +18,8 @@
"""Show information about the OpenGL setup.""" """Show information about the OpenGL setup."""
from PyQt5.QtGui import (QOpenGLContext, QOpenGLVersionProfile, from PyQt6.QtGui import QOpenGLContext, QOffscreenSurface, QGuiApplication
QOffscreenSurface, QGuiApplication) from PyQt6.QtOpenGL import QOpenGLVersionProfile, QOpenGLVersionFunctionsFactory
app = QGuiApplication([]) app = QGuiApplication([])
@ -38,7 +38,7 @@ print(f"GLES: {ctx.isOpenGLES()}")
vp = QOpenGLVersionProfile() vp = QOpenGLVersionProfile()
vp.setVersion(2, 0) vp.setVersion(2, 0)
vf = ctx.versionFunctions(vp) vf = QOpenGLVersionFunctionsFactory.get(vp, ctx)
print(f"Vendor: {vf.glGetString(vf.GL_VENDOR)}") print(f"Vendor: {vf.glGetString(vf.GL_VENDOR)}")
print(f"Renderer: {vf.glGetString(vf.GL_RENDERER)}") print(f"Renderer: {vf.glGetString(vf.GL_RENDERER)}")
print(f"Version: {vf.glGetString(vf.GL_VERSION)}") print(f"Version: {vf.glGetString(vf.GL_VERSION)}")

View File

@ -15,9 +15,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# FIXME:qt6 (lint)
# pylint: disable=no-name-in-module
"""Tests for webelement.tabhistory.""" """Tests for webelement.tabhistory."""
import dataclasses import dataclasses
@ -26,7 +23,9 @@ from typing import Any
import pytest import pytest
pytest.importorskip('qutebrowser.qt.webkit') pytest.importorskip('qutebrowser.qt.webkit')
from qutebrowser.qt.core import QUrl, QPoint from qutebrowser.qt.core import QUrl, QPoint
# pylint: disable=no-name-in-module
from qutebrowser.qt.webkit import QWebHistory from qutebrowser.qt.webkit import QWebHistory
# pylint: enable=no-name-in-module
from qutebrowser.browser.webkit import tabhistory from qutebrowser.browser.webkit import tabhistory
from qutebrowser.misc.sessions import TabHistoryItem as Item from qutebrowser.misc.sessions import TabHistoryItem as Item

View File

@ -15,9 +15,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# FIXME:qt6 (lint)
# pylint: disable=no-name-in-module
"""Check how Qt behaves when trying to execute JS.""" """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.""" """With QtWebKit, evaluateJavaScript works when JS is on."""
# If we get there (because of the webview fixture) we can be certain # If we get there (because of the webview fixture) we can be certain
# QtWebKit is available # 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) webview.settings().setAttribute(QWebSettings.WebAttribute.JavascriptEnabled, js_enabled)
result = webview.page().mainFrame().evaluateJavaScript('1 + 1') result = webview.page().mainFrame().evaluateJavaScript('1 + 1')
assert result == expected 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.""" """With QtWebKit, evaluateJavaScript on an element works with JS off."""
# If we get there (because of the webview fixture) we can be certain # If we get there (because of the webview fixture) we can be certain
# QtWebKit is available # 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) webview.settings().setAttribute(QWebSettings.WebAttribute.JavascriptEnabled, js_enabled)
elem = webview.page().mainFrame().documentElement() elem = webview.page().mainFrame().documentElement()
result = elem.evaluateJavaScript('1 + 1') result = elem.evaluateJavaScript('1 + 1')

View File

@ -24,6 +24,7 @@ import dataclasses
from typing import Optional from typing import Optional
from qutebrowser.qt.core import Qt from qutebrowser.qt.core import Qt
from qutebrowser.keyinput import keyutils
@dataclasses.dataclass(order=True) @dataclasses.dataclass(order=True)
@ -606,7 +607,7 @@ KEYS = [
Key('unknown', 'Unknown', qtest=False), Key('unknown', 'Unknown', qtest=False),
# 0x0 is used by Qt for unknown keys... # 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),
] ]

View File

@ -171,7 +171,7 @@ class TestHandle:
assert not prompt_keyparser._count assert not prompt_keyparser._count
def test_invalid_key(self, prompt_keyparser): 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: for key in keys:
info = keyutils.KeyInfo(key, Qt.KeyboardModifier.NoModifier) info = keyutils.KeyInfo(key, Qt.KeyboardModifier.NoModifier)
prompt_keyparser.handle(info.to_event()) prompt_keyparser.handle(info.to_event())

View File

@ -31,6 +31,16 @@ from qutebrowser.keyinput import keyutils
from qutebrowser.utils import utils 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) @pytest.fixture(params=key_data.KEYS, ids=lambda k: k.attribute)
def qt_key(request): def qt_key(request):
"""Get all existing keys from key_data.py. """Get all existing keys from key_data.py.
@ -156,10 +166,14 @@ class TestKeyToString:
(Qt.Key.Key_A, (Qt.Key.Key_A,
Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.MetaModifier | Qt.KeyboardModifier.ShiftModifier, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.MetaModifier | Qt.KeyboardModifier.ShiftModifier,
'<Meta+Ctrl+Alt+Shift+a>'), '<Meta+Ctrl+Alt+Shift+a>'),
(ord('Œ'), Qt.KeyboardModifier.NoModifier, '<Œ>'),
(ord('Œ'), Qt.KeyboardModifier.ShiftModifier, '<Shift+Œ>'), pytest.param(OE_KEY, Qt.KeyboardModifier.NoModifier, '<Œ>',
(ord('Œ'), Qt.KeyboardModifier.GroupSwitchModifier, '<AltGr+Œ>'), marks=pyqt_enum_workaround_skip),
(ord('Œ'), Qt.KeyboardModifier.GroupSwitchModifier | Qt.KeyboardModifier.ShiftModifier, '<AltGr+Shift+Œ>'), pytest.param(OE_KEY, Qt.KeyboardModifier.ShiftModifier, '<Shift+Œ>',
marks=pyqt_enum_workaround_skip),
pytest.param(OE_KEY, Qt.KeyboardModifier.GroupSwitchModifier, '<AltGr+Œ>',
marks=pyqt_enum_workaround_skip),
pytest.param(OE_KEY, Qt.KeyboardModifier.GroupSwitchModifier | Qt.KeyboardModifier.ShiftModifier, '<AltGr+Shift+Œ>'),
(Qt.Key.Key_Shift, Qt.KeyboardModifier.ShiftModifier, '<Shift>'), (Qt.Key.Key_Shift, Qt.KeyboardModifier.ShiftModifier, '<Shift>'),
(Qt.Key.Key_Shift, Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.ControlModifier, '<Ctrl+Shift>'), (Qt.Key.Key_Shift, Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.ControlModifier, '<Ctrl+Shift>'),
@ -212,10 +226,10 @@ def test_surrogates(key, modifiers, text, expected, pyqt_enum_workaround):
([Qt.Key.Key_Shift, 0x29df6], '<Shift><𩷶>'), ([Qt.Key.Key_Shift, 0x29df6], '<Shift><𩷶>'),
([0x1f468, 0x200d, 0x1f468, 0x200d, 0x1f466], '<👨><><👨><><👦>'), ([0x1f468, 0x200d, 0x1f468, 0x200d, 0x1f466], '<👨><><👨><><👦>'),
]) ])
def test_surrogate_sequences(keys, expected, pyqt_enum_workaround): @pyqt_enum_workaround_skip
infos = [keyutils.KeyInfo(key) for key in keys] def test_surrogate_sequences(keys, expected):
with pyqt_enum_workaround(keyutils.KeyParseError): infos = [keyutils.KeyInfo(Qt.Key(key)) for key in keys]
seq = keyutils.KeySequence(*infos) seq = keyutils.KeySequence(*infos)
assert str(seq) == expected assert str(seq) == expected
@ -590,7 +604,8 @@ def test_key_info_to_qt():
(Qt.Key.Key_Return, False), (Qt.Key.Key_Return, False),
(Qt.Key.Key_Enter, False), (Qt.Key.Key_Enter, False),
(Qt.Key.Key_Space, 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_ydiaeresis, True),
(Qt.Key.Key_X, True), (Qt.Key.Key_X, True),

View File

@ -23,6 +23,7 @@ import html
import argparse import argparse
import typing import typing
from typing import Any, Optional, List, Dict, Union from typing import Any, Optional, List, Dict, Union
import dataclasses
import pytest import pytest
@ -214,7 +215,7 @@ def modules():
reason=machinery.SelectionReason.auto, reason=machinery.SelectionReason.auto,
outcomes={ outcomes={
"PyQt6": "ImportError: Fake ImportError for PyQt6.", "PyQt6": "ImportError: Fake ImportError for PyQt6.",
} },
), ),
id="import-error", id="import-error",
), ),
@ -230,111 +231,157 @@ def test_autoselect(
assert machinery._autoselect_wrapper() == expected assert machinery._autoselect_wrapper() == expected
@pytest.mark.parametrize( @dataclasses.dataclass
"args, env, expected", class SelectWrapperCase:
[ name: str
# Defaults with no overrides expected: machinery.SelectionInfo
( args: Optional[argparse.Namespace] = None
None, env: Optional[str] = None
None, override: Optional[str] = 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
),
),
(
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
),
),
# Only environment variable given
(
None,
"PyQt6",
machinery.SelectionInfo(
wrapper="PyQt6", 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
),
),
(
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
),
),
],
)
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)
assert machinery._select_wrapper(args) == expected def __str__(self):
return self.name
def test_select_wrapper_after_qt_import(monkeypatch: pytest.MonkeyPatch): class TestSelectWrapper:
monkeypatch.setitem(sys.modules, "PyQt6", None) @pytest.mark.parametrize(
with pytest.warns(UserWarning, match="PyQt6 already imported"): "tc",
machinery._select_wrapper(args=None) [
# Only argument given
SelectWrapperCase(
"pyqt6-arg",
args=argparse.Namespace(qt_wrapper="PyQt6"),
expected=machinery.SelectionInfo(
wrapper="PyQt6", reason=machinery.SelectionReason.cli
),
),
SelectWrapperCase(
"pyqt5-arg",
args=argparse.Namespace(qt_wrapper="PyQt5"),
expected=machinery.SelectionInfo(
wrapper="PyQt5", reason=machinery.SelectionReason.cli
),
),
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
SelectWrapperCase(
"pyqt6-env",
env="PyQt6",
expected=machinery.SelectionInfo(
wrapper="PyQt6", reason=machinery.SelectionReason.env
),
),
SelectWrapperCase(
"pyqt5-env",
env="PyQt5",
expected=machinery.SelectionInfo(
wrapper="PyQt5", reason=machinery.SelectionReason.env
),
),
# Both given
SelectWrapperCase(
"pyqt5-arg-pyqt6-env",
args=argparse.Namespace(qt_wrapper="PyQt5"),
env="PyQt6",
expected=machinery.SelectionInfo(
wrapper="PyQt5", reason=machinery.SelectionReason.cli
),
),
SelectWrapperCase(
"pyqt6-arg-pyqt5-env",
args=argparse.Namespace(qt_wrapper="PyQt6"),
env="PyQt5",
expected=machinery.SelectionInfo(
wrapper="PyQt6", reason=machinery.SelectionReason.cli
),
),
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, tc: SelectWrapperCase, monkeypatch: pytest.MonkeyPatch):
if tc.env is None:
monkeypatch.delenv("QUTE_QT_WRAPPER", raising=False)
else:
monkeypatch.setenv("QUTE_QT_WRAPPER", tc.env)
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",
[
(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_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)
def test_invalid_override(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(machinery, "_WRAPPER_OVERRIDE", "invalid")
with pytest.raises(AssertionError):
machinery._select_wrapper(args=None)
class TestInit: class TestInit:
@ -359,15 +406,34 @@ class TestInit:
): ):
machinery.init(args=empty_args) machinery.init(args=empty_args)
@pytest.fixture(params=["auto", "", None])
def qt_auto_env(
self,
request: pytest.FixtureRequest,
monkeypatch: pytest.MonkeyPatch,
):
"""Trigger wrapper autoselection via environment variable.
Autoselection should be used in three scenarios:
- The environment variable is set to "auto".
- The environment variable is set to an empty string.
- The environment variable is not set at all.
We run test_none_available_*() for all three scenarios.
"""
if request.param is None:
monkeypatch.delenv("QUTE_QT_WRAPPER", raising=False)
else:
monkeypatch.setenv("QUTE_QT_WRAPPER", request.param)
def test_none_available_implicit( def test_none_available_implicit(
self, self,
stubs: Any, stubs: Any,
modules: Dict[str, bool], modules: Dict[str, bool],
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
undo_init: None, qt_auto_env: None,
): ):
# FIXME:qt6 Also try without this once auto is default
monkeypatch.setenv("QUTE_QT_WRAPPER", "auto")
stubs.ImportFake(modules, monkeypatch).patch() stubs.ImportFake(modules, monkeypatch).patch()
message_lines = [ message_lines = [
@ -391,10 +457,8 @@ class TestInit:
modules: Dict[str, bool], modules: Dict[str, bool],
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
empty_args: argparse.Namespace, empty_args: argparse.Namespace,
undo_init: None, qt_auto_env: None,
): ):
# FIXME:qt6 Also try without this once auto is default
monkeypatch.setenv("QUTE_QT_WRAPPER", "auto")
stubs.ImportFake(modules, monkeypatch).patch() stubs.ImportFake(modules, monkeypatch).patch()
info = machinery.init(args=empty_args) info = machinery.init(args=empty_args)
@ -403,7 +467,7 @@ class TestInit:
reason=machinery.SelectionReason.auto, reason=machinery.SelectionReason.auto,
outcomes={ outcomes={
"PyQt6": "ImportError: Fake ImportError for PyQt6.", "PyQt6": "ImportError: Fake ImportError for PyQt6.",
} },
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -422,7 +486,6 @@ class TestInit:
true_vars: str, true_vars: str,
explicit: bool, explicit: bool,
empty_args: argparse.Namespace, empty_args: argparse.Namespace,
undo_init: None,
): ):
info = machinery.SelectionInfo( info = machinery.SelectionInfo(
wrapper=selected_wrapper, wrapper=selected_wrapper,

41
tox.ini
View File

@ -11,10 +11,10 @@ minversion = 3.20
[testenv] [testenv]
setenv = setenv =
PYTEST_QT_API=pyqt5 PYTEST_QT_API=pyqt6
QUTE_QT_WRAPPER=PyQt5 QUTE_QT_WRAPPER=PyQt6
pyqt{62,63,64,65}: PYTEST_QT_API=pyqt6 pyqt{515,5152}: PYTEST_QT_API=pyqt5
pyqt{62,63,64,65}: QUTE_QT_WRAPPER=PyQt6 pyqt{515,5152}: QUTE_QT_WRAPPER=PyQt5
cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report= cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report=
py312: VIRTUALENV_PIP=23.2 py312: VIRTUALENV_PIP=23.2
py312: PIP_REQUIRE_VIRTUALENV=0 py312: PIP_REQUIRE_VIRTUALENV=0
@ -56,10 +56,10 @@ commands =
{envpython} -bb -m pytest {posargs:tests} {envpython} -bb -m pytest {posargs:tests}
cov: {envpython} scripts/dev/check_coverage.py {posargs} cov: {envpython} scripts/dev/check_coverage.py {posargs}
[testenv:py-qt6] [testenv:py-qt5]
setenv = setenv =
PYTEST_QT_API=pyqt6 PYTEST_QT_API=pyqt5
QUTE_QT_WRAPPER=PyQt6 QUTE_QT_WRAPPER=PyQt5
[testenv:bleeding] [testenv:bleeding]
basepython = {env:PYTHON:python3} basepython = {env:PYTHON:python3}
@ -112,6 +112,7 @@ deps =
-r{toxinidir}/misc/requirements/requirements-tests.txt -r{toxinidir}/misc/requirements/requirements-tests.txt
-r{toxinidir}/misc/requirements/requirements-pylint.txt -r{toxinidir}/misc/requirements/requirements-pylint.txt
-r{toxinidir}/misc/requirements/requirements-pyqt.txt -r{toxinidir}/misc/requirements/requirements-pyqt.txt
-r{toxinidir}/misc/requirements/requirements-pyqt-5.txt
commands = commands =
{envpython} -m pylint scripts qutebrowser --output-format=colorized --reports=no {posargs} {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} {envpython} scripts/dev/run_pylint_on_tests.py {toxinidir} --output-format=colorized --reports=no {posargs}
@ -180,19 +181,19 @@ commands =
{envpython} scripts/dev/check_doc_changes.py {posargs} {envpython} scripts/dev/check_doc_changes.py {posargs}
{envpython} scripts/asciidoc2html.py {posargs} {envpython} scripts/asciidoc2html.py {posargs}
[testenv:pyinstaller-{64bit,32bit}{,-qt6}] [testenv:pyinstaller-{64bit,32bit}{,-qt5}]
basepython = {env:PYTHON:python3} basepython = {env:PYTHON:python3}
passenv = passenv =
APPDATA APPDATA
HOME HOME
PYINSTALLER_DEBUG PYINSTALLER_DEBUG
setenv = setenv =
qt6: PYINSTALLER_QT6=true qt5: PYINSTALLER_QT5=true
deps = deps =
-r{toxinidir}/requirements.txt -r{toxinidir}/requirements.txt
-r{toxinidir}/misc/requirements/requirements-pyinstaller.txt -r{toxinidir}/misc/requirements/requirements-pyinstaller.txt
!qt6: -r{toxinidir}/misc/requirements/requirements-pyqt.txt !qt5: -r{toxinidir}/misc/requirements/requirements-pyqt-6.txt
qt6: -r{toxinidir}/misc/requirements/requirements-pyqt-6.txt qt5: -r{toxinidir}/misc/requirements/requirements-pyqt-5.txt
commands = commands =
{envbindir}/pyinstaller --noconfirm misc/qutebrowser.spec {envbindir}/pyinstaller --noconfirm misc/qutebrowser.spec
@ -246,9 +247,7 @@ commands =
basepython = {env:PYTHON:python3} basepython = {env:PYTHON:python3}
passenv = {[testenv:mypy-pyqt6]passenv} passenv = {[testenv:mypy-pyqt6]passenv}
deps = {[testenv:mypy-pyqt6]deps} deps = {[testenv:mypy-pyqt6]deps}
setenv = setenv = {[testenv:mypy-pyqt6]setenv}
pyqt6: QUTE_CONSTANTS_ARGS=--always-true=USE_PYQT6 --always-false=USE_PYQT5 --always-false=USE_PYSIDE2 --always-false=USE_PYSIDE6 --always-false=IS_QT5 --always-true=IS_QT6
pyqt5: QUTE_CONSTANTS_ARGS=--always-false=USE_PYQT6 --always-true=USE_PYQT5 --always-false=USE_PYSIDE2 --always-false=USE_PYSIDE6 --always-true=IS_QT5 --always-false=IS_QT6
commands = commands =
{envpython} -m mypy --cobertura-xml-report {envtmpdir} {env:QUTE_CONSTANTS_ARGS} qutebrowser tests {posargs} {envpython} -m mypy --cobertura-xml-report {envtmpdir} {env:QUTE_CONSTANTS_ARGS} qutebrowser tests {posargs}
{envdir}/bin/diff-cover --fail-under=100 --compare-branch={env:DIFF_BRANCH:origin/{env:GITHUB_BASE_REF:master}} {envtmpdir}/cobertura.xml {envdir}/bin/diff-cover --fail-under=100 --compare-branch={env:DIFF_BRANCH:origin/{env:GITHUB_BASE_REF:master}} {envtmpdir}/cobertura.xml
@ -264,21 +263,21 @@ deps =
commands = commands =
{envpython} -m sphinx -jauto -W --color {posargs} {toxinidir}/doc/extapi/ {toxinidir}/doc/extapi/_build/ {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} basepython = {env:PYTHON:python3}
passenv = * passenv = *
# Override default PyQt5 from [testenv] # Override default PyQt6 from [testenv]
setenv = setenv =
qt6: QUTE_QT_WRAPPER=PyQt6 qt5: QUTE_QT_WRAPPER=PyQt5
usedevelop = true usedevelop = true
deps = deps =
-r{toxinidir}/requirements.txt -r{toxinidir}/requirements.txt
-r{toxinidir}/misc/requirements/requirements-tox.txt -r{toxinidir}/misc/requirements/requirements-tox.txt
-r{toxinidir}/misc/requirements/requirements-docs.txt -r{toxinidir}/misc/requirements/requirements-docs.txt
!qt6: -r{toxinidir}/misc/requirements/requirements-pyqt.txt !qt5: -r{toxinidir}/misc/requirements/requirements-pyqt.txt
qt6: -r{toxinidir}/misc/requirements/requirements-pyqt-6.txt qt5: -r{toxinidir}/misc/requirements/requirements-pyqt-5.txt
-r{toxinidir}/misc/requirements/requirements-dev.txt -r{toxinidir}/misc/requirements/requirements-dev.txt
-r{toxinidir}/misc/requirements/requirements-pyinstaller.txt -r{toxinidir}/misc/requirements/requirements-pyinstaller.txt
commands = commands =
!qt6: {envpython} {toxinidir}/scripts/dev/build_release.py {posargs} !qt5: {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 --qt5 {posargs}