Merge pull request #7392 from qutebrowser/feat/turn_on_mypy_pyqt5
Get mypy working, with pyqt5, on qt6-v2 branch
This commit is contained in:
commit
be5b4c5dd0
|
|
@ -22,7 +22,8 @@ jobs:
|
|||
- testenv: pylint
|
||||
- testenv: flake8
|
||||
# FIXME:qt6 (lint)
|
||||
# - testenv: mypy
|
||||
# - testenv: mypy-pyqt6
|
||||
- testenv: mypy-pyqt5
|
||||
- testenv: docs
|
||||
- testenv: vulture
|
||||
- testenv: misc
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ mypy-extensions==0.4.3
|
|||
pluggy==1.0.0
|
||||
Pygments==2.13.0
|
||||
PyQt5-stubs==5.15.6.0
|
||||
PyQt6==6.3.1
|
||||
PyQt6-WebEngine==6.3.1
|
||||
tomli==2.0.1
|
||||
types-PyYAML==6.0.11
|
||||
typing_extensions==4.3.0
|
||||
|
|
|
|||
|
|
@ -236,12 +236,12 @@ class AbstractPrinting(QObject):
|
|||
super().__init__(parent)
|
||||
self._widget = cast(_WidgetType, None)
|
||||
self._tab = tab
|
||||
self._dialog: QPrintDialog = None
|
||||
self._dialog: Optional[QPrintDialog] = None
|
||||
self.printing_finished.connect(self._on_printing_finished)
|
||||
self.pdf_printing_finished.connect(self._on_pdf_printing_finished)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_printing_finished(self, ok):
|
||||
def _on_printing_finished(self, ok: bool) -> None:
|
||||
# Only reporting error here, as the user has feedback from the dialog
|
||||
# (and probably their printer) already.
|
||||
if not ok:
|
||||
|
|
@ -251,7 +251,7 @@ class AbstractPrinting(QObject):
|
|||
self._dialog = None
|
||||
|
||||
@pyqtSlot(str, bool)
|
||||
def _on_pdf_printing_finished(self, path, ok):
|
||||
def _on_pdf_printing_finished(self, path: str, ok: bool) -> None:
|
||||
if ok:
|
||||
message.info(f"Printed to {path}")
|
||||
else:
|
||||
|
|
@ -277,7 +277,7 @@ class AbstractPrinting(QObject):
|
|||
"""Print the tab to a PDF with the given filename."""
|
||||
raise NotImplementedError
|
||||
|
||||
def to_printer(self, printer: QPrinter):
|
||||
def to_printer(self, printer: QPrinter) -> None:
|
||||
"""Print the tab.
|
||||
|
||||
Args:
|
||||
|
|
@ -288,7 +288,9 @@ class AbstractPrinting(QObject):
|
|||
def show_dialog(self) -> None:
|
||||
"""Print with a QPrintDialog."""
|
||||
self._dialog = QPrintDialog(self._tab)
|
||||
self._dialog.open(lambda: self.to_printer(self._dialog.printer()))
|
||||
assert self._dialog is not None
|
||||
not_none_dialog = self._dialog
|
||||
self._dialog.open(lambda: self.to_printer(not_none_dialog.printer()))
|
||||
# Gets cleaned up in on_printing_finished
|
||||
|
||||
|
||||
|
|
@ -1320,7 +1322,8 @@ class AbstractTab(QWidget):
|
|||
def __repr__(self) -> str:
|
||||
try:
|
||||
qurl = self.url()
|
||||
url = qurl.toDisplayString(QUrl.ComponentFormattingOption.EncodeUnicode)
|
||||
as_unicode = QUrl.ComponentFormattingOption.EncodeUnicode
|
||||
url = qurl.toDisplayString(as_unicode) # type: ignore[arg-type]
|
||||
except (AttributeError, RuntimeError) as exc:
|
||||
url = '<{}>'.format(exc.__class__.__name__)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ class HintActions:
|
|||
|
||||
flags = QUrl.ComponentFormattingOption.FullyEncoded | QUrl.UrlFormattingOption.RemovePassword
|
||||
if url.scheme() == 'mailto':
|
||||
flags |= QUrl.UrlFormattingOption.RemoveScheme
|
||||
flags |= QUrl.UrlFormattingOption.RemoveScheme # type: ignore[operator]
|
||||
urlstr = url.toString(flags)
|
||||
|
||||
new_content = urlstr
|
||||
|
|
|
|||
|
|
@ -177,7 +177,10 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_redirected(self, url):
|
||||
log.downloads.debug(f"redirected: {self._reply.url()} -> {url}")
|
||||
if self._reply is None:
|
||||
log.downloads.warning(f"redirected: REPLY GONE -> {url}")
|
||||
else:
|
||||
log.downloads.debug(f"redirected: {self._reply.url()} -> {url}")
|
||||
|
||||
def _do_cancel(self):
|
||||
self._read_timer.stop()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
from typing import Iterator, Optional, Set, TYPE_CHECKING, Union, Dict
|
||||
import collections.abc
|
||||
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import QUrl, Qt, QEvent, QTimer, QRect, QPointF
|
||||
from qutebrowser.qt.gui import QMouseEvent
|
||||
|
||||
|
|
@ -35,6 +36,11 @@ if TYPE_CHECKING:
|
|||
|
||||
JsValueType = Union[int, float, str, None]
|
||||
|
||||
if machinery.IS_QT6:
|
||||
KeybordModifierType = Qt.KeyboardModifier
|
||||
else:
|
||||
KeybordModifierType = Union[Qt.KeyboardModifiers, Qt.KeyboardModifier]
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
||||
|
|
@ -345,7 +351,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
|
|||
log.webelem.debug("Sending fake click to {!r} at position {} with "
|
||||
"target {}".format(self, pos, click_target))
|
||||
|
||||
target_modifiers: Dict[usertypes.ClickTarget, Qt.KeyboardModifier] = {
|
||||
target_modifiers: Dict[usertypes.ClickTarget, KeybordModifierType] = {
|
||||
usertypes.ClickTarget.normal: Qt.KeyboardModifier.NoModifier,
|
||||
usertypes.ClickTarget.window: Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ShiftModifier,
|
||||
usertypes.ClickTarget.tab: Qt.KeyboardModifier.ControlModifier,
|
||||
|
|
|
|||
|
|
@ -32,23 +32,36 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
|||
|
||||
"""A wrapper over a QWebEngineCertificateError.
|
||||
|
||||
Base code shared between Qt 5 and 6 implementations.
|
||||
Support both Qt 5 and 6.
|
||||
"""
|
||||
|
||||
def __init__(self, error: QWebEngineCertificateError) -> None:
|
||||
super().__init__()
|
||||
self._error = error
|
||||
self.ignore = False
|
||||
self._validate()
|
||||
|
||||
def _validate(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
raise NotImplementedError
|
||||
if machinery.IS_QT5:
|
||||
return self._error.errorDescription()
|
||||
else:
|
||||
return self._error.description()
|
||||
|
||||
def _type(self) -> Any: # QWebEngineCertificateError.Type or .Error
|
||||
raise NotImplementedError
|
||||
if machinery.IS_QT5:
|
||||
return self._error.error()
|
||||
else:
|
||||
return self._error.type()
|
||||
|
||||
def reject_certificate(self) -> None:
|
||||
super().reject_certificate()
|
||||
self._error.rejectCertificate()
|
||||
|
||||
def accept_certificate(self) -> None:
|
||||
super().accept_certificate()
|
||||
if machinery.IS_QT5:
|
||||
self._error.ignoreCertificateError()
|
||||
else:
|
||||
self._error.acceptCertificate()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return utils.get_repr(
|
||||
|
|
@ -68,54 +81,6 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
|||
raise usertypes.UndeferrableError("PyQt bug")
|
||||
|
||||
|
||||
class CertificateErrorWrapperQt5(CertificateErrorWrapper):
|
||||
|
||||
"""QWebEngineCertificateError handling for Qt 5 API."""
|
||||
|
||||
def _validate(self) -> None:
|
||||
assert machinery.IS_QT5
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._error.errorDescription()
|
||||
|
||||
def _type(self) -> Any:
|
||||
return self._error.error()
|
||||
|
||||
def reject_certificate(self) -> None:
|
||||
super().reject_certificate()
|
||||
self._error.rejectCertificate()
|
||||
|
||||
def accept_certificate(self) -> None:
|
||||
super().accept_certificate()
|
||||
self._error.ignoreCertificateError()
|
||||
|
||||
|
||||
class CertificateErrorWrapperQt6(CertificateErrorWrapper):
|
||||
|
||||
"""QWebEngineCertificateError handling for Qt 6 API."""
|
||||
|
||||
def _validate(self) -> None:
|
||||
assert machinery.IS_QT6
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._error.description()
|
||||
|
||||
def _type(self) -> Any:
|
||||
return self._error.type()
|
||||
|
||||
def reject_certificate(self) -> None:
|
||||
super().reject_certificate()
|
||||
self._error.rejectCertificate()
|
||||
|
||||
def accept_certificate(self) -> None:
|
||||
super().accept_certificate()
|
||||
self._error.acceptCertificate()
|
||||
|
||||
|
||||
def create(error: QWebEngineCertificateError) -> CertificateErrorWrapper:
|
||||
"""Factory function picking the right class based on Qt version."""
|
||||
if machinery.IS_QT5:
|
||||
return CertificateErrorWrapperQt5(error)
|
||||
elif machinery.IS_QT6:
|
||||
return CertificateErrorWrapperQt6(error)
|
||||
raise utils.Unreachable
|
||||
return CertificateErrorWrapper(error)
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ _DEFINITIONS[Variant.qt_63] = _DEFINITIONS[Variant.qt_515_3].copy_add_setting(
|
|||
)
|
||||
|
||||
|
||||
_PREFERRED_COLOR_SCHEME_DEFINITIONS = {
|
||||
_PREFERRED_COLOR_SCHEME_DEFINITIONS: Mapping[Variant, Mapping[Any, str]] = {
|
||||
Variant.qt_515_2: {
|
||||
# 0: no-preference (not exposed)
|
||||
"dark": "1",
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import functools
|
|||
import subprocess
|
||||
from typing import Any, List, Dict, Optional, Iterator, Type, TYPE_CHECKING
|
||||
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import (Qt, QObject, QVariant, QMetaType, QByteArray, pyqtSlot,
|
||||
pyqtSignal, QTimer, QProcess, QUrl)
|
||||
from qutebrowser.qt.gui import QImage, QIcon, QPixmap
|
||||
|
|
@ -686,10 +687,9 @@ def _as_uint32(x: int) -> QVariant:
|
|||
"""Convert the given int to an uint32 for DBus."""
|
||||
variant = QVariant(x)
|
||||
|
||||
try:
|
||||
# Qt 5
|
||||
if machinery.IS_QT5:
|
||||
target_type = QVariant.Type.UInt
|
||||
except AttributeError:
|
||||
else:
|
||||
# Qt 6
|
||||
target_type = QMetaType(QMetaType.Type.UInt.value)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import re
|
|||
import os.path
|
||||
import functools
|
||||
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import pyqtSlot, Qt, QUrl, QObject
|
||||
from qutebrowser.qt.webenginecore import QWebEngineDownloadRequest
|
||||
|
||||
|
|
@ -44,10 +45,9 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
parent: QObject = None) -> None:
|
||||
super().__init__(manager=manager, parent=manager)
|
||||
self._qt_item = qt_item
|
||||
try:
|
||||
# Qt 5
|
||||
if machinery.IS_QT5:
|
||||
qt_item.downloadProgress.connect(self.stats.on_download_progress)
|
||||
except AttributeError:
|
||||
else:
|
||||
# Qt 6
|
||||
qt_item.receivedBytesChanged.connect(
|
||||
lambda: self.stats.on_download_progress(
|
||||
|
|
@ -106,10 +106,9 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
"{}".format(state_name))
|
||||
|
||||
def _do_die(self):
|
||||
try:
|
||||
# Qt 5
|
||||
if machinery.IS_QT5:
|
||||
self._qt_item.downloadProgress.disconnect()
|
||||
except AttributeError:
|
||||
else:
|
||||
# Qt 6
|
||||
self._qt_item.receivedBytesChanged.disconnect()
|
||||
self._qt_item.totalBytesChanged.disconnect()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
"""Customized QWebInspector for QtWebEngine."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.webenginewidgets import QWebEngineView
|
||||
from qutebrowser.qt.webenginecore import QWebEnginePage
|
||||
from qutebrowser.qt.widgets import QWidget
|
||||
|
|
@ -48,12 +51,11 @@ class WebEngineInspectorView(QWebEngineView):
|
|||
See WebEngineView.createWindow for details.
|
||||
"""
|
||||
inspected_page = self.page().inspectedPage()
|
||||
try:
|
||||
# Qt 5
|
||||
if machinery.IS_QT5:
|
||||
view = inspected_page.view()
|
||||
assert isinstance(view, QWebEngineView), view
|
||||
return view.createWindow(wintype)
|
||||
except AttributeError:
|
||||
else:
|
||||
# Qt 6
|
||||
newpage = inspected_page.createWindow(wintype)
|
||||
return webview.WebEngineView.forPage(newpage)
|
||||
|
|
@ -70,7 +72,7 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
|||
parent: QWidget = None) -> None:
|
||||
super().__init__(splitter, win_id, parent)
|
||||
self._check_devtools_resources()
|
||||
self._settings = None
|
||||
self._settings: Optional[webenginesettings.WebEngineSettings] = None
|
||||
|
||||
def _on_window_close_requested(self) -> None:
|
||||
"""Called when the 'x' was clicked in the devtools."""
|
||||
|
|
@ -115,6 +117,7 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
|||
assert inspector_page.profile() == page.profile()
|
||||
|
||||
inspector_page.setInspectedPage(page)
|
||||
assert self._settings is not None
|
||||
self._settings.update_for_url(inspector_page.requestedUrl())
|
||||
|
||||
def _needs_recreate(self) -> bool:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
|||
|
||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||
resources, message, jinja, debug, version)
|
||||
from qutebrowser.qt import sip
|
||||
from qutebrowser.qt import sip, machinery
|
||||
from qutebrowser.misc import objects, miscwidgets
|
||||
|
||||
|
||||
|
|
@ -85,12 +85,8 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
|
|||
"""Called from WebEngineTab.connect_signals."""
|
||||
page = self._widget.page()
|
||||
page.pdfPrintingFinished.connect(self.pdf_printing_finished)
|
||||
try:
|
||||
# Qt 6
|
||||
if machinery.IS_QT6:
|
||||
self._widget.printFinished.connect(self.printing_finished)
|
||||
except AttributeError:
|
||||
# Qt 5: Uses callbacks instead
|
||||
pass
|
||||
|
||||
def check_pdf_support(self):
|
||||
pass
|
||||
|
|
@ -103,10 +99,9 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
|
|||
self._widget.page().printToPdf(filename)
|
||||
|
||||
def to_printer(self, printer):
|
||||
try:
|
||||
# Qt 5
|
||||
if machinery.IS_QT5:
|
||||
self._widget.page().print(printer, self.printing_finished.emit)
|
||||
except AttributeError:
|
||||
else:
|
||||
# Qt 6
|
||||
self._widget.print(printer)
|
||||
|
||||
|
|
@ -1068,8 +1063,7 @@ class _WebEngineScripts(QObject):
|
|||
def _remove_js(self, name):
|
||||
"""Remove an early QWebEngineScript."""
|
||||
scripts = self._widget.page().scripts()
|
||||
if hasattr(scripts, 'find'):
|
||||
# Qt 6
|
||||
if machinery.IS_QT6:
|
||||
for script in scripts.find(f'_qute_{name}'):
|
||||
scripts.remove(script)
|
||||
else:
|
||||
|
|
@ -1702,6 +1696,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
# pylint: disable=protected-access
|
||||
self.audio._connect_signals()
|
||||
self.search.connect_signals()
|
||||
assert isinstance(self.printing, WebEnginePrinting)
|
||||
self.printing.connect_signals()
|
||||
self._permissions.connect_signals()
|
||||
self._scripts.connect_signals()
|
||||
|
|
|
|||
|
|
@ -195,11 +195,10 @@ class WebEnginePage(QWebEnginePage):
|
|||
self._theme_color = theme_color
|
||||
self._set_bg_color()
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
try:
|
||||
self.certificateError.connect(self._handle_certificate_error)
|
||||
except AttributeError:
|
||||
# Qt 5: Overridden method instead of signal
|
||||
pass
|
||||
if machinery.IS_QT6:
|
||||
self.certificateError.connect( # pylint: disable=no-member
|
||||
self._handle_certificate_error
|
||||
)
|
||||
|
||||
@config.change_filter('colors.webpage.bg')
|
||||
def _set_bg_color(self):
|
||||
|
|
|
|||
|
|
@ -33,14 +33,17 @@ handle what we actually think we do.
|
|||
|
||||
import itertools
|
||||
import dataclasses
|
||||
from typing import Iterator, List, Mapping, Optional, Union, overload
|
||||
from typing import Iterator, List, Mapping, Optional, Union, overload, cast
|
||||
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import Qt, QEvent
|
||||
from qutebrowser.qt.gui import QKeySequence, QKeyEvent
|
||||
try:
|
||||
from qutebrowser.qt.core import QKeyCombination
|
||||
except ImportError:
|
||||
QKeyCombination = None # Qt 6 only
|
||||
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
|
||||
else:
|
||||
QKeyCombination = None
|
||||
|
||||
from qutebrowser.utils import utils, qtutils, debug
|
||||
|
||||
|
|
@ -69,9 +72,13 @@ try:
|
|||
except ValueError:
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2022-April/044607.html
|
||||
_NIL_KEY = 0
|
||||
_NIL_KEY = 0 # type: ignore[assignment]
|
||||
|
||||
_ModifierType = Qt.KeyboardModifier
|
||||
if machinery.IS_QT6:
|
||||
_KeyInfoType = QKeyCombination
|
||||
else:
|
||||
_KeyInfoType = int
|
||||
|
||||
|
||||
_SPECIAL_NAMES = {
|
||||
|
|
@ -252,7 +259,7 @@ def _modifiers_to_string(modifiers: _ModifierType) -> str:
|
|||
_assert_plain_modifier(modifiers)
|
||||
altgr = Qt.KeyboardModifier.GroupSwitchModifier
|
||||
if modifiers & altgr:
|
||||
modifiers &= ~altgr
|
||||
modifiers &= ~altgr # type: ignore[assignment]
|
||||
result = 'AltGr+'
|
||||
else:
|
||||
result = ''
|
||||
|
|
@ -376,13 +383,14 @@ class KeyInfo:
|
|||
except ValueError as ex:
|
||||
raise InvalidKeyError(str(ex))
|
||||
key = _remap_unicode(key, e.text())
|
||||
modifiers = e.modifiers()
|
||||
modifiers = cast(Qt.KeyboardModifier, e.modifiers())
|
||||
return cls(key, modifiers)
|
||||
|
||||
@classmethod
|
||||
def from_qt(cls, combination: Union[int, QKeyCombination]) -> 'KeyInfo':
|
||||
def from_qt(cls, combination: _KeyInfoType) -> 'KeyInfo':
|
||||
"""Construct a KeyInfo from a Qt5-style int or Qt6-style QKeyCombination."""
|
||||
if isinstance(combination, int):
|
||||
if machinery.IS_QT5:
|
||||
assert isinstance(combination, int)
|
||||
key = Qt.Key(
|
||||
int(combination) & ~Qt.KeyboardModifier.KeyboardModifierMask)
|
||||
modifiers = Qt.KeyboardModifier(
|
||||
|
|
@ -411,7 +419,7 @@ class KeyInfo:
|
|||
|
||||
if self.key in _MODIFIER_MAP:
|
||||
# Don't return e.g. <Shift+Shift>
|
||||
modifiers &= ~_MODIFIER_MAP[self.key]
|
||||
modifiers &= ~_MODIFIER_MAP[self.key] # type: ignore[assignment]
|
||||
elif _is_printable(self.key):
|
||||
# "normal" binding
|
||||
if not key_string: # pragma: no cover
|
||||
|
|
@ -461,25 +469,25 @@ class KeyInfo:
|
|||
"""Get a QKeyEvent from this KeyInfo."""
|
||||
return QKeyEvent(typ, self.key, self.modifiers, self.text())
|
||||
|
||||
def to_qt(self) -> Union[int, QKeyCombination]:
|
||||
def to_qt(self) -> _KeyInfoType:
|
||||
"""Get something suitable for a QKeySequence."""
|
||||
if QKeyCombination is None:
|
||||
# Qt 5
|
||||
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)
|
||||
|
||||
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, key)
|
||||
|
||||
def with_stripped_modifiers(self, modifiers: Qt.KeyboardModifier) -> "KeyInfo":
|
||||
return KeyInfo(key=self.key, modifiers=self.modifiers & ~modifiers)
|
||||
mods = self.modifiers & ~modifiers
|
||||
return KeyInfo(key=self.key, modifiers=mods) # type: ignore[arg-type]
|
||||
|
||||
def is_special(self) -> bool:
|
||||
"""Check whether this key requires special key syntax."""
|
||||
|
|
@ -541,7 +549,10 @@ class KeySequence:
|
|||
|
||||
def __iter__(self) -> Iterator[KeyInfo]:
|
||||
"""Iterate over KeyInfo objects."""
|
||||
for combination in itertools.chain.from_iterable(self._sequences):
|
||||
combination: QKeySequence
|
||||
for combination in itertools.chain.from_iterable(
|
||||
self._sequences # type: ignore[arg-type]
|
||||
):
|
||||
yield KeyInfo.from_qt(combination)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
|
@ -648,7 +659,7 @@ class KeySequence:
|
|||
raise KeyParseError(None, f"Got invalid key: {e}")
|
||||
|
||||
_assert_plain_key(key)
|
||||
_assert_plain_modifier(ev.modifiers())
|
||||
_assert_plain_modifier(cast(Qt.KeyboardModifier, ev.modifiers()))
|
||||
|
||||
key = _remap_unicode(key, ev.text())
|
||||
modifiers = ev.modifiers()
|
||||
|
|
@ -675,10 +686,11 @@ class KeySequence:
|
|||
#
|
||||
# In addition, Shift also *is* relevant when other modifiers are
|
||||
# involved. Shift-Ctrl-X should not be equivalent to Ctrl-X.
|
||||
if (modifiers == Qt.KeyboardModifier.ShiftModifier and
|
||||
shift_modifier = Qt.KeyboardModifier.ShiftModifier
|
||||
if (modifiers == shift_modifier and # type: ignore[comparison-overlap]
|
||||
_is_printable(key) and
|
||||
not ev.text().isupper()):
|
||||
modifiers = Qt.KeyboardModifier.NoModifier
|
||||
modifiers = Qt.KeyboardModifier.NoModifier # type: ignore[assignment]
|
||||
|
||||
# On macOS, swap Ctrl and Meta back
|
||||
#
|
||||
|
|
@ -697,7 +709,7 @@ class KeySequence:
|
|||
modifiers |= Qt.KeyboardModifier.ControlModifier
|
||||
|
||||
infos = list(self)
|
||||
infos.append(KeyInfo(key, modifiers))
|
||||
infos.append(KeyInfo(key, cast(Qt.KeyboardModifier, modifiers)))
|
||||
|
||||
return self.__class__(*infos)
|
||||
|
||||
|
|
@ -712,7 +724,7 @@ class KeySequence:
|
|||
mappings: Mapping['KeySequence', 'KeySequence']
|
||||
) -> 'KeySequence':
|
||||
"""Get a new KeySequence with the given mappings applied."""
|
||||
infos = []
|
||||
infos: List[KeyInfo] = []
|
||||
for info in self:
|
||||
key_seq = KeySequence(info)
|
||||
if key_seq in mappings:
|
||||
|
|
|
|||
|
|
@ -567,7 +567,8 @@ class MainWindow(QWidget):
|
|||
window_flags = Qt.WindowType.Window
|
||||
refresh_window = self.isVisible()
|
||||
if hidden:
|
||||
window_flags |= Qt.WindowType.CustomizeWindowHint | Qt.WindowType.NoDropShadowWindowHint
|
||||
modifiers = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.NoDropShadowWindowHint
|
||||
window_flags |= modifiers # type: ignore[assignment]
|
||||
self.setWindowFlags(window_flags)
|
||||
|
||||
if utils.is_mac and hidden and not qtutils.version_check('6.3', compiled=False):
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ class PromptQueue(QObject):
|
|||
question.completed.connect(loop.deleteLater)
|
||||
log.prompt.debug("Starting loop.exec() for {}".format(question))
|
||||
flags = QEventLoop.ProcessEventsFlag.ExcludeSocketNotifiers
|
||||
loop.exec(flags)
|
||||
loop.exec(flags) # type: ignore[arg-type]
|
||||
log.prompt.debug("Ending loop.exec() for {}".format(question))
|
||||
|
||||
log.prompt.debug("Restoring old question {}".format(old_question))
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ class TabbedBrowser(QWidget):
|
|||
self.widget.tabCloseRequested.connect(self.on_tab_close_requested)
|
||||
self.widget.new_tab_requested.connect(self.tabopen)
|
||||
self.widget.currentChanged.connect(self._on_current_changed)
|
||||
self.cur_fullscreen_requested.connect(self.widget.tabBar().maybe_hide)
|
||||
self.cur_fullscreen_requested.connect(self.widget.tab_bar().maybe_hide)
|
||||
|
||||
self.widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ import collections
|
|||
import contextlib
|
||||
import dataclasses
|
||||
import types
|
||||
from typing import Any, Dict, Iterator, List, Mapping, MutableSequence, Optional, Type
|
||||
from typing import Any, Dict, Iterator, List, Mapping, MutableSequence, Optional, Type, Union
|
||||
|
||||
from qutebrowser.qt.core import QObject, pyqtSignal
|
||||
from qutebrowser.qt.sql import QSqlDatabase, QSqlError, QSqlQuery
|
||||
|
||||
from qutebrowser.qt import sip
|
||||
from qutebrowser.qt import sip, machinery
|
||||
from qutebrowser.utils import debug, log
|
||||
|
||||
|
||||
|
|
@ -149,6 +149,7 @@ class BugError(Error):
|
|||
def raise_sqlite_error(msg: str, error: QSqlError) -> None:
|
||||
"""Raise either a BugError or KnownError."""
|
||||
error_code = error.nativeErrorCode()
|
||||
primary_error_code: Union[SqliteErrorCode, str]
|
||||
try:
|
||||
# https://sqlite.org/rescode.html#pve
|
||||
primary_error_code = SqliteErrorCode(int(error_code) & 0xff)
|
||||
|
|
@ -351,10 +352,10 @@ class Query:
|
|||
def _validate_bound_values(self):
|
||||
"""Make sure all placeholders are bound."""
|
||||
qt_bound_values = self.query.boundValues()
|
||||
try:
|
||||
if machinery.IS_QT5:
|
||||
# Qt 5: Returns a dict
|
||||
values = qt_bound_values.values()
|
||||
except AttributeError:
|
||||
values = list(qt_bound_values.values())
|
||||
else:
|
||||
# Qt 6: Returns a list
|
||||
values = qt_bound_values
|
||||
|
||||
|
|
|
|||
|
|
@ -7,18 +7,25 @@ from qutebrowser.qt import machinery
|
|||
|
||||
# While upstream recommends using PyQt6.sip ever since PyQt6 5.11, some distributions
|
||||
# still package later versions of PyQt6 with a top-level "sip" rather than "PyQt6.sip".
|
||||
VENDORED_SIP=False
|
||||
|
||||
if machinery.USE_PYSIDE6:
|
||||
raise machinery.Unavailable()
|
||||
elif machinery.USE_PYQT5:
|
||||
try:
|
||||
from PyQt5.sip import *
|
||||
VENDORED_SIP=True
|
||||
except ImportError:
|
||||
from sip import *
|
||||
pass
|
||||
elif machinery.USE_PYQT6:
|
||||
try:
|
||||
from PyQt6.sip import *
|
||||
VENDORED_SIP=True
|
||||
except ImportError:
|
||||
from sip import *
|
||||
pass
|
||||
|
||||
else:
|
||||
raise machinery.UnknownWrapper()
|
||||
|
||||
if not VENDORED_SIP:
|
||||
from sip import * # type: ignore[import] # pylint: disable=import-error
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ _EnumValueType = Union[sip.simplewrapper, int]
|
|||
|
||||
def _qenum_key_python(
|
||||
value: _EnumValueType,
|
||||
klass: Type[_EnumValueType] = None,
|
||||
klass: Type[_EnumValueType],
|
||||
) -> Optional[str]:
|
||||
"""New-style PyQt6: Try getting value from Python enum."""
|
||||
if isinstance(value, enum.Enum) and value.name:
|
||||
|
|
@ -113,6 +113,7 @@ def _qenum_key_python(
|
|||
# We got an int with klass passed: Try asking Python enum for member
|
||||
if issubclass(klass, enum.Enum):
|
||||
try:
|
||||
assert isinstance(value, int)
|
||||
name = klass(value).name
|
||||
if name is not None and name != str(value):
|
||||
return name
|
||||
|
|
@ -125,7 +126,7 @@ def _qenum_key_python(
|
|||
def _qenum_key_qt(
|
||||
base: Type[_EnumValueType],
|
||||
value: _EnumValueType,
|
||||
klass: Type[_EnumValueType] = None,
|
||||
klass: Type[_EnumValueType],
|
||||
) -> Optional[str]:
|
||||
# On PyQt5, or PyQt6 with int passed: Try to ask Qt's introspection.
|
||||
# However, not every Qt enum value has a staticMetaObject
|
||||
|
|
@ -168,6 +169,7 @@ def qenum_key(
|
|||
klass = value.__class__
|
||||
if klass == int:
|
||||
raise TypeError("Can't guess enum class of an int!")
|
||||
assert klass is not None
|
||||
|
||||
name = _qenum_key_python(value=value, klass=klass)
|
||||
if name is not None:
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import contextlib
|
|||
from typing import (Any, AnyStr, TYPE_CHECKING, BinaryIO, IO, Iterator,
|
||||
Optional, Union, Tuple, cast)
|
||||
|
||||
from qutebrowser.qt import machinery, sip
|
||||
from qutebrowser.qt.core import (qVersion, QEventLoop, QDataStream, QByteArray,
|
||||
QIODevice, QFileDevice, QSaveFile, QT_VERSION_STR,
|
||||
PYQT_VERSION_STR, QObject, QUrl, QLibraryInfo)
|
||||
|
|
@ -455,6 +456,12 @@ class QtValueError(ValueError):
|
|||
super().__init__(err)
|
||||
|
||||
|
||||
if machinery.IS_QT6:
|
||||
_ProcessEventFlagType = QEventLoop.ProcessEventsFlag
|
||||
else:
|
||||
_ProcessEventFlagType = QEventLoop.ProcessEventsFlags
|
||||
|
||||
|
||||
class EventLoop(QEventLoop):
|
||||
|
||||
"""A thin wrapper around QEventLoop.
|
||||
|
|
@ -468,8 +475,9 @@ class EventLoop(QEventLoop):
|
|||
|
||||
def exec(
|
||||
self,
|
||||
flags: QEventLoop.ProcessEventsFlag =
|
||||
QEventLoop.ProcessEventsFlag.AllEvents
|
||||
flags: _ProcessEventFlagType = (
|
||||
QEventLoop.ProcessEventsFlag.AllEvents # type: ignore[assignment]
|
||||
),
|
||||
) -> int:
|
||||
"""Override exec_ to raise an exception when re-running."""
|
||||
if self._executing:
|
||||
|
|
@ -581,8 +589,7 @@ class LibraryPath(enum.Enum):
|
|||
|
||||
def library_path(which: LibraryPath) -> pathlib.Path:
|
||||
"""Wrapper around QLibraryInfo.path / .location."""
|
||||
if hasattr(QLibraryInfo, "path"):
|
||||
# Qt 6
|
||||
if machinery.IS_QT6:
|
||||
val = getattr(QLibraryInfo.LibraryPath, which.value)
|
||||
ret = QLibraryInfo.path(val)
|
||||
else:
|
||||
|
|
@ -593,7 +600,7 @@ def library_path(which: LibraryPath) -> pathlib.Path:
|
|||
return pathlib.Path(ret)
|
||||
|
||||
|
||||
def extract_enum_val(val: Union[int, enum.Enum]) -> int:
|
||||
def extract_enum_val(val: Union[sip.simplewrapper, int, enum.Enum]) -> int:
|
||||
"""Extract an int value from a Qt enum value.
|
||||
|
||||
For Qt 5, enum values are basically Python integers.
|
||||
|
|
@ -602,4 +609,6 @@ def extract_enum_val(val: Union[int, enum.Enum]) -> int:
|
|||
"""
|
||||
if isinstance(val, enum.Enum):
|
||||
return val.value
|
||||
elif isinstance(val, sip.simplewrapper):
|
||||
return int(val) # type: ignore[call-overload]
|
||||
return int(val)
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ class AbstractCertificateErrorWrapper:
|
|||
"""A wrapper over an SSL/certificate error."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._certificate_accepted = None
|
||||
self._certificate_accepted: Optional[bool] = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
|
@ -514,7 +514,7 @@ class AbstractCertificateErrorWrapper:
|
|||
def defer(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def certificate_was_accepted(self) -> None:
|
||||
def certificate_was_accepted(self) -> bool:
|
||||
"""Check whether the certificate was accepted by the user."""
|
||||
if not self.is_overridable():
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -785,18 +785,19 @@ def qtwebengine_versions(*, avoid_init: bool = False) -> WebEngineVersions:
|
|||
if override is not None:
|
||||
return WebEngineVersions.from_pyqt(override, source='override')
|
||||
|
||||
try:
|
||||
from qutebrowser.qt.webenginecore import (
|
||||
qWebEngineVersion,
|
||||
qWebEngineChromiumVersion,
|
||||
)
|
||||
except ImportError:
|
||||
pass # Needs QtWebEngine 6.2+ with PyQtWebEngine 6.3.1+
|
||||
else:
|
||||
return WebEngineVersions.from_api(
|
||||
qtwe_version=qWebEngineVersion(),
|
||||
chromium_version=qWebEngineChromiumVersion(),
|
||||
)
|
||||
if machinery.IS_QT6:
|
||||
try:
|
||||
from qutebrowser.qt.webenginecore import (
|
||||
qWebEngineVersion,
|
||||
qWebEngineChromiumVersion,
|
||||
)
|
||||
except ImportError:
|
||||
pass # Needs QtWebEngine 6.2+ with PyQtWebEngine 6.3.1+
|
||||
else:
|
||||
return WebEngineVersions.from_api(
|
||||
qtwe_version=qWebEngineVersion(),
|
||||
chromium_version=qWebEngineChromiumVersion(),
|
||||
)
|
||||
|
||||
from qutebrowser.browser.webengine import webenginesettings
|
||||
|
||||
|
|
@ -1023,13 +1024,11 @@ def opengl_info() -> Optional[OpenGLInfo]: # pragma: no cover
|
|||
vp.setVersion(2, 0)
|
||||
|
||||
try:
|
||||
try:
|
||||
# Qt 5
|
||||
if machinery.IS_QT5:
|
||||
vf = ctx.versionFunctions(vp)
|
||||
except AttributeError:
|
||||
else:
|
||||
# Qt 6
|
||||
# FIXME:qt6 (lint)
|
||||
# pylint: disable-next=no-name-in-module
|
||||
from qutebrowser.qt.opengl import QOpenGLVersionFunctionsFactory
|
||||
vf = QOpenGLVersionFunctionsFactory.get(vp, ctx)
|
||||
except ImportError as e:
|
||||
|
|
|
|||
18
tox.ini
18
tox.ini
|
|
@ -180,16 +180,19 @@ deps =
|
|||
whitelist_externals = bash
|
||||
commands = bash scripts/dev/run_shellcheck.sh {posargs}
|
||||
|
||||
[testenv:mypy]
|
||||
[testenv:mypy-{pyqt5,pyqt6}]
|
||||
basepython = {env:PYTHON:python3}
|
||||
passenv = TERM MYPY_FORCE_TERMINAL_WIDTH
|
||||
setenv =
|
||||
pyqt6: 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: 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
|
||||
deps =
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/misc/requirements/requirements-dev.txt
|
||||
-r{toxinidir}/misc/requirements/requirements-tests.txt
|
||||
-r{toxinidir}/misc/requirements/requirements-mypy.txt
|
||||
commands =
|
||||
{envpython} -m mypy qutebrowser {posargs}
|
||||
{envpython} -m mypy {env:CONSTANTS_ARGS} qutebrowser {posargs}
|
||||
|
||||
[testenv:yamllint]
|
||||
basepython = {env:PYTHON:python3}
|
||||
|
|
@ -204,12 +207,15 @@ whitelist_externals = actionlint
|
|||
commands =
|
||||
actionlint
|
||||
|
||||
[testenv:mypy-diff]
|
||||
[testenv:mypy-{pyqt5,pyqt6}-diff]
|
||||
basepython = {env:PYTHON:python3}
|
||||
passenv = {[testenv:mypy]passenv}
|
||||
deps = {[testenv:mypy]deps}
|
||||
passenv = {[testenv:mypy-pyqt6]passenv}
|
||||
deps = {[testenv:mypy-pyqt6]deps}
|
||||
setenv =
|
||||
pyqt6: 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: 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 =
|
||||
{envpython} -m mypy --cobertura-xml-report {envtmpdir} qutebrowser tests {posargs}
|
||||
{envpython} -m mypy --cobertura-xml-report {envtmpdir} {env: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
|
||||
|
||||
[testenv:sphinx]
|
||||
|
|
|
|||
Loading…
Reference in New Issue