Merge branch 'qt6-v2' into master-qt6
This commit is contained in:
commit
4793070db3
|
|
@ -21,7 +21,9 @@ jobs:
|
|||
include:
|
||||
- testenv: pylint
|
||||
- testenv: flake8
|
||||
- testenv: mypy
|
||||
# FIXME:qt6 (lint)
|
||||
# - testenv: mypy-pyqt6
|
||||
- testenv: mypy-pyqt5
|
||||
- testenv: docs
|
||||
- testenv: vulture
|
||||
- testenv: misc
|
||||
|
|
@ -90,11 +92,11 @@ jobs:
|
|||
image:
|
||||
- archlinux-webkit
|
||||
- archlinux-webengine
|
||||
# - archlinux-webengine-qt6 # FIXME:qt6 activate
|
||||
# - archlinux-webengine-unstable
|
||||
container:
|
||||
image: "qutebrowser/ci:${{ matrix.image }}"
|
||||
env:
|
||||
QUTE_BDD_WEBENGINE: "${{ matrix.image != 'archlinux-webkit' }}"
|
||||
DOCKER: "${{ matrix.image }}"
|
||||
CI: true
|
||||
PYTEST_ADDOPTS: "--color=yes"
|
||||
|
|
@ -119,30 +121,32 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
### PyQt 5.12 (Python 3.7)
|
||||
- testenv: py37-pyqt512
|
||||
os: ubuntu-20.04
|
||||
python: "3.7"
|
||||
### PyQt 5.13 (Python 3.7)
|
||||
- testenv: py37-pyqt513
|
||||
os: ubuntu-20.04
|
||||
python: "3.7"
|
||||
### PyQt 5.14 (Python 3.8)
|
||||
- testenv: py38-pyqt514
|
||||
os: ubuntu-20.04
|
||||
python: "3.8"
|
||||
### PyQt 5.15.0 (Python 3.9)
|
||||
- testenv: py39-pyqt5150
|
||||
### PyQt 5.15.2 (Python 3.9)
|
||||
- testenv: py39-pyqt5152
|
||||
os: ubuntu-20.04
|
||||
python: "3.9"
|
||||
### PyQt 5.15 (Python 3.10, with coverage)
|
||||
- testenv: py310-pyqt515-cov
|
||||
os: ubuntu-22.04
|
||||
python: "3.10"
|
||||
### PyQt 5.15 (Python 3.9, with coverage)
|
||||
# FIXME:qt6
|
||||
# - testenv: py39-pyqt515-cov
|
||||
# os: ubuntu-22.04
|
||||
# python: "3.10"
|
||||
### PyQt 5.15 (Python 3.11)
|
||||
- testenv: py311-pyqt515
|
||||
os: ubuntu-20.04
|
||||
python: "3.11-dev"
|
||||
### PyQt 6.2 (Python 3.9)
|
||||
- testenv: py39-pyqt62
|
||||
os: ubuntu-20.04
|
||||
python: 3.9
|
||||
### PyQt 6.3 (Python 3.9)
|
||||
- testenv: py39-pyqt63
|
||||
os: ubuntu-20.04
|
||||
python: 3.9
|
||||
### PyQt 6.4 (Python 3.9)
|
||||
# - testenv: py39-pyqt64
|
||||
# os: ubuntu-20.04
|
||||
# python: 3.9
|
||||
### macOS Big Sur: PyQt 5.15 (Python 3.9 to match PyInstaller env)
|
||||
- testenv: py39-pyqt515
|
||||
os: macos-11
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ disable=locally-disabled,
|
|||
missing-type-doc,
|
||||
missing-param-doc,
|
||||
useless-param-doc,
|
||||
wrong-import-order, # FIXME:qt6 (lint)
|
||||
ungrouped-imports, # FIXME:qt6 (lint)
|
||||
|
||||
[BASIC]
|
||||
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
||||
|
|
@ -74,7 +76,8 @@ no-docstring-rgx=(^_|^main$)
|
|||
class-const-naming-style = snake_case
|
||||
|
||||
[FORMAT]
|
||||
max-line-length=88
|
||||
# FIXME:qt6 (lint) down to 88 again once we use black
|
||||
max-line-length=190
|
||||
ignore-long-lines=(<?https?://|file://|^# Copyright 201\d|link:)
|
||||
expected-line-ending-format=LF
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ include doc/qutebrowser.1.asciidoc
|
|||
include doc/changelog.asciidoc
|
||||
prune qutebrowser/3rdparty
|
||||
exclude mypy.ini
|
||||
exclude pyrightconfig.json
|
||||
exclude tox.ini
|
||||
exclude qutebrowser/javascript/.eslintrc.yaml
|
||||
exclude qutebrowser/javascript/.eslintignore
|
||||
|
|
|
|||
|
|
@ -83,15 +83,14 @@ Requirements
|
|||
The following software and libraries are required to run qutebrowser:
|
||||
|
||||
* https://www.python.org/[Python] 3.7 or newer
|
||||
* https://www.qt.io/[Qt] 5.12.0 or newer (5.12 LTS or 5.15 recommended, Qt 6 is
|
||||
not supported yet) with the following modules:
|
||||
* https://www.qt.io/[Qt], either 6.2.0 or newer, or 5.15.0 or newer, with the following modules:
|
||||
- QtCore / qtbase
|
||||
- QtQuick (part of qtbase or qtdeclarative in some distributions)
|
||||
- QtSQL (part of qtbase in some distributions)
|
||||
- QtDBus (part of qtbase in some distributions; note that a connection to DBus at
|
||||
runtime is optional)
|
||||
- QtOpenGL
|
||||
- QtWebEngine, or
|
||||
- QtWebEngine (if using Qt 5, 5.15.2 or newer), or
|
||||
- alternatively QtWebKit (5.212) - **This is not recommended** due to known security
|
||||
issues in QtWebKit, you most likely want to use qutebrowser with the
|
||||
default QtWebEngine backend (based on Chromium) instead. Quoting the
|
||||
|
|
@ -99,8 +98,8 @@ The following software and libraries are required to run qutebrowser:
|
|||
_[The latest QtWebKit] release is based on [an] old WebKit revision with known
|
||||
unpatched vulnerabilities. Please use it carefully and avoid visiting untrusted
|
||||
websites and using it for transmission of sensitive data._
|
||||
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.12.0 or newer
|
||||
for Python 3
|
||||
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 6.2.2 or newer
|
||||
(Qt 6) or 5.15.0 or newer (Qt 5)
|
||||
* https://palletsprojects.com/p/jinja/[jinja2]
|
||||
* https://github.com/yaml/pyyaml[PyYAML]
|
||||
|
||||
|
|
|
|||
|
|
@ -57,9 +57,12 @@ Removed
|
|||
- Support for Python 3.6 is dropped, as it's been
|
||||
https://discuss.python.org/t/python-3-6-rides-into-the-sunset/12964[end-of-life upstream]
|
||||
since December 2021. Python 3.7.0 or newer is now required.
|
||||
- Support for Qt/PyQt before 5.15.0 and QtWebEngine before 5.15.2 are now
|
||||
dropped, as older Qt versions are
|
||||
https://endoflife.date/qt[end-of-life upstream] since mid/late 2020
|
||||
(5.13/5.14) and late 2021 (5.12 LTS).
|
||||
- It's planned to drop support for various legacy platforms and libraries which
|
||||
are unsupported upstream, such as:
|
||||
* Qt before 5.15 LTS (plus adding support for Qt 6.2+)
|
||||
* The QtWebKit backend
|
||||
* macOS 10.14 (via Homebrew)
|
||||
* 32-bit Windows (via Qt)
|
||||
|
|
|
|||
|
|
@ -128,6 +128,9 @@ Currently, the following tox environments are available:
|
|||
- untracked git files
|
||||
- VCS conflict markers
|
||||
- common spelling mistakes
|
||||
* http://mypy-lang.org/[mypy] for static type checking:
|
||||
- `mypy-pyqt5` run mypy with PyQt5 installed
|
||||
- `mypy-pyqt6` run mypy with PyQt6 installed
|
||||
|
||||
The default test suite is run with `tox`; the list of default
|
||||
environments is obtained with `tox -l`.
|
||||
|
|
@ -153,7 +156,7 @@ smallest scope which makes sense. Most of the time, this will be line scope.
|
|||
false-positives, let me know! I'm still tweaking the parameters.
|
||||
|
||||
|
||||
Running Specific Tests
|
||||
Running specific tests
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While you are developing you often don't want to run the full test
|
||||
|
|
@ -180,6 +183,15 @@ tox -e py37 -- tests/end2end/features/test_tabs_bdd.py -k undo
|
|||
tox -e py37-cov -- tests/unit/browser/test_webelem.py
|
||||
----
|
||||
|
||||
Specifying the backend for tests
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests automatically pick the backend based on what they manage to import. If
|
||||
you have both backends available and you would like the tests to be run with a
|
||||
specific one you can set either of a) the environment variable QUTE_TESTS_BACKEND
|
||||
, or b) the command line argument --qute-backend, to the desired backend
|
||||
(webkit/webengine).
|
||||
|
||||
Profiling
|
||||
~~~~~~~~~
|
||||
|
||||
|
|
@ -219,7 +231,8 @@ Useful websites
|
|||
|
||||
Some resources which might be handy:
|
||||
|
||||
* https://doc.qt.io/qt-5/classes.html[The Qt5 reference]
|
||||
* https://doc.qt.io/qt-6/classes.html[The Qt 6 reference]
|
||||
* https://doc.qt.io/qt-5/classes.html[The Qt 5 reference]
|
||||
* https://docs.python.org/3/library/index.html[The Python reference]
|
||||
* https://httpbin.org/[httpbin, a test service for HTTP requests/responses]
|
||||
* https://requestbin.com/[RequestBin, a service to inspect HTTP requests]
|
||||
|
|
@ -274,7 +287,7 @@ Other
|
|||
Languages] (https://www.rfc-editor.org/errata_search.php?rfc=5646[Errata])
|
||||
* https://www.w3.org/TR/CSS2/[Cascading Style Sheets Level 2 Revision 1 (CSS
|
||||
2.1) Specification]
|
||||
* https://doc.qt.io/qt-5/stylesheet-reference.html[Qt Style Sheets Reference]
|
||||
* https://doc.qt.io/qt-6/stylesheet-reference.html[Qt Style Sheets Reference]
|
||||
* https://mimesniff.spec.whatwg.org/[MIME Sniffing Standard]
|
||||
* https://spec.whatwg.org/[WHATWG specifications]
|
||||
* https://www.w3.org/html/wg/drafts/html/master/Overview.html[HTML 5.1 Nightly]
|
||||
|
|
@ -353,7 +366,7 @@ All objects can be printed by starting with the `--debug` flag and using the
|
|||
|
||||
The registry is mainly used for <<commands,command handlers>>, but it can
|
||||
also be useful in places where using Qt's
|
||||
https://doc.qt.io/qt-5/signalsandslots.html[signals and slots] mechanism would
|
||||
https://doc.qt.io/qt-6/signalsandslots.html[signals and slots] mechanism would
|
||||
be difficult.
|
||||
|
||||
Logging
|
||||
|
|
@ -600,11 +613,14 @@ This is mostly useful for qutebrowser maintainers to work around issues in Qt -
|
|||
The hierarchy of widgets when QtWebEngine is involved looks like this:
|
||||
|
||||
- qutebrowser has a `WebEngineTab` object, which is its abstraction over QtWebKit/QtWebEngine.
|
||||
- The `WebEngineTab` has a `_widget` attribute, which is the https://doc.qt.io/qt-5/qwebengineview.html[QWebEngineView]
|
||||
- That view has a https://doc.qt.io/qt-5/qwebenginepage.html[QWebEnginePage] for everything which doesn't require rendering.
|
||||
- The view also has a layout with exactly one element (which also is its `focusProxy()`)
|
||||
- That element is the https://code.qt.io/cgit/qt/qtwebengine.git/tree/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp[RenderWidgetHostViewQtDelegateWidget] (it inherits https://doc.qt.io/qt-5/qquickwidget.html[QQuickWidget]) - also often referred to as RWHV or RWHVQDW. It can be obtained via `sip.cast(tab._widget.focusProxy(), QQuickWidget)`.
|
||||
- Calling `rootObject()` on that gives us the https://doc.qt.io/qt-5/qquickitem.html[QQuickItem] where Chromium renders into (?). With it, we can do things like `.setRotation(20)`.
|
||||
- The `WebEngineTab` has a `_widget` attribute, which is the https://doc.qt.io/qt-6/qwebengineview.html[QWebEngineView]
|
||||
- That view has a https://doc.qt.io/qt-6/qwebenginepage.html[QWebEnginePage] for everything which doesn't require rendering.
|
||||
- The view also has a layout with exactly one element (which also is its `focusProxy()`).
|
||||
- Qt 5: That element is the https://code.qt.io/cgit/qt/qtwebengine.git/tree/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp?h=5.15[RenderWidgetHostViewQtDelegateWidget] (it inherits https://doc.qt.io/qt-6/qquickwidget.html[QQuickWidget]) - also often referred to as RWHV or RWHVQDW.
|
||||
It can be obtained via `sip.cast(tab._widget.focusProxy(), QQuickWidget)`.
|
||||
- Qt 6: That element is the https://code.qt.io/cgit/qt/qtwebengine.git/tree/src/webenginewidgets/api/qwebengineview.cpp[WebEngineQuickWidget] (it inherits https://doc.qt.io/qt-6/qquickwidget.html[QQuickWidget]).
|
||||
It can be obtained via `tab._widget.focusProxy()`.
|
||||
- Calling `rootObject()` on that gives us the https://doc.qt.io/qt-6/qquickitem.html[QQuickItem] where Chromium renders into (?). With it, we can do things like `.setRotation(20)`.
|
||||
|
||||
Style conventions
|
||||
-----------------
|
||||
|
|
@ -676,6 +692,41 @@ Return:
|
|||
- `__magic__` methods
|
||||
- other methods
|
||||
- overrides of Qt methods
|
||||
* Type hinting: the qutebrowser codebase uses type hints liberally to enable
|
||||
static type checking and autocompletion in editors.
|
||||
- We use http://mypy-lang.org/[mypy] in CI jobs to perform static type
|
||||
checking.
|
||||
- Not all of the codebase is covered by type hints currently. We encourage
|
||||
including type hints on all new code and even adding type hints to
|
||||
existing code if you find yourself working on some that isn't already
|
||||
covered. There are some module specific rules in the mypy config file,
|
||||
`.mypy.ini`, to make type hints strictly required in some areas.
|
||||
- More often than not mypy is correct when it raises issues. But don't be
|
||||
afraid to add `# type: ignore[...]` statements or casts if you need to.
|
||||
As an optional part of the language not all type information from third
|
||||
parties is always correct. Mypy will raise a new issue if it spots an
|
||||
"ignore" statement which is no longer needed because the underlying
|
||||
issue has been resolved.
|
||||
- One area where we have to take particular care is in code that deals
|
||||
with differences between PyQt5 and PyQt6. We try to write most code in a
|
||||
way that will work with either backend but when you need to deal with
|
||||
differences you should use a pattern like:
|
||||
+
|
||||
[source,python]
|
||||
----
|
||||
if machinery.IS_QT5:
|
||||
... # do PyQt5 specific implementation
|
||||
else:
|
||||
# PyQt6
|
||||
... # do PyQt6 specific implementation
|
||||
----
|
||||
+
|
||||
then you have to https://mypy.readthedocs.io/en/latest/command_line.html#cmdoption-mypy-always-true[tell]
|
||||
mypy to treat `machinery.IS_QT5` as a constant value then run mypy twice to
|
||||
cover both branches. There are a handful of variables in
|
||||
`qutebrowser/qt/machinery.py` that mypy needs to know about. There are tox
|
||||
jobs (`mypy-pyqt5` and `mypy-pyqt6`) that take care of telling mypy to use
|
||||
them as constants.
|
||||
|
||||
Checklists
|
||||
----------
|
||||
|
|
|
|||
|
|
@ -337,8 +337,13 @@ Remove a key from a dict.
|
|||
|
||||
[[config-diff]]
|
||||
=== config-diff
|
||||
Syntax: +:config-diff [*--include-hidden*]+
|
||||
|
||||
Show all customized options.
|
||||
|
||||
==== optional arguments
|
||||
* +*-i*+, +*--include-hidden*+: Also include internal qutebrowser settings.
|
||||
|
||||
[[config-edit]]
|
||||
=== config-edit
|
||||
Syntax: +:config-edit [*--no-source*]+
|
||||
|
|
@ -821,7 +826,7 @@ Show an error message in the statusbar.
|
|||
* +'text'+: The text to show.
|
||||
|
||||
==== optional arguments
|
||||
* +*-r*+, +*--rich*+: Render the given text as https://doc.qt.io/qt-5/richtext-html-subset.html[Qt Rich Text].
|
||||
* +*-r*+, +*--rich*+: Render the given text as https://doc.qt.io/qt-6/richtext-html-subset.html[Qt Rich Text].
|
||||
|
||||
|
||||
[[message-info]]
|
||||
|
|
@ -834,7 +839,7 @@ Show an info message in the statusbar.
|
|||
* +'text'+: The text to show.
|
||||
|
||||
==== optional arguments
|
||||
* +*-r*+, +*--rich*+: Render the given text as https://doc.qt.io/qt-5/richtext-html-subset.html[Qt Rich Text].
|
||||
* +*-r*+, +*--rich*+: Render the given text as https://doc.qt.io/qt-6/richtext-html-subset.html[Qt Rich Text].
|
||||
|
||||
|
||||
==== count
|
||||
|
|
@ -850,7 +855,7 @@ Show a warning message in the statusbar.
|
|||
* +'text'+: The text to show.
|
||||
|
||||
==== optional arguments
|
||||
* +*-r*+, +*--rich*+: Render the given text as https://doc.qt.io/qt-5/richtext-html-subset.html[Qt Rich Text].
|
||||
* +*-r*+, +*--rich*+: Render the given text as https://doc.qt.io/qt-6/richtext-html-subset.html[Qt Rich Text].
|
||||
|
||||
|
||||
[[messages]]
|
||||
|
|
@ -2176,7 +2181,7 @@ Syntax: +:debug-webaction 'action'+
|
|||
|
||||
Execute a webaction.
|
||||
|
||||
Available actions: https://doc.qt.io/archives/qt-5.5/qwebpage.html#WebAction-enum (WebKit) https://doc.qt.io/qt-5/qwebenginepage.html#WebAction-enum (WebEngine)
|
||||
Available actions: https://doc.qt.io/archives/qt-5.5/qwebpage.html#WebAction-enum (WebKit) https://doc.qt.io/qt-6/qwebenginepage.html#WebAction-enum (WebEngine)
|
||||
|
||||
==== positional arguments
|
||||
* +'action'+: The action to execute, e.g. MoveToNextChar.
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@
|
|||
|<<colors.webpage.darkmode.enabled,colors.webpage.darkmode.enabled>>|Render all web contents using a dark theme.
|
||||
|<<colors.webpage.darkmode.grayscale.all,colors.webpage.darkmode.grayscale.all>>|Render all colors as grayscale.
|
||||
|<<colors.webpage.darkmode.grayscale.images,colors.webpage.darkmode.grayscale.images>>|Desaturation factor for images in dark mode.
|
||||
|<<colors.webpage.darkmode.increase_text_contrast,colors.webpage.darkmode.increase_text_contrast>>|Increase text contrast by drawing an outline of the uninverted color.
|
||||
|<<colors.webpage.darkmode.policy.images,colors.webpage.darkmode.policy.images>>|Which images to apply dark mode to.
|
||||
|<<colors.webpage.darkmode.policy.page,colors.webpage.darkmode.policy.page>>|Which pages to apply dark mode to.
|
||||
|<<colors.webpage.darkmode.threshold.background,colors.webpage.darkmode.threshold.background>>|Threshold for inverting background elements with dark mode.
|
||||
|
|
@ -1671,6 +1672,9 @@ Example configurations from Chromium's `chrome://flags`:
|
|||
- "With selective inversion of everything": Combines the two variants
|
||||
above.
|
||||
|
||||
- "With increased text contrast": Set
|
||||
`colors.webpage.darkmode.increase_text_contrast` (QtWebEngine 6.3+)
|
||||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
|
@ -1699,14 +1703,26 @@ If set to 0, images are left as-is. If set to 1, images are completely grayscale
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Float>>
|
||||
|
||||
Default: +pass:[0.0]+
|
||||
|
||||
[[colors.webpage.darkmode.increase_text_contrast]]
|
||||
=== colors.webpage.darkmode.increase_text_contrast
|
||||
Increase text contrast by drawing an outline of the uninverted color.
|
||||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 6.3 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[colors.webpage.darkmode.policy.images]]
|
||||
=== colors.webpage.darkmode.policy.images
|
||||
Which images to apply dark mode to.
|
||||
|
|
@ -1733,9 +1749,7 @@ The underlying Chromium setting has been removed in QtWebEngine 5.15.3, thus thi
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
|
|
@ -1754,9 +1768,7 @@ Note: This behavior is the opposite of `colors.webpage.darkmode.threshold.text`!
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
|
|
@ -1769,9 +1781,7 @@ Text colors with brightness below this threshold will be inverted, and above it
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
|
|
@ -1785,9 +1795,7 @@ The "auto" value is broken on QtWebEngine 5.15.2 due to a Qt bug. There, it will
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
|
|
@ -2274,7 +2282,7 @@ Type: <<types,String>>
|
|||
|
||||
Valid values:
|
||||
|
||||
* +always+: Always send the Referer.
|
||||
* +always+: Always send the Referer. With QtWebEngine 6.2+, this value is unavailable and will act like `same-domain`.
|
||||
* +never+: Never send the Referer. This is not recommended, as some sites may break.
|
||||
* +same-domain+: Only send the Referer for the same domain. This will still protect your privacy, but shouldn't break any sites. With QtWebEngine, the referer will still be sent for other domains, but with stripped path information.
|
||||
|
||||
|
|
@ -2579,8 +2587,6 @@ Allow websites to show notifications.
|
|||
|
||||
This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.13 or newer.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2595,8 +2601,6 @@ Default: +pass:[ask]+
|
|||
=== content.notifications.presenter
|
||||
What notification presenter to use for web notifications.
|
||||
Note that not all implementations support all features of notifications:
|
||||
- With PyQt 5.14, any setting other than `qt` does not support the `click` and
|
||||
`close` events, as well as the `tag` option to replace existing notifications.
|
||||
- The `qt` and `systray` options only support showing one notification at the time
|
||||
and ignore the `tag` option to replace existing notifications.
|
||||
- The `herbe` option only supports showing one notification at the time and doesn't
|
||||
|
|
@ -2604,16 +2608,14 @@ Note that not all implementations support all features of notifications:
|
|||
- The `messages` option doesn't show icons and doesn't support the `click` and
|
||||
`close` events.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +auto+: Tries `libnotify`, `systray` and `messages`, uses the first one available without showing error messages.
|
||||
* +qt+: Use Qt's native notification presenter, based on a system tray icon. Switching from or to this value requires a restart of qutebrowser. Recommended over `systray` on PyQt 5.14.
|
||||
* +qt+: Use Qt's native notification presenter, based on a system tray icon. Switching from or to this value requires a restart of qutebrowser.
|
||||
* +libnotify+: Shows messages via DBus in a libnotify-compatible way. If DBus isn't available, falls back to `systray` or `messages`, but shows an error message.
|
||||
* +systray+: Use a notification presenter based on a systray icon. Falls back to `libnotify` or `messages` if not systray is available. This is a reimplementation of the `qt` setting value, but with the possibility to switch to it at runtime.
|
||||
* +messages+: Show notifications as qutebrowser messages. Most notification features aren't available.
|
||||
|
|
@ -2629,9 +2631,7 @@ Note that with the `qt` presenter, origins are never shown.
|
|||
|
||||
This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
|
|
@ -2684,9 +2684,7 @@ On Windows, if this setting is set to False, the system-wide animation setting i
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
|
|
@ -3635,9 +3633,7 @@ On Linux, disabling this also disables Chromium's MPRIS integration.
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
|
|
@ -3853,7 +3849,7 @@ Alternative process models use less resources, but decrease security and robustn
|
|||
See the following pages for more details:
|
||||
|
||||
- https://www.chromium.org/developers/design-documents/process-models
|
||||
- https://doc.qt.io/qt-5/qtwebengine-features.html#process-models
|
||||
- https://doc.qt.io/qt-6/qtwebengine-features.html#process-models
|
||||
|
||||
This setting requires a restart.
|
||||
|
||||
|
|
@ -3950,7 +3946,7 @@ Default: +pass:[none]+
|
|||
[[qt.highdpi]]
|
||||
=== qt.highdpi
|
||||
Turn on Qt HighDPI scaling.
|
||||
This is equivalent to setting QT_AUTO_SCREEN_SCALE_FACTOR=1 or QT_ENABLE_HIGHDPI_SCALING=1 (Qt >= 5.14) in the environment.
|
||||
This is equivalent to setting QT_ENABLE_HIGHDPI_SCALING=1 (Qt >= 5.14) in the environment.
|
||||
It's off by default as it can cause issues with some bitmap fonts. As an alternative to this, it's possible to set font sizes and the `zoom.default` setting.
|
||||
|
||||
This setting requires a restart.
|
||||
|
|
@ -4033,8 +4029,6 @@ Default: +pass:[true]+
|
|||
=== search.wrap
|
||||
Wrap around at the top and bottom of the page when advancing through text matches using `:search-next` and `:search-prev`.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
|
@ -4809,7 +4803,7 @@ When setting from a string, pass a json-like list, e.g. `["one", "two"]`.
|
|||
|Proxy|A proxy URL, or `system`/`none`.
|
||||
|QssColor|A color value supporting gradients.
|
||||
|
||||
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#AARRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in https://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color) * `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages) * `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359) * A gradient as explained in https://doc.qt.io/qt-5/stylesheet-reference.html#list-of-property-types[the Qt documentation] under ``Gradient''
|
||||
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#AARRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in https://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color) * `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages) * `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359) * A gradient as explained in https://doc.qt.io/qt-6/stylesheet-reference.html#list-of-property-types[the Qt documentation] under ``Gradient''
|
||||
|QtColor|A color value.
|
||||
|
||||
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#AARRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in https://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color) * `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages) * `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ show it.
|
|||
Which backend to use.
|
||||
|
||||
*--desktop-file-name* 'DESKTOP_FILE_NAME'::
|
||||
Set the base name of the desktop entry for this application. Used to set the app_id under Wayland. See https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop
|
||||
Set the base name of the desktop entry for this application. Used to set the app_id under Wayland. See https://doc.qt.io/qt-6/qguiapplication.html#desktopFileName-prop
|
||||
|
||||
*--untrusted-args*::
|
||||
Mark all following arguments as untrusted, which enforces that they are URLs/search terms (and not flags or commands)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ def get_data_files():
|
|||
|
||||
|
||||
def get_hidden_imports():
|
||||
imports = ['PyQt5.QtOpenGL', 'PyQt5._QOpenGLFunctions_2_0']
|
||||
imports = [] if "PYINSTALLER_QT6" in os.environ else ['PyQt5.QtOpenGL']
|
||||
for info in loader.walk_components():
|
||||
imports.append(info.name)
|
||||
return imports
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.12.3 # rq.filter: < 5.13
|
||||
PyQt5-sip==12.11.1
|
||||
PyQtWebEngine==5.12.1 # rq.filter: < 5.13
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#@ filter: PyQt5 < 5.13
|
||||
#@ filter: PyQtWebEngine < 5.13
|
||||
PyQt5 >= 5.12, < 5.13
|
||||
PyQtWebEngine >= 5.12, < 5.13
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.13.2 # rq.filter: < 5.14
|
||||
PyQt5-sip==12.11.1
|
||||
PyQtWebEngine==5.13.2 # rq.filter: < 5.14
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#@ filter: PyQt5 < 5.14
|
||||
#@ filter: PyQtWebEngine < 5.14
|
||||
PyQt5 >= 5.13, < 5.14
|
||||
PyQtWebEngine >= 5.13, < 5.14
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.14.2 # rq.filter: < 5.15
|
||||
PyQt5-sip==12.11.1
|
||||
PyQtWebEngine==5.14.0 # rq.filter: < 5.15
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#@ filter: PyQt5 < 5.15
|
||||
#@ filter: PyQtWebEngine < 5.15
|
||||
PyQt5 >= 5.14, < 5.15
|
||||
PyQtWebEngine >= 5.14, < 5.15
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.0 # rq.filter: == 5.15.0
|
||||
PyQt5-sip==12.11.1
|
||||
PyQtWebEngine==5.15.0 # rq.filter: == 5.15.0
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#@ filter: PyQt5 == 5.15.0
|
||||
#@ filter: PyQtWebEngine == 5.15.0
|
||||
PyQt5 == 5.15.0
|
||||
PyQtWebEngine == 5.15.0
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.2 # rq.filter: == 5.15.2
|
||||
PyQt5-sip==12.10.1
|
||||
PyQtWebEngine==5.15.2 # rq.filter: == 5.15.2
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#@ filter: PyQt5 == 5.15.2
|
||||
#@ filter: PyQtWebEngine == 5.15.2
|
||||
PyQt5 == 5.15.2
|
||||
PyQtWebEngine == 5.15.2
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.6
|
||||
PyQt5-Qt5==5.15.2
|
||||
PyQt5-sip==12.10.1
|
||||
PyQtWebEngine==5.15.5
|
||||
PyQtWebEngine-Qt5==5.15.2
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
PyQt5
|
||||
PyQtWebEngine
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt6==6.2.3
|
||||
PyQt6-Qt6==6.2.4
|
||||
PyQt6-sip==13.3.1
|
||||
PyQt6-WebEngine==6.2.1
|
||||
PyQt6-WebEngine-Qt6==6.2.4
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
PyQt6 >= 6.2, < 6.3
|
||||
PyQt6-Qt6 >= 6.2, < 6.3
|
||||
PyQt6-WebEngine >= 6.2, < 6.3
|
||||
PyQt6-WebEngine-Qt6 >= 6.2, < 6.3
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt6==6.3.1
|
||||
PyQt6-Qt6==6.3.1
|
||||
PyQt6-sip==13.4.0
|
||||
PyQt6-WebEngine==6.3.1
|
||||
PyQt6-WebEngine-Qt6==6.3.1
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
PyQt6 >= 6.3, < 6.4
|
||||
PyQt6-Qt6 >= 6.3, < 6.4
|
||||
PyQt6-WebEngine >= 6.3, < 6.4
|
||||
PyQt6-WebEngine-Qt6 >= 6.3, < 6.4
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt6==6.4.0
|
||||
PyQt6-Qt6==6.4.1
|
||||
PyQt6-sip==13.4.0
|
||||
PyQt6-WebEngine==6.4.0
|
||||
PyQt6-WebEngine-Qt6==6.4.1
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
PyQt6 >= 6.4, < 6.5
|
||||
PyQt6-Qt6 >= 6.4, < 6.5
|
||||
PyQt6-WebEngine >= 6.4, < 6.5
|
||||
PyQt6-WebEngine-Qt6 >= 6.4, < 6.5
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt6==6.3.1
|
||||
PyQt6-Qt6==6.3.1
|
||||
PyQt6-sip==13.4.0
|
||||
PyQt6-WebEngine==6.3.1
|
||||
PyQt6-WebEngine-Qt6==6.3.1
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
PyQt6
|
||||
PyQt6-Qt6
|
||||
PyQt6-WebEngine
|
||||
PyQt6-WebEngine-Qt6
|
||||
|
|
@ -2,6 +2,8 @@ Jinja2
|
|||
PyYAML
|
||||
|
||||
## Only used on macOS to make borderless windows resizable
|
||||
# Not needed anymore with Qt 6.3, but we can't express that
|
||||
# here, and it won't hurt either.
|
||||
## our recompile_requirements.py can't really deal with
|
||||
## platform-specific dependencies unfortunately...
|
||||
# pyobjc-core
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"defineConstant": {
|
||||
"USE_PYQT6": false,
|
||||
"USE_PYQT5": true,
|
||||
"USE_PYSIDE2": false,
|
||||
"USE_PYSIDE6": false,
|
||||
"IS_QT5": true,
|
||||
"IS_QT6": false
|
||||
}
|
||||
}
|
||||
55
pytest.ini
55
pytest.ini
|
|
@ -27,7 +27,6 @@ markers =
|
|||
no_ci: Tests which should not run on CI.
|
||||
qtwebengine_todo: Features still missing with QtWebEngine
|
||||
qtwebengine_skip: Tests not applicable with QtWebEngine
|
||||
qtwebengine_notifications: Tests which need QtWebEngine notification support
|
||||
qtwebkit_skip: Tests not applicable with QtWebKit
|
||||
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
|
||||
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
|
||||
|
|
@ -35,58 +34,30 @@ markers =
|
|||
no_invalid_lines: Don't fail on unparsable lines in end2end tests
|
||||
fake_os: Fake utils.is_* to a fake operating system
|
||||
unicode_locale: Tests which need a unicode locale to work
|
||||
js_headers: Sets JS headers dynamically on QtWebEngine (unsupported on some versions)
|
||||
qtwebkit_pdf_imageformat_skip: Broken on QtWebKit with PDF image format plugin installed
|
||||
qtwebkit_openssl3_skip: Broken due to cheroot bug with OpenSSL 3 on QtWebKit
|
||||
windows_skip: Tests which should be skipped on Windows
|
||||
qt5_only: Tests which should only run with Qt 5
|
||||
qt6_only: Tests which should only run with Qt 6
|
||||
qt5_xfail: Tests which fail with Qt 5
|
||||
qt6_xfail: Tests which fail with Qt 6
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
^SpellCheck: .*
|
||||
^SetProcessDpiAwareness failed: .*
|
||||
^QWindowsWindow::setGeometry(Dp)?: Unable to set geometry .*
|
||||
^QProcess: Destroyed while process .* is still running\.
|
||||
^"Method "GetAll" with signature "s" on interface "org\.freedesktop\.DBus\.Properties" doesn't exist
|
||||
^"Method \\"GetAll\\" with signature \\"s\\" on interface \\"org\.freedesktop\.DBus\.Properties\\" doesn't exist\\n"
|
||||
^propsReply "Method \\"GetAll\\" with signature \\"s\\" on interface \\"org\.freedesktop\.DBus\.Properties\\" doesn't exist\\n"
|
||||
^nmReply "Method \\"GetDevices\\" with signature \\"\\" on interface \\"org\.freedesktop\.NetworkManager\\" doesn't exist\\n"
|
||||
^"Object path cannot be empty"
|
||||
^virtual void QSslSocketBackendPrivate::transmit\(\) SSL write failed with error: -9805
|
||||
^virtual void QSslSocketBackendPrivate::transmit\(\) SSLRead failed with: -9805
|
||||
^Type conversion already registered from type .*
|
||||
^QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once\.
|
||||
^QWaitCondition: Destroyed while threads are still waiting
|
||||
^QXcbXSettings::QXcbXSettings\(QXcbScreen\*\) Failed to get selection owner for XSETTINGS_S atom
|
||||
# GitHub Actions
|
||||
^QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to .*
|
||||
^QObject::connect: Cannot connect \(null\)::stateChanged\(QNetworkSession::State\) to QNetworkReplyHttpImpl::_q_networkSessionStateChanged\(QNetworkSession::State\)
|
||||
^QXcbClipboard: Cannot transfer data, no data available
|
||||
^load glyph failed
|
||||
^Error when parsing the netrc file
|
||||
^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST=
|
||||
^QPainter::end: Painter ended with \d+ saved states
|
||||
^QSslSocket: cannot resolve .*
|
||||
^QSslSocket: cannot call unresolved function .*
|
||||
^Incompatible version of OpenSSL
|
||||
^QQuickWidget::invalidateRenderControl could not make context current
|
||||
^libpng warning: iCCP: known incorrect sRGB profile
|
||||
^inotify_add_watch\(".*"\) failed: "No space left on device"
|
||||
^QSettings::value: Empty key passed
|
||||
^Icon theme ".*" not found
|
||||
^Error receiving trust for a CA certificate
|
||||
^QBackingStore::endPaint\(\) called with active painter.*
|
||||
^QPaintDevice: Cannot destroy paint device that is being painted
|
||||
^DirectWrite: CreateFontFaceFromHDC\(\) failed .*
|
||||
^Attribute Qt::AA_ShareOpenGLContexts must be set before QCoreApplication is created\.
|
||||
^QHttpNetworkConnectionPrivate::_q_hostLookupFinished could not de-queue request, failed to report HostNotFoundError
|
||||
# test_on_focus_changed_issue1484 on macOS
|
||||
^The available OpenGL surface format was either not version 3\.2 or higher or not a Core Profile.*
|
||||
# tests/unit/mainwindow/test_messageview.py and
|
||||
# tests/unit/mainwindow/statusbar/test_textbase.py::test_resize
|
||||
# on Windows
|
||||
^QWindowsWindow::setGeometry(Dp)?: Unable to set geometry .*
|
||||
# tests/unit/commands/test_userscripts.py::test_killed_command
|
||||
# on Windows
|
||||
^QProcess: Destroyed while process .* is still running\.
|
||||
xfail_strict = true
|
||||
filterwarnings =
|
||||
error
|
||||
default:Test process .* failed to terminate!:UserWarning
|
||||
ignore:_SixMetaPathImporter\.exec_module\(\) not found; falling back to load_module\(\):ImportWarning
|
||||
ignore:VendorImporter\.find_spec\(\) not found; falling back to find_module\(\):ImportWarning
|
||||
ignore:_SixMetaPathImporter\.find_spec\(\) not found; falling back to find_module\(\):ImportWarning
|
||||
# https://github.com/ionelmc/python-hunter/issues/97
|
||||
ignore:The distutils\.sysconfig module is deprecated, use sysconfig instead:DeprecationWarning
|
||||
# https://github.com/certifi/python-certifi/issues/170
|
||||
ignore:path is deprecated\. Use files\(\) instead\. Refer to https.//importlib-resources\.readthedocs\.io/en/latest/using\.html#migrating-from-legacy for migration advice\.:DeprecationWarning:certifi.core
|
||||
# https://github.com/HypothesisWorks/hypothesis/issues/3309
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from typing import cast, Any
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from qutebrowser.qt.core import QUrl
|
||||
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
import io
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QUrl
|
||||
from qutebrowser.qt.core import QObject, pyqtSignal, pyqtSlot, QUrl
|
||||
|
||||
from qutebrowser.browser import downloads, qtnetworkdownloads
|
||||
from qutebrowser.utils import objreg
|
||||
|
|
|
|||
|
|
@ -46,9 +46,10 @@ import datetime
|
|||
import argparse
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QWidget
|
||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QObject, QEvent, pyqtSignal, Qt
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.widgets import QApplication, QWidget
|
||||
from qutebrowser.qt.gui import QDesktopServices, QPixmap, QIcon
|
||||
from qutebrowser.qt.core import pyqtSlot, QUrl, QObject, QEvent, pyqtSignal, Qt
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.commands import runners
|
||||
|
|
@ -149,7 +150,6 @@ def init(*, args: argparse.Namespace) -> None:
|
|||
quitter.instance.shutting_down.connect(QApplication.closeAllWindows)
|
||||
|
||||
_init_icon()
|
||||
_init_pulseaudio()
|
||||
|
||||
loader.init()
|
||||
loader.load_components()
|
||||
|
|
@ -195,27 +195,12 @@ def _init_icon():
|
|||
objects.qapp.setWindowIcon(icon)
|
||||
|
||||
|
||||
def _init_pulseaudio():
|
||||
"""Set properties for PulseAudio.
|
||||
|
||||
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-85363
|
||||
|
||||
Affected Qt versions:
|
||||
- Older than 5.11 (which is unsupported)
|
||||
- 5.14.0 to 5.15.0 (inclusive)
|
||||
|
||||
However, we set this on all versions so that qutebrowser's icon gets picked
|
||||
up as well.
|
||||
"""
|
||||
for prop in ['application.name', 'application.icon_name']:
|
||||
os.environ['PULSE_PROP_OVERRIDE_' + prop] = 'qutebrowser'
|
||||
|
||||
|
||||
def _process_args(args):
|
||||
"""Open startpage etc. and process commandline args."""
|
||||
if not args.override_restore:
|
||||
sessions.load_default(args.session)
|
||||
|
||||
new_window = None
|
||||
if not sessions.session_manager.did_load:
|
||||
log.init.debug("Initializing main window...")
|
||||
private = args.target == 'private-window'
|
||||
|
|
@ -226,15 +211,17 @@ def _process_args(args):
|
|||
error.handle_fatal_exc(err, 'Cannot start in private mode',
|
||||
no_err_windows=args.no_err_windows)
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
window = mainwindow.MainWindow(private=private)
|
||||
if not args.nowindow:
|
||||
window.show()
|
||||
objects.qapp.setActiveWindow(window)
|
||||
|
||||
new_window = mainwindow.MainWindow(private=private)
|
||||
|
||||
process_pos_args(args.command)
|
||||
_open_startpage()
|
||||
_open_special_pages(args)
|
||||
|
||||
if new_window is not None and not args.nowindow:
|
||||
new_window.show()
|
||||
objects.qapp.setActiveWindow(new_window)
|
||||
|
||||
delta = datetime.datetime.now() - earlyinit.START_TIME
|
||||
log.init.debug("Init finished after {}s".format(delta.total_seconds()))
|
||||
|
||||
|
|
@ -258,26 +245,30 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
|||
if command_target in {'window', 'private-window'}:
|
||||
command_target = 'tab-silent'
|
||||
|
||||
win_id: Optional[int] = None
|
||||
window: Optional[mainwindow.MainWindow] = None
|
||||
|
||||
if via_ipc and (not args or args == ['']):
|
||||
win_id = mainwindow.get_window(via_ipc=via_ipc,
|
||||
target=new_window_target)
|
||||
_open_startpage(win_id)
|
||||
window = mainwindow.get_window(via_ipc=via_ipc, target=new_window_target)
|
||||
_open_startpage(window)
|
||||
window.show()
|
||||
window.maybe_raise()
|
||||
return
|
||||
|
||||
for cmd in args:
|
||||
if cmd.startswith(':'):
|
||||
if win_id is None:
|
||||
win_id = mainwindow.get_window(via_ipc=via_ipc,
|
||||
target=command_target)
|
||||
if window is None:
|
||||
window = mainwindow.get_window(via_ipc=via_ipc, target=command_target)
|
||||
# FIXME preserving old behavior, but we probably shouldn't be
|
||||
# doing this...
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/5094
|
||||
window.maybe_raise()
|
||||
|
||||
log.init.debug("Startup cmd {!r}".format(cmd))
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
commandrunner = runners.CommandRunner(window.win_id)
|
||||
commandrunner.run_safely(cmd[1:])
|
||||
elif not cmd:
|
||||
log.init.debug("Empty argument")
|
||||
win_id = mainwindow.get_window(via_ipc=via_ipc,
|
||||
target=new_window_target)
|
||||
window = mainwindow.get_window(via_ipc=via_ipc, target=new_window_target)
|
||||
else:
|
||||
if via_ipc and target_arg and target_arg != 'auto':
|
||||
open_target = target_arg
|
||||
|
|
@ -291,7 +282,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
|||
message.error("Error in startup argument '{}': {}".format(
|
||||
cmd, e))
|
||||
else:
|
||||
win_id = open_url(url, target=open_target, via_ipc=via_ipc)
|
||||
window = open_url(url, target=open_target, via_ipc=via_ipc)
|
||||
|
||||
|
||||
def open_url(url, target=None, no_raise=False, via_ipc=True):
|
||||
|
|
@ -304,39 +295,37 @@ def open_url(url, target=None, no_raise=False, via_ipc=True):
|
|||
via_ipc: Whether the arguments were transmitted over IPC.
|
||||
|
||||
Return:
|
||||
ID of a window that was used to open URL
|
||||
The MainWindow of a window that was used to open the URL.
|
||||
"""
|
||||
target = target or config.val.new_instance_open_target
|
||||
background = target in {'tab-bg', 'tab-bg-silent'}
|
||||
win_id = mainwindow.get_window(via_ipc=via_ipc, target=target,
|
||||
no_raise=no_raise)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
window = mainwindow.get_window(via_ipc=via_ipc, target=target, no_raise=no_raise)
|
||||
log.init.debug("About to open URL: {}".format(url.toDisplayString()))
|
||||
tabbed_browser.tabopen(url, background=background, related=False)
|
||||
return win_id
|
||||
window.tabbed_browser.tabopen(url, background=background, related=False)
|
||||
window.show()
|
||||
window.maybe_raise()
|
||||
return window
|
||||
|
||||
|
||||
def _open_startpage(win_id=None):
|
||||
def _open_startpage(window: Optional[mainwindow.MainWindow] = None) -> None:
|
||||
"""Open startpage.
|
||||
|
||||
The startpage is never opened if the given windows are not empty.
|
||||
|
||||
Args:
|
||||
win_id: If None, open startpage in all empty windows.
|
||||
window: If None, open startpage in all empty windows.
|
||||
If set, open the startpage in the given window.
|
||||
"""
|
||||
if win_id is not None:
|
||||
window_ids: Iterable[int] = [win_id]
|
||||
if window is not None:
|
||||
windows: Iterable[mainwindow.MainWindow] = [window]
|
||||
else:
|
||||
window_ids = objreg.window_registry
|
||||
for cur_win_id in list(window_ids): # Copying as the dict could change
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=cur_win_id)
|
||||
if tabbed_browser.widget.count() == 0:
|
||||
windows = objreg.window_registry.values()
|
||||
|
||||
for cur_window in list(windows): # Copying as the dict could change
|
||||
if cur_window.tabbed_browser.widget.count() == 0:
|
||||
log.init.debug("Opening start pages")
|
||||
for url in config.val.url.start_pages:
|
||||
tabbed_browser.tabopen(url)
|
||||
cur_window.tabbed_browser.tabopen(url)
|
||||
|
||||
|
||||
def _open_special_pages(args):
|
||||
|
|
@ -369,8 +358,17 @@ def _open_special_pages(args):
|
|||
'qute://warning/webkit'),
|
||||
|
||||
('session-warning-shown',
|
||||
qtutils.version_check('5.15', compiled=False),
|
||||
True,
|
||||
'qute://warning/sessions'),
|
||||
|
||||
('sandboxing-warning-shown',
|
||||
(
|
||||
hasattr(sys, "frozen") and
|
||||
utils.is_mac and
|
||||
machinery.IS_QT6 and
|
||||
os.environ.get("QTWEBENGINE_DISABLE_SANDBOX") == "1"
|
||||
),
|
||||
'qute://warning/sandboxing'),
|
||||
]
|
||||
|
||||
if 'quickstart-done' not in general_sect:
|
||||
|
|
@ -432,10 +430,10 @@ def on_focus_changed(_old, new):
|
|||
def open_desktopservices_url(url):
|
||||
"""Handler to open a URL via QDesktopServices."""
|
||||
target = config.val.new_instance_open_target
|
||||
win_id = mainwindow.get_window(via_ipc=True, target=target)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tabbed_browser.tabopen(url)
|
||||
window = mainwindow.get_window(via_ipc=True, target=target)
|
||||
window.tabbed_browser.tabopen(url)
|
||||
window.show()
|
||||
window.maybe_raise()
|
||||
|
||||
|
||||
# This is effectively a @config.change_filter
|
||||
|
|
@ -563,7 +561,12 @@ class Application(QApplication):
|
|||
|
||||
self.launch_time = datetime.datetime.now()
|
||||
self.focusObjectChanged.connect(self.on_focus_object_changed)
|
||||
self.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
||||
|
||||
try:
|
||||
self.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps, True)
|
||||
except AttributeError:
|
||||
# default and removed in Qt 6
|
||||
pass
|
||||
|
||||
self.new_window.connect(self._on_new_window)
|
||||
|
||||
|
|
@ -582,7 +585,7 @@ class Application(QApplication):
|
|||
|
||||
def event(self, e):
|
||||
"""Handle macOS FileOpen events."""
|
||||
if e.type() != QEvent.FileOpen:
|
||||
if e.type() != QEvent.Type.FileOpen:
|
||||
return super().event(e)
|
||||
|
||||
url = e.url()
|
||||
|
|
|
|||
|
|
@ -26,17 +26,17 @@ import dataclasses
|
|||
from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional,
|
||||
Sequence, Set, Type, Union, Tuple)
|
||||
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt,
|
||||
from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt,
|
||||
QEvent, QPoint, QRect)
|
||||
from PyQt5.QtGui import QKeyEvent, QIcon, QPixmap
|
||||
from PyQt5.QtWidgets import QWidget, QApplication, QDialog
|
||||
from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager
|
||||
from qutebrowser.qt.gui import QKeyEvent, QIcon, QPixmap
|
||||
from qutebrowser.qt.widgets import QApplication, QWidget
|
||||
from qutebrowser.qt.printsupport import QPrintDialog, QPrinter
|
||||
from qutebrowser.qt.network import QNetworkAccessManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5.QtWebKit import QWebHistory, QWebHistoryItem
|
||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebView
|
||||
from PyQt5.QtWebEngineWidgets import (
|
||||
from qutebrowser.qt.webkit import QWebHistory, QWebHistoryItem
|
||||
from qutebrowser.qt.webkitwidgets import QWebPage, QWebView
|
||||
from qutebrowser.qt.webenginewidgets import (
|
||||
QWebEngineHistory, QWebEngineHistoryItem, QWebEnginePage, QWebEngineView)
|
||||
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
|
@ -153,7 +153,6 @@ class AbstractAction:
|
|||
|
||||
"""Attribute ``action`` of AbstractTab for Qt WebActions."""
|
||||
|
||||
action_class: Type[Union['QWebPage', 'QWebEnginePage']]
|
||||
action_base: Type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']]
|
||||
|
||||
def __init__(self, tab: 'AbstractTab') -> None:
|
||||
|
|
@ -170,10 +169,10 @@ class AbstractAction:
|
|||
|
||||
def run_string(self, name: str) -> None:
|
||||
"""Run a webaction based on its name."""
|
||||
member = getattr(self.action_class, name, None)
|
||||
if not isinstance(member, self.action_base):
|
||||
raise WebTabError("{} is not a valid web action!".format(name))
|
||||
assert member is not None # for mypy
|
||||
try:
|
||||
member = getattr(self.action_base, name)
|
||||
except AttributeError:
|
||||
raise WebTabError(f"{name} is not a valid web action!")
|
||||
self._widget.triggerPageAction(member)
|
||||
|
||||
def show_source(self, pygments: bool = False) -> None:
|
||||
|
|
@ -226,13 +225,37 @@ class AbstractAction:
|
|||
self._tab.dump_async(show_source_cb)
|
||||
|
||||
|
||||
class AbstractPrinting:
|
||||
class AbstractPrinting(QObject):
|
||||
|
||||
"""Attribute ``printing`` of AbstractTab for printing the page."""
|
||||
|
||||
def __init__(self, tab: 'AbstractTab') -> None:
|
||||
printing_finished = pyqtSignal(bool)
|
||||
pdf_printing_finished = pyqtSignal(str, bool) # filename, ok
|
||||
|
||||
def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._widget = cast(_WidgetType, None)
|
||||
self._tab = tab
|
||||
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: bool) -> None:
|
||||
# Only reporting error here, as the user has feedback from the dialog
|
||||
# (and probably their printer) already.
|
||||
if not ok:
|
||||
message.error("Printing failed!")
|
||||
if self._dialog is not None:
|
||||
self._dialog.deleteLater()
|
||||
self._dialog = None
|
||||
|
||||
@pyqtSlot(str, bool)
|
||||
def _on_pdf_printing_finished(self, path: str, ok: bool) -> None:
|
||||
if ok:
|
||||
message.info(f"Printed to {path}")
|
||||
else:
|
||||
message.error(f"Printing to {path} failed!")
|
||||
|
||||
def check_pdf_support(self) -> None:
|
||||
"""Check whether writing to PDFs is supported.
|
||||
|
|
@ -250,41 +273,23 @@ class AbstractPrinting:
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def to_pdf(self, filename: str) -> bool:
|
||||
def to_pdf(self, filename: str) -> None:
|
||||
"""Print the tab to a PDF with the given filename."""
|
||||
raise NotImplementedError
|
||||
|
||||
def to_printer(self, printer: QPrinter,
|
||||
callback: Callable[[bool], None] = None) -> None:
|
||||
def to_printer(self, printer: QPrinter) -> None:
|
||||
"""Print the tab.
|
||||
|
||||
Args:
|
||||
printer: The QPrinter to print to.
|
||||
callback: Called with a boolean
|
||||
(True if printing succeeded, False otherwise)
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def show_dialog(self) -> None:
|
||||
"""Print with a QPrintDialog."""
|
||||
def print_callback(ok: bool) -> None:
|
||||
"""Called when printing finished."""
|
||||
if not ok:
|
||||
message.error("Printing failed!")
|
||||
diag.deleteLater()
|
||||
|
||||
def do_print() -> None:
|
||||
"""Called when the dialog was closed."""
|
||||
self.to_printer(diag.printer(), print_callback)
|
||||
|
||||
diag = QPrintDialog(self._tab)
|
||||
if utils.is_mac:
|
||||
# For some reason we get a segfault when using open() on macOS
|
||||
ret = diag.exec()
|
||||
if ret == QDialog.Accepted:
|
||||
do_print()
|
||||
else:
|
||||
diag.open(do_print)
|
||||
self._dialog = dialog = QPrintDialog(self._tab)
|
||||
self._dialog.open(lambda: self.to_printer(dialog.printer()))
|
||||
# Gets cleaned up in on_printing_finished
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
|
@ -603,9 +608,9 @@ class AbstractCaret(QObject):
|
|||
def _follow_enter(self, tab: bool) -> None:
|
||||
"""Follow a link by faking an enter press."""
|
||||
if tab:
|
||||
self._tab.fake_key_press(Qt.Key_Enter, modifier=Qt.ControlModifier)
|
||||
self._tab.fake_key_press(Qt.Key.Key_Enter, modifier=Qt.KeyboardModifier.ControlModifier)
|
||||
else:
|
||||
self._tab.fake_key_press(Qt.Key_Enter)
|
||||
self._tab.fake_key_press(Qt.Key.Key_Enter)
|
||||
|
||||
def follow_selected(self, *, tab: bool = False) -> None:
|
||||
raise NotImplementedError
|
||||
|
|
@ -1238,10 +1243,10 @@ class AbstractTab(QWidget):
|
|||
|
||||
def fake_key_press(self,
|
||||
key: Qt.Key,
|
||||
modifier: Qt.KeyboardModifier = Qt.NoModifier) -> None:
|
||||
modifier: Qt.KeyboardModifier = Qt.KeyboardModifier.NoModifier) -> None:
|
||||
"""Send a fake key event to this tab."""
|
||||
press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0)
|
||||
release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier,
|
||||
press_evt = QKeyEvent(QEvent.Type.KeyPress, key, modifier, 0, 0, 0)
|
||||
release_evt = QKeyEvent(QEvent.Type.KeyRelease, key, modifier,
|
||||
0, 0, 0)
|
||||
self.send_event(press_evt)
|
||||
self.send_event(release_evt)
|
||||
|
|
@ -1315,8 +1320,8 @@ class AbstractTab(QWidget):
|
|||
def __repr__(self) -> str:
|
||||
try:
|
||||
qurl = self.url()
|
||||
url = qurl.toDisplayString(
|
||||
QUrl.EncodeUnicode) # type: ignore[arg-type]
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import shlex
|
|||
import functools
|
||||
from typing import cast, Callable, Dict, Union, Optional
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QTabBar
|
||||
from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery
|
||||
from qutebrowser.qt.widgets import QApplication, QTabBar
|
||||
from qutebrowser.qt.core import Qt, QUrl, QEvent, QUrlQuery
|
||||
|
||||
from qutebrowser.commands import userscripts, runners
|
||||
from qutebrowser.api import cmdutils
|
||||
|
|
@ -70,9 +70,7 @@ class CommandDispatcher:
|
|||
raise cmdutils.CommandError("Private windows are unavailable with "
|
||||
"the single-process process model.")
|
||||
|
||||
new_window = mainwindow.MainWindow(private=private)
|
||||
new_window.show()
|
||||
return new_window.tabbed_browser
|
||||
return mainwindow.MainWindow(private=private).tabbed_browser
|
||||
|
||||
def _count(self) -> int:
|
||||
"""Convenience method to get the widget count."""
|
||||
|
|
@ -109,8 +107,15 @@ class CommandDispatcher:
|
|||
raise cmdutils.CommandError("No WebView available yet!")
|
||||
return widget
|
||||
|
||||
def _open(self, url, tab=False, background=False, window=False,
|
||||
related=False, private=None):
|
||||
def _open(
|
||||
self,
|
||||
url: QUrl,
|
||||
tab: bool = False,
|
||||
background: bool = False,
|
||||
window: bool = False,
|
||||
related: bool = False,
|
||||
private: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Helper function to open a page.
|
||||
|
||||
Args:
|
||||
|
|
@ -123,13 +128,15 @@ class CommandDispatcher:
|
|||
"""
|
||||
urlutils.raise_cmdexc_if_invalid(url)
|
||||
tabbed_browser = self._tabbed_browser
|
||||
cmdutils.check_exclusive((tab, background, window, private), 'tbwp')
|
||||
cmdutils.check_exclusive((tab, background, window, private or False), 'tbwp')
|
||||
if window and private is None:
|
||||
private = self._tabbed_browser.is_private
|
||||
|
||||
if window or private:
|
||||
assert isinstance(private, bool)
|
||||
tabbed_browser = self._new_tabbed_browser(private)
|
||||
tabbed_browser.tabopen(url)
|
||||
tabbed_browser.window().show()
|
||||
elif tab:
|
||||
tabbed_browser.tabopen(url, background=False, related=related)
|
||||
elif background:
|
||||
|
|
@ -192,21 +199,21 @@ class CommandDispatcher:
|
|||
what's configured in 'tabs.select_on_remove'.
|
||||
|
||||
Return:
|
||||
QTabBar.SelectLeftTab, QTabBar.SelectRightTab, or None if no change
|
||||
QTabBar.SelectionBehavior.SelectLeftTab, QTabBar.SelectionBehavior.SelectRightTab, or None if no change
|
||||
should be made.
|
||||
"""
|
||||
cmdutils.check_exclusive((prev, next_, opposite), 'pno')
|
||||
if prev:
|
||||
return QTabBar.SelectLeftTab
|
||||
return QTabBar.SelectionBehavior.SelectLeftTab
|
||||
elif next_:
|
||||
return QTabBar.SelectRightTab
|
||||
return QTabBar.SelectionBehavior.SelectRightTab
|
||||
elif opposite:
|
||||
conf_selection = config.val.tabs.select_on_remove
|
||||
if conf_selection == QTabBar.SelectLeftTab:
|
||||
return QTabBar.SelectRightTab
|
||||
elif conf_selection == QTabBar.SelectRightTab:
|
||||
return QTabBar.SelectLeftTab
|
||||
elif conf_selection == QTabBar.SelectPreviousTab:
|
||||
if conf_selection == QTabBar.SelectionBehavior.SelectLeftTab:
|
||||
return QTabBar.SelectionBehavior.SelectRightTab
|
||||
elif conf_selection == QTabBar.SelectionBehavior.SelectRightTab:
|
||||
return QTabBar.SelectionBehavior.SelectLeftTab
|
||||
elif conf_selection == QTabBar.SelectionBehavior.SelectPreviousTab:
|
||||
raise cmdutils.CommandError(
|
||||
"-o is not supported with 'tabs.select_on_remove' set to "
|
||||
"'last-used'!")
|
||||
|
|
@ -403,14 +410,17 @@ class CommandDispatcher:
|
|||
except browsertab.WebTabError as e:
|
||||
raise cmdutils.CommandError(e)
|
||||
|
||||
# The new tab could be in a new tabbed_browser (e.g. because of
|
||||
# tabs.tabs_are_windows being set)
|
||||
if window or private:
|
||||
new_tabbed_browser = self._new_tabbed_browser(
|
||||
private=self._tabbed_browser.is_private or private)
|
||||
else:
|
||||
new_tabbed_browser = self._tabbed_browser
|
||||
|
||||
newtab = new_tabbed_browser.tabopen(background=bg)
|
||||
new_tabbed_browser.window().show()
|
||||
|
||||
# The new tab could be in a new tabbed_browser (e.g. because of
|
||||
# tabs.tabs_are_windows being set)
|
||||
new_tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=newtab.win_id)
|
||||
idx = new_tabbed_browser.widget.indexOf(newtab)
|
||||
|
|
@ -498,6 +508,8 @@ class CommandDispatcher:
|
|||
"The window with id {} is not private".format(win_id))
|
||||
|
||||
tabbed_browser.tabopen(self._current_url())
|
||||
tabbed_browser.window().show()
|
||||
|
||||
if not keep:
|
||||
self._tabbed_browser.close_tab(self._current_widget(),
|
||||
add_undo=False,
|
||||
|
|
@ -706,9 +718,9 @@ class CommandDispatcher:
|
|||
assert what in ['url', 'pretty-url'], what
|
||||
|
||||
if what == 'pretty-url':
|
||||
flags = QUrl.RemovePassword | QUrl.DecodeReserved
|
||||
flags = QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.DecodeReserved
|
||||
else:
|
||||
flags = QUrl.RemovePassword | QUrl.FullyEncoded
|
||||
flags = QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded
|
||||
|
||||
url = QUrl(self._current_url())
|
||||
url_query = QUrlQuery()
|
||||
|
|
@ -1200,7 +1212,7 @@ class CommandDispatcher:
|
|||
except qtutils.QtValueError:
|
||||
pass
|
||||
else:
|
||||
env['QUTE_URL'] = url.toString(QUrl.FullyEncoded)
|
||||
env['QUTE_URL'] = url.toString(QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
|
||||
try:
|
||||
runner = userscripts.run_async(
|
||||
|
|
@ -1330,8 +1342,8 @@ class CommandDispatcher:
|
|||
current page's url.
|
||||
"""
|
||||
if url is None:
|
||||
url = self._current_url().toString(QUrl.RemovePassword |
|
||||
QUrl.FullyEncoded)
|
||||
url = self._current_url().toString(QUrl.UrlFormattingOption.RemovePassword |
|
||||
QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
try:
|
||||
objreg.get('bookmark-manager').delete(url)
|
||||
except KeyError:
|
||||
|
|
@ -1774,8 +1786,8 @@ class CommandDispatcher:
|
|||
raise cmdutils.CommandError(str(e))
|
||||
|
||||
for keyinfo in sequence:
|
||||
press_event = keyinfo.to_event(QEvent.KeyPress)
|
||||
release_event = keyinfo.to_event(QEvent.KeyRelease)
|
||||
press_event = keyinfo.to_event(QEvent.Type.KeyPress)
|
||||
release_event = keyinfo.to_event(QEvent.Type.KeyRelease)
|
||||
|
||||
if global_:
|
||||
window = QApplication.focusWindow()
|
||||
|
|
@ -1882,9 +1894,9 @@ class CommandDispatcher:
|
|||
if not window.isFullScreen():
|
||||
window.state_before_fullscreen = window.windowState()
|
||||
if enter:
|
||||
window.setWindowState(window.windowState() | Qt.WindowFullScreen)
|
||||
window.setWindowState(window.windowState() | Qt.WindowState.WindowFullScreen)
|
||||
else:
|
||||
window.setWindowState(window.windowState() ^ Qt.WindowFullScreen)
|
||||
window.setWindowState(window.windowState() ^ Qt.WindowState.WindowFullScreen)
|
||||
|
||||
log.misc.debug('state before fullscreen: {}'.format(
|
||||
debug.qflags_key(Qt, window.state_before_fullscreen)))
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import tempfile
|
|||
import enum
|
||||
from typing import Any, Dict, IO, List, MutableSequence, Optional, Union
|
||||
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||
from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||
QTimer, QAbstractListModel, QUrl)
|
||||
|
||||
from qutebrowser.browser import pdfjs
|
||||
|
|
@ -45,7 +45,7 @@ class ModelRole(enum.IntEnum):
|
|||
|
||||
"""Custom download model roles."""
|
||||
|
||||
item = Qt.UserRole
|
||||
item = Qt.ItemDataRole.UserRole
|
||||
|
||||
|
||||
# Remember the last used directory
|
||||
|
|
@ -188,7 +188,7 @@ def get_filename_question(*, suggested_filename, url, parent=None):
|
|||
q.title = "Save file to:"
|
||||
q.text = "Please enter a location for <b>{}</b>".format(
|
||||
html.escape(url.toDisplayString()))
|
||||
q.url = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
q.url = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
q.mode = usertypes.PromptMode.download
|
||||
q.completed.connect(q.deleteLater)
|
||||
q.default = _path_suggestion(suggested_filename)
|
||||
|
|
@ -1266,10 +1266,10 @@ class DownloadModel(QAbstractListModel):
|
|||
idx = self.index(self.rowCount() - 1)
|
||||
return idx
|
||||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
|
||||
"""Simple constant header."""
|
||||
if (section == 0 and orientation == Qt.Horizontal and
|
||||
role == Qt.DisplayRole):
|
||||
if (section == 0 and orientation == Qt.Orientation.Horizontal and
|
||||
role == Qt.ItemDataRole.DisplayRole):
|
||||
return "Downloads"
|
||||
else:
|
||||
return ""
|
||||
|
|
@ -1283,15 +1283,15 @@ class DownloadModel(QAbstractListModel):
|
|||
return None
|
||||
|
||||
item = self[index.row()]
|
||||
if role == Qt.DisplayRole:
|
||||
if role == Qt.ItemDataRole.DisplayRole:
|
||||
data: Any = str(item)
|
||||
elif role == Qt.ForegroundRole:
|
||||
elif role == Qt.ItemDataRole.ForegroundRole:
|
||||
data = item.get_status_color('fg')
|
||||
elif role == Qt.BackgroundRole:
|
||||
elif role == Qt.ItemDataRole.BackgroundRole:
|
||||
data = item.get_status_color('bg')
|
||||
elif role == ModelRole.item:
|
||||
data = item
|
||||
elif role == Qt.ToolTipRole:
|
||||
elif role == Qt.ItemDataRole.ToolTipRole:
|
||||
if item.error_msg is None:
|
||||
data = None
|
||||
else:
|
||||
|
|
@ -1303,11 +1303,11 @@ class DownloadModel(QAbstractListModel):
|
|||
def flags(self, index):
|
||||
"""Override flags so items aren't selectable.
|
||||
|
||||
The default would be Qt.ItemIsEnabled | Qt.ItemIsSelectable.
|
||||
The default would be Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable.
|
||||
"""
|
||||
if not index.isValid():
|
||||
return Qt.ItemFlags()
|
||||
return Qt.ItemIsEnabled | Qt.ItemNeverHasChildren
|
||||
return Qt.ItemFlag.NoItemFlags
|
||||
return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemNeverHasChildren
|
||||
|
||||
def rowCount(self, parent=QModelIndex()):
|
||||
"""Get count of active downloads."""
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
import functools
|
||||
from typing import Callable, MutableSequence, Tuple, Union
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QSize, Qt
|
||||
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
||||
from qutebrowser.qt.core import pyqtSlot, QSize, Qt
|
||||
from qutebrowser.qt.widgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.config import stylesheet
|
||||
|
|
@ -62,11 +62,11 @@ class DownloadView(QListView):
|
|||
if not utils.is_mac:
|
||||
self.setStyle(QStyleFactory.create('Fusion'))
|
||||
stylesheet.set_register(self)
|
||||
self.setResizeMode(QListView.Adjust)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
self.setFlow(QListView.LeftToRight)
|
||||
self.setResizeMode(QListView.ResizeMode.Adjust)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Fixed)
|
||||
self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||
self.setFlow(QListView.Flow.LeftToRight)
|
||||
self.setSpacing(1)
|
||||
self._menu = None
|
||||
model.rowsInserted.connect(self._update_geometry)
|
||||
|
|
@ -74,7 +74,7 @@ class DownloadView(QListView):
|
|||
model.dataChanged.connect(self._update_geometry)
|
||||
self.setModel(model)
|
||||
self.setWrapping(True)
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self.show_context_menu)
|
||||
self.clicked.connect(self.on_clicked)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@
|
|||
|
||||
"""Event handling for a browser tab."""
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import QObject, QEvent, Qt, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log, usertypes, qtutils
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.utils import log, message, usertypes
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ class ChildEventFilter(QObject):
|
|||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Act on ChildAdded events."""
|
||||
if event.type() == QEvent.ChildAdded:
|
||||
if event.type() == QEvent.Type.ChildAdded:
|
||||
child = event.child()
|
||||
log.misc.debug("{} got new child {}, installing filter"
|
||||
.format(obj, child))
|
||||
|
|
@ -58,7 +58,7 @@ class ChildEventFilter(QObject):
|
|||
assert obj is self._widget
|
||||
|
||||
child.installEventFilter(self._filter)
|
||||
elif event.type() == QEvent.ChildRemoved:
|
||||
elif event.type() == QEvent.Type.ChildRemoved:
|
||||
child = event.child()
|
||||
log.misc.debug("{}: removed child {}".format(obj, child))
|
||||
|
||||
|
|
@ -81,10 +81,9 @@ class TabEventFilter(QObject):
|
|||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._handlers = {
|
||||
QEvent.MouseButtonPress: self._handle_mouse_press,
|
||||
QEvent.MouseButtonRelease: self._handle_mouse_release,
|
||||
QEvent.Wheel: self._handle_wheel,
|
||||
QEvent.KeyRelease: self._handle_key_release,
|
||||
QEvent.Type.MouseButtonPress: self._handle_mouse_press,
|
||||
QEvent.Type.MouseButtonRelease: self._handle_mouse_release,
|
||||
QEvent.Type.Wheel: self._handle_wheel,
|
||||
}
|
||||
self._ignore_wheel_event = False
|
||||
self._check_insertmode_on_release = False
|
||||
|
|
@ -99,10 +98,13 @@ class TabEventFilter(QObject):
|
|||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
is_rocker_gesture = (config.val.input.mouse.rocker_gestures and
|
||||
e.buttons() == Qt.LeftButton | Qt.RightButton)
|
||||
e.buttons() == Qt.MouseButton.LeftButton | Qt.MouseButton.RightButton)
|
||||
|
||||
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
|
||||
self._mousepress_backforward(e)
|
||||
if e.button() in [Qt.MouseButton.XButton1, Qt.MouseButton.XButton2] or is_rocker_gesture:
|
||||
if not machinery.IS_QT6:
|
||||
self._mousepress_backforward(e)
|
||||
# FIXME:qt6 For some reason, this doesn't filter the action on
|
||||
# Qt 6...
|
||||
return True
|
||||
|
||||
self._ignore_wheel_event = True
|
||||
|
|
@ -112,7 +114,7 @@ class TabEventFilter(QObject):
|
|||
log.mouse.warning("Ignoring invalid click at {}".format(pos))
|
||||
return False
|
||||
|
||||
if e.button() != Qt.NoButton:
|
||||
if e.button() != Qt.MouseButton.NoButton:
|
||||
self._tab.elements.find_at_pos(pos, self._mousepress_insertmode_cb)
|
||||
|
||||
return False
|
||||
|
|
@ -150,7 +152,7 @@ class TabEventFilter(QObject):
|
|||
if mode == usertypes.KeyMode.hint:
|
||||
return True
|
||||
|
||||
elif e.modifiers() & Qt.ControlModifier:
|
||||
elif e.modifiers() & Qt.KeyboardModifier.ControlModifier:
|
||||
if mode == usertypes.KeyMode.passthrough:
|
||||
return False
|
||||
|
||||
|
|
@ -170,21 +172,6 @@ class TabEventFilter(QObject):
|
|||
|
||||
return False
|
||||
|
||||
def _handle_key_release(self, e):
|
||||
"""Ignore repeated key release events going to the website.
|
||||
|
||||
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-77208
|
||||
|
||||
Args:
|
||||
e: The QKeyEvent.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
return (e.isAutoRepeat() and
|
||||
not qtutils.version_check('5.14', compiled=False) and
|
||||
objects.backend == usertypes.Backend.QtWebEngine)
|
||||
|
||||
def _mousepress_insertmode_cb(self, elem):
|
||||
"""Check if the clicked element is editable."""
|
||||
if elem is None:
|
||||
|
|
@ -240,17 +227,17 @@ class TabEventFilter(QObject):
|
|||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
if (not config.val.input.mouse.back_forward_buttons and
|
||||
e.button() in [Qt.XButton1, Qt.XButton2]):
|
||||
e.button() in [Qt.MouseButton.XButton1, Qt.MouseButton.XButton2]):
|
||||
# Back and forward on mice are disabled
|
||||
return
|
||||
|
||||
if e.button() in [Qt.XButton1, Qt.LeftButton]:
|
||||
if e.button() in [Qt.MouseButton.XButton1, Qt.MouseButton.LeftButton]:
|
||||
# Back button on mice which have it, or rocker gesture
|
||||
if self._tab.history.can_go_back():
|
||||
self._tab.history.back()
|
||||
else:
|
||||
message.error("At beginning of history.")
|
||||
elif e.button() in [Qt.XButton2, Qt.RightButton]:
|
||||
elif e.button() in [Qt.MouseButton.XButton2, Qt.MouseButton.RightButton]:
|
||||
# Forward button on mice which have it, or rocker gesture
|
||||
if self._tab.history.can_go_forward():
|
||||
self._tab.history.forward()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import textwrap
|
|||
import dataclasses
|
||||
from typing import cast, List, Sequence, Tuple, Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
from qutebrowser.qt.core import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
|
||||
javascript, urlmatch, version, usertypes, message)
|
||||
|
|
@ -145,7 +145,7 @@ class GreasemonkeyScript:
|
|||
def needs_document_end_workaround(self):
|
||||
"""Check whether to force @run-at document-end.
|
||||
|
||||
This needs to be done on QtWebEngine (since Qt 5.12) for known-broken scripts.
|
||||
This needs to be done on QtWebEngine for known-broken scripts.
|
||||
|
||||
On Qt 5.12, accessing the DOM isn't possible with "@run-at
|
||||
document-start". It was documented to be impossible before, but seems
|
||||
|
|
@ -270,7 +270,7 @@ class GreasemonkeyMatcher:
|
|||
|
||||
def __init__(self, url):
|
||||
self._url = url
|
||||
self._url_string = url.toString(QUrl.FullyEncoded)
|
||||
self._url_string = url.toString(QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
self.is_greaseable = url.scheme() in self.GREASEABLE_SCHEMES
|
||||
|
||||
def _match_pattern(self, pattern):
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ from string import ascii_lowercase
|
|||
from typing import (TYPE_CHECKING, Callable, Dict, Iterable, Iterator, List, Mapping,
|
||||
MutableSequence, Optional, Sequence, Set)
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt, QUrl
|
||||
from PyQt5.QtWidgets import QLabel
|
||||
from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt, QUrl
|
||||
from qutebrowser.qt.widgets import QLabel
|
||||
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.keyinput import modeman, modeparsers, basekeyparser
|
||||
|
|
@ -92,12 +92,12 @@ class HintLabel(QLabel):
|
|||
self._context = context
|
||||
self.elem = elem
|
||||
|
||||
self.setTextFormat(Qt.RichText)
|
||||
self.setTextFormat(Qt.TextFormat.RichText)
|
||||
|
||||
# Make sure we can style the background via a style sheet, and we don't
|
||||
# get any extra text indent from Qt.
|
||||
# The real stylesheet lives in mainwindow.py for performance reasons..
|
||||
self.setAttribute(Qt.WA_StyledBackground, True)
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
|
||||
self.setIndent(0)
|
||||
|
||||
self._context.tab.contents_size_changed.connect(self._move_to_elem)
|
||||
|
|
@ -252,9 +252,9 @@ class HintActions:
|
|||
sel = (context.target == Target.yank_primary and
|
||||
utils.supports_selection())
|
||||
|
||||
flags = QUrl.FullyEncoded | QUrl.RemovePassword
|
||||
flags = QUrl.ComponentFormattingOption.FullyEncoded | QUrl.UrlFormattingOption.RemovePassword
|
||||
if url.scheme() == 'mailto':
|
||||
flags |= QUrl.RemoveScheme # type: ignore[operator]
|
||||
flags |= QUrl.UrlFormattingOption.RemoveScheme # type: ignore[operator]
|
||||
urlstr = url.toString(flags)
|
||||
|
||||
new_content = urlstr
|
||||
|
|
@ -276,14 +276,14 @@ class HintActions:
|
|||
|
||||
def run_cmd(self, url: QUrl, context: HintContext) -> None:
|
||||
"""Run the command based on a hint URL."""
|
||||
urlstr = url.toString(QUrl.FullyEncoded) # type: ignore[arg-type]
|
||||
urlstr = url.toString(QUrl.ComponentFormattingOption.FullyEncoded) # type: ignore[arg-type]
|
||||
args = context.get_args(urlstr)
|
||||
commandrunner = runners.CommandRunner(self._win_id)
|
||||
commandrunner.run_safely(' '.join(args))
|
||||
|
||||
def preset_cmd_text(self, url: QUrl, context: HintContext) -> None:
|
||||
"""Preset a commandline text based on a hint URL."""
|
||||
flags = QUrl.FullyEncoded
|
||||
flags = QUrl.ComponentFormattingOption.FullyEncoded
|
||||
urlstr = url.toDisplayString(flags) # type: ignore[arg-type]
|
||||
args = context.get_args(urlstr)
|
||||
text = ' '.join(args)
|
||||
|
|
@ -325,7 +325,7 @@ class HintActions:
|
|||
|
||||
cmd = context.args[0]
|
||||
args = context.args[1:]
|
||||
flags = QUrl.FullyEncoded
|
||||
flags = QUrl.ComponentFormattingOption.FullyEncoded
|
||||
|
||||
env = {
|
||||
'QUTE_MODE': 'hints',
|
||||
|
|
@ -356,7 +356,8 @@ class HintActions:
|
|||
url: The URL to open as a QUrl.
|
||||
context: The HintContext to use.
|
||||
"""
|
||||
urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||
urlstr = url.toString(
|
||||
QUrl.ComponentFormattingOption.FullyEncoded | QUrl.UrlFormattingOption.RemovePassword)
|
||||
args = context.get_args(urlstr)
|
||||
commandrunner = runners.CommandRunner(self._win_id)
|
||||
commandrunner.run_safely('spawn ' + ' '.join(args))
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import contextlib
|
|||
import pathlib
|
||||
from typing import cast, Mapping, MutableSequence, Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QObject, pyqtSignal
|
||||
from PyQt5.QtWidgets import QProgressDialog, QApplication
|
||||
from qutebrowser.qt.core import pyqtSlot, QUrl, QObject, pyqtSignal
|
||||
from qutebrowser.qt.widgets import QProgressDialog, QApplication
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.api import cmdutils
|
||||
|
|
@ -435,10 +435,10 @@ class WebHistory(sql.SqlTable):
|
|||
}, replace=True)
|
||||
|
||||
def _format_url(self, url):
|
||||
return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
return url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
|
||||
def _format_completion_url(self, url):
|
||||
return url.toString(QUrl.RemovePassword)
|
||||
return url.toString(QUrl.UrlFormattingOption.RemovePassword)
|
||||
|
||||
|
||||
@cmdutils.register()
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import binascii
|
|||
import enum
|
||||
from typing import cast, Optional, Any
|
||||
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
from qutebrowser.qt.widgets import QWidget
|
||||
from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QEvent
|
||||
from qutebrowser.qt.gui import QCloseEvent
|
||||
|
||||
from qutebrowser.browser import eventfilter
|
||||
from qutebrowser.config import configfiles, config
|
||||
|
|
@ -74,7 +74,7 @@ class _EventFilter(QObject):
|
|||
|
||||
def eventFilter(self, _obj: QObject, event: QEvent) -> bool:
|
||||
"""Translate mouse presses to a clicked signal."""
|
||||
if event.type() == QEvent.MouseButtonPress:
|
||||
if event.type() == QEvent.Type.MouseButtonPress:
|
||||
self.clicked.emit()
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import re
|
|||
import posixpath
|
||||
from typing import Optional, Set
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from qutebrowser.qt.core import QUrl
|
||||
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.config import config
|
||||
|
|
@ -42,24 +42,24 @@ class Error(Exception):
|
|||
# of information. (host and path use FullyDecoded by default)
|
||||
_URL_SEGMENTS = [
|
||||
('host',
|
||||
lambda url: url.host(QUrl.FullyEncoded),
|
||||
lambda url, host: url.setHost(host, QUrl.StrictMode)),
|
||||
lambda url: url.host(QUrl.ComponentFormattingOption.FullyEncoded),
|
||||
lambda url, host: url.setHost(host, QUrl.ParsingMode.StrictMode)),
|
||||
|
||||
('port',
|
||||
lambda url: str(url.port()) if url.port() > 0 else '',
|
||||
lambda url, x: url.setPort(int(x))),
|
||||
|
||||
('path',
|
||||
lambda url: url.path(QUrl.FullyEncoded),
|
||||
lambda url, path: url.setPath(path, QUrl.StrictMode)),
|
||||
lambda url: url.path(QUrl.ComponentFormattingOption.FullyEncoded),
|
||||
lambda url, path: url.setPath(path, QUrl.ParsingMode.StrictMode)),
|
||||
|
||||
('query',
|
||||
lambda url: url.query(QUrl.FullyEncoded),
|
||||
lambda url, query: url.setQuery(query, QUrl.StrictMode)),
|
||||
lambda url: url.query(QUrl.ComponentFormattingOption.FullyEncoded),
|
||||
lambda url, query: url.setQuery(query, QUrl.ParsingMode.StrictMode)),
|
||||
|
||||
('anchor',
|
||||
lambda url: url.fragment(QUrl.FullyEncoded),
|
||||
lambda url, fragment: url.setFragment(fragment, QUrl.StrictMode)),
|
||||
lambda url: url.fragment(QUrl.ComponentFormattingOption.FullyEncoded),
|
||||
lambda url, fragment: url.setFragment(fragment, QUrl.ParsingMode.StrictMode)),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -130,14 +130,14 @@ def path_up(url, count):
|
|||
count: The number of levels to go up in the url.
|
||||
"""
|
||||
urlutils.ensure_valid(url)
|
||||
url = url.adjusted(QUrl.RemoveFragment | QUrl.RemoveQuery)
|
||||
path = url.path(QUrl.FullyEncoded)
|
||||
url = url.adjusted(QUrl.UrlFormattingOption.RemoveFragment | QUrl.UrlFormattingOption.RemoveQuery)
|
||||
path = url.path(QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
if not path or path == '/':
|
||||
raise Error("Can't go up!")
|
||||
for _i in range(0, min(count, path.count('/'))):
|
||||
path = posixpath.join(path, posixpath.pardir)
|
||||
path = posixpath.normpath(path)
|
||||
url.setPath(path, QUrl.StrictMode)
|
||||
url.setPath(path, QUrl.ParsingMode.StrictMode)
|
||||
return url
|
||||
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ def strip(url, count):
|
|||
if count != 1:
|
||||
raise Error("Count is not supported when stripping URL components")
|
||||
urlutils.ensure_valid(url)
|
||||
return url.adjusted(QUrl.RemoveFragment | QUrl.RemoveQuery)
|
||||
return url.adjusted(QUrl.UrlFormattingOption.RemoveFragment | QUrl.UrlFormattingOption.RemoveQuery)
|
||||
|
||||
|
||||
def _find_prevnext(prev, elems):
|
||||
|
|
@ -219,10 +219,10 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
|
|||
if window:
|
||||
new_window = mainwindow.MainWindow(
|
||||
private=cur_tabbed_browser.is_private)
|
||||
new_window.show()
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=new_window.win_id)
|
||||
tabbed_browser.tabopen(url, background=False)
|
||||
new_window.show()
|
||||
elif tab:
|
||||
cur_tabbed_browser.tabopen(url, background=background)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ import sys
|
|||
import functools
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QUrl
|
||||
from PyQt5.QtNetwork import (QNetworkProxy, QNetworkRequest, QHostInfo,
|
||||
from qutebrowser.qt.core import QObject, pyqtSignal, pyqtSlot, QUrl
|
||||
from qutebrowser.qt.network import (QNetworkProxy, QNetworkRequest, QHostInfo,
|
||||
QNetworkReply, QNetworkAccessManager,
|
||||
QHostAddress)
|
||||
from PyQt5.QtQml import QJSEngine, QJSValue
|
||||
from qutebrowser.qt.qml import QJSEngine, QJSValue
|
||||
|
||||
from qutebrowser.utils import log, utils, qtutils, resources
|
||||
|
||||
|
|
@ -109,10 +109,10 @@ class _PACContext(QObject):
|
|||
host: hostname to resolve.
|
||||
"""
|
||||
ips = QHostInfo.fromName(host)
|
||||
if ips.error() != QHostInfo.NoError or not ips.addresses():
|
||||
if ips.error() != QHostInfo.HostInfoError.NoError or not ips.addresses():
|
||||
err_f = "Failed to resolve host during PAC evaluation: {}"
|
||||
log.network.info(err_f.format(host))
|
||||
return QJSValue(QJSValue.NullValue)
|
||||
return QJSValue(QJSValue.SpecialValue.NullValue)
|
||||
else:
|
||||
return ips.addresses()[0].toString()
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ class _PACContext(QObject):
|
|||
Return the server IP address of the current machine, as a string in
|
||||
the dot-separated integer format.
|
||||
"""
|
||||
return QHostAddress(QHostAddress.LocalHost).toString()
|
||||
return QHostAddress(QHostAddress.SpecialAddress.LocalHost).toString()
|
||||
|
||||
|
||||
class PACResolver:
|
||||
|
|
@ -150,17 +150,17 @@ class PACResolver:
|
|||
if len(config) != 1:
|
||||
raise ParseProxyError("Invalid number of parameters for " +
|
||||
"DIRECT")
|
||||
return QNetworkProxy(QNetworkProxy.NoProxy)
|
||||
return QNetworkProxy(QNetworkProxy.ProxyType.NoProxy)
|
||||
elif config[0] == "PROXY":
|
||||
if len(config) != 2:
|
||||
raise ParseProxyError("Invalid number of parameters for PROXY")
|
||||
host, port = PACResolver._parse_proxy_host(config[1])
|
||||
return QNetworkProxy(QNetworkProxy.HttpProxy, host, port)
|
||||
return QNetworkProxy(QNetworkProxy.ProxyType.HttpProxy, host, port)
|
||||
elif config[0] in ["SOCKS", "SOCKS5"]:
|
||||
if len(config) != 2:
|
||||
raise ParseProxyError("Invalid number of parameters for SOCKS")
|
||||
host, port = PACResolver._parse_proxy_host(config[1])
|
||||
return QNetworkProxy(QNetworkProxy.Socks5Proxy, host, port)
|
||||
return QNetworkProxy(QNetworkProxy.ProxyType.Socks5Proxy, host, port)
|
||||
else:
|
||||
err = "Unknown proxy type: {}"
|
||||
raise ParseProxyError(err.format(config[0]))
|
||||
|
|
@ -184,7 +184,7 @@ class PACResolver:
|
|||
"""
|
||||
self._engine = QJSEngine()
|
||||
|
||||
self._engine.installExtensions(QJSEngine.ConsoleExtension)
|
||||
self._engine.installExtensions(QJSEngine.Extension.ConsoleExtension)
|
||||
|
||||
self._ctx = _PACContext(self._engine)
|
||||
self._engine.globalObject().setProperty(
|
||||
|
|
@ -215,12 +215,12 @@ class PACResolver:
|
|||
qtutils.ensure_valid(query.url())
|
||||
|
||||
if from_file:
|
||||
string_flags = QUrl.PrettyDecoded
|
||||
string_flags = QUrl.ComponentFormattingOption.PrettyDecoded
|
||||
else:
|
||||
string_flags = QUrl.RemoveUserInfo # type: ignore[assignment]
|
||||
string_flags = QUrl.UrlFormattingOption.RemoveUserInfo # type: ignore[assignment]
|
||||
if query.url().scheme() == 'https':
|
||||
string_flags |= QUrl.RemovePath # type: ignore[assignment]
|
||||
string_flags |= QUrl.RemoveQuery # type: ignore[assignment]
|
||||
string_flags |= QUrl.UrlFormattingOption.RemovePath # type: ignore[assignment]
|
||||
string_flags |= QUrl.UrlFormattingOption.RemoveQuery # type: ignore[assignment]
|
||||
|
||||
result = self._resolver.call([query.url().toString(string_flags),
|
||||
query.peerHostName()])
|
||||
|
|
@ -255,7 +255,7 @@ class PACFetcher(QObject):
|
|||
# WORKAROUND for a hang when messages are printed, see our
|
||||
# NetworkAccessManager subclass for details.
|
||||
self._manager: Optional[QNetworkAccessManager] = QNetworkAccessManager()
|
||||
self._manager.setProxy(QNetworkProxy(QNetworkProxy.NoProxy))
|
||||
self._manager.setProxy(QNetworkProxy(QNetworkProxy.ProxyType.NoProxy))
|
||||
self._pac = None
|
||||
self._error_message = None
|
||||
self._reply = None
|
||||
|
|
@ -275,7 +275,7 @@ class PACFetcher(QObject):
|
|||
@pyqtSlot()
|
||||
def _finish(self):
|
||||
assert self._reply is not None
|
||||
if self._reply.error() != QNetworkReply.NoError:
|
||||
if self._reply.error() != QNetworkReply.NetworkError.NoError:
|
||||
error = "Can't fetch PAC file from URL, error code {}: {}"
|
||||
self._error_message = error.format(
|
||||
self._reply.error(), self._reply.errorString())
|
||||
|
|
@ -337,4 +337,4 @@ class PACFetcher(QObject):
|
|||
# Later NetworkManager.createRequest will detect this and display
|
||||
# an error message.
|
||||
error_host = "pac-resolve-error.qutebrowser.invalid"
|
||||
return [QNetworkProxy(QNetworkProxy.HttpProxy, error_host, 9)]
|
||||
return [QNetworkProxy(QNetworkProxy.ProxyType.HttpProxy, error_host, 9)]
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
"""Handling of proxies."""
|
||||
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot
|
||||
from PyQt5.QtNetwork import QNetworkProxy, QNetworkProxyFactory
|
||||
from qutebrowser.qt.core import QUrl, pyqtSlot
|
||||
from qutebrowser.qt.network import QNetworkProxy, QNetworkProxyFactory
|
||||
|
||||
from qutebrowser.config import config, configtypes
|
||||
from qutebrowser.utils import message, usertypes, urlutils, utils
|
||||
|
|
@ -73,11 +73,11 @@ class ProxyFactory(QNetworkProxyFactory):
|
|||
return None
|
||||
|
||||
def _set_capabilities(self, proxy):
|
||||
if proxy.type() == QNetworkProxy.NoProxy:
|
||||
if proxy.type() == QNetworkProxy.ProxyType.NoProxy:
|
||||
return
|
||||
|
||||
capabilities = proxy.capabilities()
|
||||
lookup_cap = QNetworkProxy.HostNameLookupCapability
|
||||
lookup_cap = QNetworkProxy.Capability.HostNameLookupCapability
|
||||
if config.val.content.proxy_dns_requests:
|
||||
capabilities |= lookup_cap
|
||||
else:
|
||||
|
|
@ -97,7 +97,7 @@ class ProxyFactory(QNetworkProxyFactory):
|
|||
if proxy is configtypes.SYSTEM_PROXY:
|
||||
# On Linux, use "export http_proxy=socks5://host:port" to manually
|
||||
# set system proxy.
|
||||
# ref. https://doc.qt.io/qt-5/qnetworkproxyfactory.html#systemProxyForQuery
|
||||
# ref. https://doc.qt.io/qt-6/qnetworkproxyfactory.html#systemProxyForQuery
|
||||
proxies = QNetworkProxyFactory.systemProxyForQuery(query)
|
||||
elif isinstance(proxy, pac.PACFetcher):
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QUrl, QUrlQuery
|
||||
from qutebrowser.qt.core import QUrl, QUrlQuery
|
||||
|
||||
from qutebrowser.utils import resources, javascript, jinja, standarddir, log
|
||||
from qutebrowser.config import config
|
||||
|
|
@ -96,7 +96,7 @@ def _generate_pdfjs_script(filename):
|
|||
url.setQuery(url_query)
|
||||
|
||||
js_url = javascript.to_js(
|
||||
url.toString(QUrl.FullyEncoded)) # type: ignore[arg-type]
|
||||
url.toString(QUrl.ComponentFormattingOption.FullyEncoded)) # type: ignore[arg-type]
|
||||
|
||||
return jinja.js_environment.from_string("""
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
|
|
@ -245,7 +245,7 @@ def get_main_url(filename: str, original_url: QUrl) -> QUrl:
|
|||
query = QUrlQuery()
|
||||
query.addQueryItem('filename', filename) # read from our JS
|
||||
query.addQueryItem('file', '') # to avoid pdfjs opening the default PDF
|
||||
urlstr = original_url.toString(QUrl.FullyEncoded) # type: ignore[arg-type]
|
||||
urlstr = original_url.toString(QUrl.ComponentFormattingOption.FullyEncoded) # type: ignore[arg-type]
|
||||
query.addQueryItem('source', urlstr)
|
||||
url.setQuery(query)
|
||||
return url
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ import functools
|
|||
import dataclasses
|
||||
from typing import Dict, IO, Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, QUrl
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
||||
from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QTimer, QUrl
|
||||
from qutebrowser.qt.widgets import QApplication
|
||||
from qutebrowser.qt.network import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
||||
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug, objreg
|
||||
|
|
@ -62,12 +62,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
As soon as we know the file object, we copy self._buffer over and the next
|
||||
readyRead will write to the real file object.
|
||||
|
||||
Class attributes:
|
||||
_MAX_REDIRECTS: The maximum redirection count.
|
||||
|
||||
Attributes:
|
||||
_retry_info: A _RetryInfo instance.
|
||||
_redirects: How many time we were redirected already.
|
||||
_buffer: A BytesIO object to buffer incoming data until we know the
|
||||
target file.
|
||||
_read_timer: A Timer which reads the QNetworkReply into self._buffer
|
||||
|
|
@ -82,7 +78,6 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
arg 0: The new DownloadItem
|
||||
"""
|
||||
|
||||
_MAX_REDIRECTS = 10
|
||||
adopt_download = pyqtSignal(object) # DownloadItem
|
||||
|
||||
def __init__(self, reply, manager):
|
||||
|
|
@ -102,7 +97,6 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
self._read_timer = usertypes.Timer(self, name='download-read-timer')
|
||||
self._read_timer.setInterval(500)
|
||||
self._read_timer.timeout.connect(self._on_read_timer_timeout)
|
||||
self._redirects = 0
|
||||
self._url = reply.url()
|
||||
self._init_reply(reply)
|
||||
|
||||
|
|
@ -123,10 +117,12 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
if self._reply is None:
|
||||
log.downloads.debug("Reply gone while dying")
|
||||
return
|
||||
|
||||
self._reply.downloadProgress.disconnect()
|
||||
self._reply.finished.disconnect()
|
||||
self._reply.error.disconnect()
|
||||
self._reply.errorOccurred.disconnect()
|
||||
self._reply.readyRead.disconnect()
|
||||
|
||||
with log.hide_qt_warning('QNetworkReplyImplPrivate::error: Internal '
|
||||
'problem, this method must only be called '
|
||||
'once.'):
|
||||
|
|
@ -135,11 +131,22 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
self._reply.deleteLater()
|
||||
self._reply = None
|
||||
if self.fileobj is not None:
|
||||
pos = self.fileobj.tell()
|
||||
log.downloads.debug(f"File position at error: {pos}")
|
||||
try:
|
||||
self.fileobj.close()
|
||||
except OSError:
|
||||
log.downloads.exception("Error while closing file object")
|
||||
|
||||
if pos == 0:
|
||||
# Emtpy remaining file
|
||||
filename = self._get_open_filename()
|
||||
log.downloads.debug(f"Removing empty file at {filename}")
|
||||
try:
|
||||
os.remove(filename)
|
||||
except OSError:
|
||||
log.downloads.exception("Error while removing empty file")
|
||||
|
||||
def _init_reply(self, reply):
|
||||
"""Set a new reply and connect its signals.
|
||||
|
||||
|
|
@ -150,11 +157,14 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
self.successful = False
|
||||
self._reply = reply
|
||||
reply.setReadBufferSize(16 * 1024 * 1024) # 16 MB
|
||||
|
||||
reply.downloadProgress.connect(self.stats.on_download_progress)
|
||||
reply.finished.connect(self._on_reply_finished)
|
||||
reply.error.connect(self._on_reply_error)
|
||||
reply.errorOccurred.connect(self._on_reply_error)
|
||||
reply.readyRead.connect(self._on_ready_read)
|
||||
reply.metaDataChanged.connect(self._on_meta_data_changed)
|
||||
reply.redirected.connect(self._on_redirected)
|
||||
|
||||
self._retry_info = _RetryInfo(request=reply.request(),
|
||||
manager=reply.manager())
|
||||
if not self.fileobj:
|
||||
|
|
@ -162,9 +172,16 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
# We could have got signals before we connected slots to them.
|
||||
# Here no signals are connected to the DownloadItem yet, so we use a
|
||||
# singleShot QTimer to emit them after they are connected.
|
||||
if reply.error() != QNetworkReply.NoError:
|
||||
if reply.error() != QNetworkReply.NetworkError.NoError:
|
||||
QTimer.singleShot(0, lambda: self._die(reply.errorString()))
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_redirected(self, 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()
|
||||
if self._reply is not None:
|
||||
|
|
@ -286,7 +303,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
self.fileobj.write(self._reply.readAll())
|
||||
if self._autoclose:
|
||||
self.fileobj.close()
|
||||
self.successful = self._reply.error() == QNetworkReply.NoError
|
||||
self.successful = self._reply.error() == QNetworkReply.NetworkError.NoError
|
||||
self._reply.close()
|
||||
self._reply.deleteLater()
|
||||
self._reply = None
|
||||
|
|
@ -307,9 +324,6 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
return
|
||||
self._read_timer.stop()
|
||||
self.stats.finish()
|
||||
is_redirected = self._handle_redirect()
|
||||
if is_redirected:
|
||||
return
|
||||
log.downloads.debug("Reply finished, fileobj {}".format(self.fileobj))
|
||||
if self.fileobj is not None:
|
||||
# We can do a "delayed" write immediately to empty the buffer and
|
||||
|
|
@ -334,7 +348,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
@pyqtSlot('QNetworkReply::NetworkError')
|
||||
def _on_reply_error(self, code):
|
||||
"""Handle QNetworkReply errors."""
|
||||
if code == QNetworkReply.OperationCanceledError:
|
||||
if code == QNetworkReply.NetworkError.OperationCanceledError:
|
||||
return
|
||||
|
||||
if self._reply is None:
|
||||
|
|
@ -364,48 +378,6 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
for key, value in self._reply.rawHeaderPairs():
|
||||
self.raw_headers[bytes(key)] = bytes(value)
|
||||
|
||||
def _handle_redirect(self):
|
||||
"""Handle an HTTP redirect.
|
||||
|
||||
Return:
|
||||
True if the download was redirected, False otherwise.
|
||||
"""
|
||||
assert self._reply is not None
|
||||
redirect = self._reply.attribute(
|
||||
QNetworkRequest.RedirectionTargetAttribute)
|
||||
if redirect is None or redirect.isEmpty():
|
||||
return False
|
||||
new_url = self._reply.url().resolved(redirect)
|
||||
new_request = self._reply.request()
|
||||
if new_url == new_request.url():
|
||||
return False
|
||||
|
||||
if self._redirects > self._MAX_REDIRECTS:
|
||||
self._die("Maximum redirection count reached!")
|
||||
self.delete()
|
||||
return True # so on_reply_finished aborts
|
||||
|
||||
log.downloads.debug("{}: Handling redirect".format(self))
|
||||
self._redirects += 1
|
||||
new_request.setUrl(new_url)
|
||||
|
||||
old_reply = self._reply
|
||||
assert old_reply is not None
|
||||
old_reply.finished.disconnect(self._on_reply_finished)
|
||||
|
||||
self._read_timer.stop()
|
||||
self._reply = None
|
||||
if self.fileobj is not None:
|
||||
self.fileobj.seek(0)
|
||||
|
||||
log.downloads.debug("redirected: {} -> {}".format(
|
||||
old_reply.url(), new_request.url()))
|
||||
new_reply = old_reply.manager().get(new_request)
|
||||
self._init_reply(new_reply)
|
||||
|
||||
old_reply.deleteLater()
|
||||
return True
|
||||
|
||||
def _uses_nam(self, nam):
|
||||
"""Check if this download uses the given QNetworkAccessManager."""
|
||||
assert self._retry_info is not None
|
||||
|
|
@ -422,8 +394,17 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
|||
|
||||
Attributes:
|
||||
_networkmanager: A NetworkManager for generic downloads.
|
||||
|
||||
Class attributes:
|
||||
_MAX_REDIRECTS: The maximum redirection count.
|
||||
"""
|
||||
|
||||
# Same as many browsers
|
||||
# https://fetch.spec.whatwg.org/#http-redirect-fetch
|
||||
# https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request.h;l=97;drc=3c19a2edb96d3d5b56a7481349a357fdbdf8ecf0
|
||||
# https://stackoverflow.com/questions/9384474/in-chrome-how-many-redirects-are-too-many
|
||||
_MAX_REDIRECTS = 20
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
|
|
@ -447,11 +428,19 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
|||
return None
|
||||
|
||||
req = QNetworkRequest(url)
|
||||
user_agent = websettings.user_agent(url)
|
||||
req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
|
||||
|
||||
user_agent = websettings.user_agent(url)
|
||||
req.setHeader(QNetworkRequest.KnownHeaders.UserAgentHeader, user_agent)
|
||||
if not cache:
|
||||
req.setAttribute(QNetworkRequest.CacheSaveControlAttribute, False)
|
||||
req.setAttribute(QNetworkRequest.Attribute.CacheSaveControlAttribute, False)
|
||||
|
||||
# Needed for Qt 5, default on Qt 6
|
||||
# We don't set this on the QNAM because QtWebKit handles redirects manually.
|
||||
req.setAttribute(
|
||||
QNetworkRequest.Attribute.RedirectPolicyAttribute,
|
||||
QNetworkRequest.RedirectPolicy.NoLessSafeRedirectPolicy,
|
||||
)
|
||||
req.setMaximumRedirectsAllowed(self._MAX_REDIRECTS)
|
||||
|
||||
return self.get_request(req, **kwargs)
|
||||
|
||||
|
|
@ -511,8 +500,8 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
|||
"""
|
||||
# WORKAROUND for Qt corrupting data loaded from cache:
|
||||
# https://bugreports.qt.io/browse/QTBUG-42757
|
||||
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
||||
QNetworkRequest.AlwaysNetwork)
|
||||
request.setAttribute(QNetworkRequest.Attribute.CacheLoadControlAttribute,
|
||||
QNetworkRequest.CacheLoadControl.AlwaysNetwork)
|
||||
|
||||
if suggested_fn is None:
|
||||
suggested_fn = self._get_suggested_filename(request)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import collections
|
|||
import secrets
|
||||
from typing import TypeVar, Callable, Dict, List, Optional, Union, Sequence, Tuple
|
||||
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
from qutebrowser.qt.core import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.browser import pdfjs, downloads, history
|
||||
|
|
@ -128,7 +128,9 @@ def data_for_url(url: QUrl) -> Tuple[str, bytes]:
|
|||
Return:
|
||||
A (mimetype, data) tuple.
|
||||
"""
|
||||
norm_url = url.adjusted(QUrl.NormalizePathSegments | QUrl.StripTrailingSlash)
|
||||
norm_url = url.adjusted(
|
||||
QUrl.UrlFormattingOption.NormalizePathSegments |
|
||||
QUrl.UrlFormattingOption.StripTrailingSlash)
|
||||
if norm_url != url:
|
||||
raise Redirect(norm_url)
|
||||
|
||||
|
|
@ -423,8 +425,8 @@ def qute_help(url: QUrl) -> _HandlerRet:
|
|||
def _qute_settings_set(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://settings/set."""
|
||||
query = QUrlQuery(url)
|
||||
option = query.queryItemValue('option', QUrl.FullyDecoded)
|
||||
value = query.queryItemValue('value', QUrl.FullyDecoded)
|
||||
option = query.queryItemValue('option', QUrl.ComponentFormattingOption.FullyDecoded)
|
||||
value = query.queryItemValue('value', QUrl.ComponentFormattingOption.FullyDecoded)
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/727
|
||||
if option == 'content.javascript.enabled' and value == 'false':
|
||||
|
|
@ -498,10 +500,11 @@ def qute_back(url: QUrl) -> _HandlerRet:
|
|||
|
||||
|
||||
@add_handler('configdiff')
|
||||
def qute_configdiff(_url: QUrl) -> _HandlerRet:
|
||||
def qute_configdiff(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://configdiff."""
|
||||
data = config.instance.dump_userconfig().encode('utf-8')
|
||||
return 'text/plain', data
|
||||
include_hidden = QUrlQuery(url).queryItemValue('include_hidden') == 'true'
|
||||
dump = config.instance.dump_userconfig(include_hidden=include_hidden)
|
||||
return 'text/plain', dump.encode('utf-8')
|
||||
|
||||
|
||||
@add_handler('pastebin-version')
|
||||
|
|
@ -579,6 +582,9 @@ def qute_warning(url: QUrl) -> _HandlerRet:
|
|||
title='Qt 5.15 sessions warning',
|
||||
datadir=standarddir.data(),
|
||||
sep=os.sep)
|
||||
elif path == '/sandboxing':
|
||||
src = jinja.render('warning-sandboxing.html',
|
||||
title='Qt 6 macOS sandboxing warning')
|
||||
else:
|
||||
raise NotFoundError("Invalid warning page {}".format(path))
|
||||
return 'text/html', src
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import netrc
|
|||
import tempfile
|
||||
from typing import Callable, Mapping, List, Optional, Iterable, Iterator
|
||||
|
||||
from PyQt5.QtCore import QUrl, pyqtBoundSignal
|
||||
from qutebrowser.qt.core import QUrl, pyqtBoundSignal
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (usertypes, message, log, objreg, jinja, utils,
|
||||
|
|
@ -72,7 +72,7 @@ def authentication_required(url, authenticator, abort_on):
|
|||
else:
|
||||
msg = '<b>{}</b> needs authentication'.format(
|
||||
html.escape(url.toDisplayString()))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
answer = message.ask(title="Authentication required", text=msg,
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
|
|
@ -95,7 +95,7 @@ def javascript_confirm(url, js_msg, abort_on):
|
|||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
_format_msg(js_msg))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
ans = message.ask('Javascript confirm', msg,
|
||||
mode=usertypes.PromptMode.yesno,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
|
|
@ -112,7 +112,7 @@ def javascript_prompt(url, js_msg, default, abort_on):
|
|||
|
||||
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
_format_msg(js_msg))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
answer = message.ask('Javascript prompt', msg,
|
||||
mode=usertypes.PromptMode.text,
|
||||
default=default,
|
||||
|
|
@ -135,7 +135,7 @@ def javascript_alert(url, js_msg, abort_on):
|
|||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
_format_msg(js_msg))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
|
||||
|
|
@ -205,13 +205,13 @@ def javascript_log_message(
|
|||
logger(logstring)
|
||||
|
||||
|
||||
def ignore_certificate_error(
|
||||
def handle_certificate_error(
|
||||
*,
|
||||
request_url: QUrl,
|
||||
first_party_url: QUrl,
|
||||
error: usertypes.AbstractCertificateErrorWrapper,
|
||||
abort_on: Iterable[pyqtBoundSignal],
|
||||
) -> bool:
|
||||
) -> None:
|
||||
"""Display a certificate error question.
|
||||
|
||||
Args:
|
||||
|
|
@ -219,9 +219,6 @@ def ignore_certificate_error(
|
|||
first_party_url: The URL of the page we're visiting. Might be an invalid QUrl.
|
||||
error: A single error.
|
||||
abort_on: Signals aborting a question.
|
||||
|
||||
Return:
|
||||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
conf = config.instance.get('content.tls.certificate_errors', url=request_url)
|
||||
log.network.debug(f"Certificate error {error!r}, config {conf}")
|
||||
|
|
@ -234,7 +231,7 @@ def ignore_certificate_error(
|
|||
first_party_url.isValid() and
|
||||
not request_url.matches(
|
||||
first_party_url,
|
||||
QUrl.RemoveScheme)) # type: ignore[arg-type]
|
||||
QUrl.UrlFormattingOption.RemoveScheme)) # type: ignore[arg-type]
|
||||
|
||||
if conf == 'ask' or conf == 'ask-block-thirdparty' and not is_resource:
|
||||
err_template = jinja.environment.from_string("""
|
||||
|
|
@ -263,28 +260,46 @@ def ignore_certificate_error(
|
|||
is_resource=is_resource,
|
||||
error=error,
|
||||
)
|
||||
|
||||
urlstr = request_url.toString(
|
||||
QUrl.RemovePassword | QUrl.FullyEncoded) # type: ignore[arg-type]
|
||||
ignore = message.ask(title="Certificate error", text=msg,
|
||||
mode=usertypes.PromptMode.yesno, default=False,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
if ignore is None:
|
||||
# prompt aborted
|
||||
ignore = False
|
||||
return ignore
|
||||
QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded) # type: ignore[arg-type]
|
||||
title = "Certificate error"
|
||||
|
||||
try:
|
||||
error.defer()
|
||||
except usertypes.UndeferrableError:
|
||||
# QtNetwork / QtWebKit and buggy PyQt versions
|
||||
# Show blocking question prompt
|
||||
ignore = message.ask(title=title, text=msg,
|
||||
mode=usertypes.PromptMode.yesno, default=False,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
if ignore:
|
||||
error.accept_certificate()
|
||||
else: # includes None, i.e. prompt aborted
|
||||
error.reject_certificate()
|
||||
else:
|
||||
# Show non-blocking question prompt
|
||||
message.confirm_async(
|
||||
title=title,
|
||||
text=msg,
|
||||
abort_on=abort_on,
|
||||
url=urlstr,
|
||||
yes_action=error.accept_certificate,
|
||||
no_action=error.reject_certificate,
|
||||
cancel_action=error.reject_certificate,
|
||||
)
|
||||
elif conf == 'load-insecurely':
|
||||
message.error(f'Certificate error: {error}')
|
||||
return True
|
||||
error.accept_certificate()
|
||||
elif conf == 'block':
|
||||
return False
|
||||
error.reject_certificate()
|
||||
elif conf == 'ask-block-thirdparty' and is_resource:
|
||||
log.network.error(
|
||||
f"Certificate error in resource load: {error}\n"
|
||||
f" request URL: {request_url.toDisplayString()}\n"
|
||||
f" first party URL: {first_party_url.toDisplayString()}")
|
||||
return False
|
||||
raise utils.Unreachable(conf, is_resource)
|
||||
error.reject_certificate()
|
||||
else:
|
||||
raise utils.Unreachable(conf, is_resource)
|
||||
|
||||
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
||||
|
|
@ -307,7 +322,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
|||
config_val = config.instance.get(option, url=url)
|
||||
if config_val == 'ask':
|
||||
if url.isValid():
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
text = "Allow the website at <b>{}</b> to {}?".format(
|
||||
html.escape(url.toDisplayString()), msg)
|
||||
else:
|
||||
|
|
@ -345,23 +360,19 @@ def get_tab(win_id, target):
|
|||
win_id: The window ID to open new tabs in
|
||||
target: A usertypes.ClickTarget
|
||||
"""
|
||||
if target == usertypes.ClickTarget.tab:
|
||||
bg_tab = False
|
||||
elif target == usertypes.ClickTarget.tab_bg:
|
||||
bg_tab = True
|
||||
elif target == usertypes.ClickTarget.window:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id)
|
||||
if target == usertypes.ClickTarget.window:
|
||||
window = mainwindow.MainWindow(private=tabbed_browser.is_private)
|
||||
tab = window.tabbed_browser.tabopen(url=None, background=False)
|
||||
window.show()
|
||||
win_id = window.win_id
|
||||
bg_tab = False
|
||||
else:
|
||||
raise ValueError("Invalid ClickTarget {}".format(target))
|
||||
return tab
|
||||
elif target in [usertypes.ClickTarget.tab, usertypes.ClickTarget.tab_bg]:
|
||||
return tabbed_browser.tabopen(
|
||||
url=None,
|
||||
background=target == usertypes.ClickTarget.tab_bg,
|
||||
)
|
||||
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
return tabbed_browser.tabopen(url=None, background=bg_tab)
|
||||
raise ValueError(f"Invalid ClickTarget {target}")
|
||||
|
||||
|
||||
def get_user_stylesheet(searching=False):
|
||||
|
|
@ -381,7 +392,7 @@ def get_user_stylesheet(searching=False):
|
|||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||
|
||||
if (objects.backend == usertypes.Backend.QtWebEngine and
|
||||
version.qtwebengine_versions().chromium_major in [69, 73, 80, 87] and
|
||||
version.qtwebengine_versions().chromium_major in [87, 90] and
|
||||
config.val.colors.webpage.darkmode.enabled and
|
||||
config.val.colors.webpage.darkmode.policy.images == 'smart' and
|
||||
config.val.content.site_specific_quirks.enabled and
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import QObject
|
||||
from qutebrowser.qt.core import QObject
|
||||
|
||||
from qutebrowser.utils import debug, log, objreg
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import functools
|
|||
import collections
|
||||
from typing import MutableMapping
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, QObject
|
||||
from qutebrowser.qt.core import pyqtSignal, QUrl, QObject
|
||||
|
||||
from qutebrowser.utils import (message, usertypes, qtutils, urlutils,
|
||||
standarddir, objreg, log)
|
||||
|
|
@ -151,7 +151,7 @@ class QuickmarkManager(UrlMarkManager):
|
|||
if not url.isValid():
|
||||
urlutils.invalid_url_error(url, "save quickmark")
|
||||
return
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
message.ask_async(
|
||||
"Add quickmark:", usertypes.PromptMode.text,
|
||||
functools.partial(self.quickmark_add, urlstr),
|
||||
|
|
@ -198,7 +198,7 @@ class QuickmarkManager(UrlMarkManager):
|
|||
Use a name instead where possible.
|
||||
"""
|
||||
qtutils.ensure_valid(url)
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
|
||||
try:
|
||||
index = list(self.marks.values()).index(urlstr)
|
||||
|
|
@ -270,7 +270,7 @@ class BookmarkManager(UrlMarkManager):
|
|||
errstr = urlutils.get_errstring(url)
|
||||
raise InvalidUrlError(errstr)
|
||||
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
|
||||
if urlstr in self.marks:
|
||||
if toggle:
|
||||
|
|
|
|||
|
|
@ -19,11 +19,12 @@
|
|||
|
||||
"""Generic web element related code."""
|
||||
|
||||
from typing import cast, TYPE_CHECKING, Iterator, Optional, Set, Union, Dict
|
||||
from typing import Iterator, Optional, Set, TYPE_CHECKING, Union, Dict
|
||||
import collections.abc
|
||||
|
||||
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer, QRect, QPoint
|
||||
from PyQt5.QtGui import QMouseEvent
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import QUrl, Qt, QEvent, QTimer, QRect, QPointF
|
||||
from qutebrowser.qt.gui import QMouseEvent
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
|
@ -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):
|
||||
|
||||
|
|
@ -317,7 +323,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
|
|||
"""Return True if clicking this element needs user interaction."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _mouse_pos(self) -> QPoint:
|
||||
def _mouse_pos(self) -> QPointF:
|
||||
"""Get the position to click/hover."""
|
||||
# Click the center of the largest square fitting into the top/left
|
||||
# corner of the rectangle, this will help if part of the <a> element
|
||||
|
|
@ -331,38 +337,37 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
|
|||
pos = rect.center()
|
||||
if pos.x() < 0 or pos.y() < 0:
|
||||
raise Error("Element position is out of view!")
|
||||
return pos
|
||||
return QPointF(pos)
|
||||
|
||||
def _move_text_cursor(self) -> None:
|
||||
"""Move cursor to end after clicking."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _click_fake_event(self, click_target: usertypes.ClickTarget,
|
||||
button: Qt.MouseButton = Qt.LeftButton) -> None:
|
||||
button: Qt.MouseButton = Qt.MouseButton.LeftButton) -> None:
|
||||
"""Send a fake click event to the element."""
|
||||
pos = self._mouse_pos()
|
||||
|
||||
log.webelem.debug("Sending fake click to {!r} at position {} with "
|
||||
"target {}".format(self, pos, click_target))
|
||||
|
||||
target_modifiers: Dict[usertypes.ClickTarget, Qt.KeyboardModifiers] = {
|
||||
usertypes.ClickTarget.normal: cast(Qt.KeyboardModifiers, Qt.NoModifier),
|
||||
usertypes.ClickTarget.window: Qt.AltModifier | Qt.ShiftModifier,
|
||||
usertypes.ClickTarget.tab: cast(Qt.KeyboardModifiers, Qt.ControlModifier),
|
||||
usertypes.ClickTarget.tab_bg:
|
||||
cast(Qt.KeyboardModifiers, Qt.ControlModifier),
|
||||
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,
|
||||
usertypes.ClickTarget.tab_bg: Qt.KeyboardModifier.ControlModifier,
|
||||
}
|
||||
if config.val.tabs.background:
|
||||
target_modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
|
||||
target_modifiers[usertypes.ClickTarget.tab] |= Qt.KeyboardModifier.ShiftModifier
|
||||
else:
|
||||
target_modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
|
||||
target_modifiers[usertypes.ClickTarget.tab_bg] |= Qt.KeyboardModifier.ShiftModifier
|
||||
|
||||
modifiers = target_modifiers[click_target]
|
||||
|
||||
events = [
|
||||
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier),
|
||||
QMouseEvent(QEvent.MouseButtonPress, pos, button, button, modifiers),
|
||||
QMouseEvent(QEvent.MouseButtonRelease, pos, button, Qt.NoButton, modifiers),
|
||||
QMouseEvent(QEvent.Type.MouseMove, pos, Qt.MouseButton.NoButton, Qt.MouseButton.NoButton, Qt.KeyboardModifier.NoModifier),
|
||||
QMouseEvent(QEvent.Type.MouseButtonPress, pos, button, button, modifiers),
|
||||
QMouseEvent(QEvent.Type.MouseButtonRelease, pos, button, Qt.MouseButton.NoButton, modifiers),
|
||||
]
|
||||
|
||||
for evt in events:
|
||||
|
|
@ -400,8 +405,8 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
|
|||
elif click_target == usertypes.ClickTarget.window:
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
window = mainwindow.MainWindow(private=tabbed_browser.is_private)
|
||||
window.show()
|
||||
window.tabbed_browser.tabopen(url)
|
||||
window.show()
|
||||
else:
|
||||
raise ValueError("Unknown ClickTarget {}".format(click_target))
|
||||
|
||||
|
|
@ -446,11 +451,11 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
|
|||
def hover(self) -> None:
|
||||
"""Simulate a mouse hover over the element."""
|
||||
pos = self._mouse_pos()
|
||||
event = QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
||||
Qt.NoModifier)
|
||||
event = QMouseEvent(QEvent.Type.MouseMove, pos, Qt.MouseButton.NoButton, Qt.MouseButton.NoButton,
|
||||
Qt.KeyboardModifier.NoModifier)
|
||||
self._tab.send_event(event)
|
||||
|
||||
def right_click(self) -> None:
|
||||
"""Simulate a right-click on the element."""
|
||||
self._click_fake_event(usertypes.ClickTarget.normal,
|
||||
button=Qt.RightButton)
|
||||
button=Qt.MouseButton.RightButton)
|
||||
|
|
|
|||
|
|
@ -19,27 +19,54 @@
|
|||
|
||||
"""Wrapper over a QWebEngineCertificateError."""
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineCertificateError
|
||||
from typing import Any
|
||||
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import QUrl
|
||||
from qutebrowser.qt.webenginecore import QWebEngineCertificateError
|
||||
|
||||
from qutebrowser.utils import usertypes, utils, debug
|
||||
|
||||
|
||||
class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
||||
|
||||
"""A wrapper over a QWebEngineCertificateError."""
|
||||
"""A wrapper over a QWebEngineCertificateError.
|
||||
|
||||
Support both Qt 5 and 6.
|
||||
"""
|
||||
|
||||
def __init__(self, error: QWebEngineCertificateError) -> None:
|
||||
super().__init__()
|
||||
self._error = error
|
||||
self.ignore = False
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._error.errorDescription()
|
||||
if machinery.IS_QT5:
|
||||
return self._error.errorDescription()
|
||||
else:
|
||||
return self._error.description()
|
||||
|
||||
def _type(self) -> Any: # QWebEngineCertificateError.Type or .Error
|
||||
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(
|
||||
self,
|
||||
error=debug.qenum_key(QWebEngineCertificateError, self._error.error()),
|
||||
error=debug.qenum_key(QWebEngineCertificateError, self._type()),
|
||||
string=str(self))
|
||||
|
||||
def url(self) -> QUrl:
|
||||
|
|
@ -47,3 +74,8 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
|||
|
||||
def is_overridable(self) -> bool:
|
||||
return self._error.isOverridable()
|
||||
|
||||
def defer(self) -> None:
|
||||
# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2022-April/044585.html
|
||||
# (PyQt 5.15.6, 6.2.3, 6.3.0)
|
||||
raise usertypes.UndeferrableError("PyQt bug")
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
Overview of blink setting names based on the Qt version:
|
||||
|
||||
Qt 5.10
|
||||
-------
|
||||
Qt 5.10 (UNSUPPORTED)
|
||||
---------------------
|
||||
|
||||
First implementation, called "high contrast mode".
|
||||
|
||||
|
|
@ -31,16 +31,16 @@ First implementation, called "high contrast mode".
|
|||
- highContrastContrast (float)
|
||||
- highContractImagePolicy (kFilterAll/kFilterNone)
|
||||
|
||||
Qt 5.11, 5.12, 5.13
|
||||
-------------------
|
||||
Qt 5.11, 5.12, 5.13 (UNSUPPORTED)
|
||||
---------------------------------
|
||||
|
||||
New "smart" image policy.
|
||||
|
||||
- Mode/Grayscale/Contrast as above
|
||||
- highContractImagePolicy (kFilterAll/kFilterNone/kFilterSmart [new!])
|
||||
|
||||
Qt 5.14
|
||||
-------
|
||||
Qt 5.14 (UNSUPPORTED)
|
||||
---------------------
|
||||
|
||||
Renamed to "darkMode".
|
||||
|
||||
|
|
@ -54,8 +54,8 @@ Renamed to "darkMode".
|
|||
- darkModeBackgroundBrightnessThreshold (int) [new!]
|
||||
- darkModeImageGrayscale (float) [new!]
|
||||
|
||||
Qt 5.15.0 and 5.15.1
|
||||
--------------------
|
||||
Qt 5.15.0 and 5.15.1 (UNSUPPORTED)
|
||||
----------------------------------
|
||||
|
||||
"darkMode" split into "darkModeEnabled" and "darkModeInversionAlgorithm".
|
||||
|
||||
|
|
@ -90,6 +90,17 @@ https://chromium-review.googlesource.com/c/chromium/src/+/2232922
|
|||
|
||||
- Now needs to be 0 for dark and 1 for light
|
||||
(before: 0 no preference / 1 dark / 2 light)
|
||||
|
||||
Qt 6.2
|
||||
------
|
||||
|
||||
No significant changes over 5.15.3
|
||||
|
||||
Qt 6.3
|
||||
------
|
||||
|
||||
- New IncreaseTextContrast:
|
||||
https://chromium-review.googlesource.com/c/chromium/src/+/2893236
|
||||
"""
|
||||
|
||||
import os
|
||||
|
|
@ -111,12 +122,9 @@ class Variant(enum.Enum):
|
|||
|
||||
"""A dark mode variant."""
|
||||
|
||||
qt_511_to_513 = enum.auto()
|
||||
qt_514 = enum.auto()
|
||||
qt_515_0 = enum.auto()
|
||||
qt_515_1 = enum.auto()
|
||||
qt_515_2 = enum.auto()
|
||||
qt_515_3 = enum.auto()
|
||||
qt_63 = enum.auto()
|
||||
|
||||
|
||||
# Mapping from a colors.webpage.darkmode.algorithm setting value to
|
||||
|
|
@ -128,9 +136,6 @@ _ALGORITHMS = {
|
|||
'lightness-hsl': 3, # kInvertLightness
|
||||
'lightness-cielab': 4, # kInvertLightnessLAB
|
||||
}
|
||||
# kInvertLightnessLAB is not available with Qt < 5.14
|
||||
_ALGORITHMS_BEFORE_QT_514 = _ALGORITHMS.copy()
|
||||
_ALGORITHMS_BEFORE_QT_514['lightness-cielab'] = _ALGORITHMS['lightness-hsl']
|
||||
# Qt >= 5.15.3, based on dark_mode_settings.h
|
||||
_ALGORITHMS_NEW = {
|
||||
# 0: kSimpleInvertForTesting (not exposed)
|
||||
|
|
@ -160,6 +165,11 @@ _BOOLS = {
|
|||
False: 'false',
|
||||
}
|
||||
|
||||
_INT_BOOLS = {
|
||||
True: '1',
|
||||
False: '0',
|
||||
}
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class _Setting:
|
||||
|
|
@ -229,18 +239,40 @@ class _Definition:
|
|||
"""Get a new _Definition object with a changed attribute.
|
||||
|
||||
NOTE: This does *not* copy the settings list. Both objects will reference the
|
||||
same list.
|
||||
same (immutable) tuple.
|
||||
"""
|
||||
new = copy.copy(self)
|
||||
setattr(new, attr, value)
|
||||
return new
|
||||
|
||||
def copy_add_setting(self, setting: _Setting) -> '_Definition':
|
||||
"""Get a new _Definition object with an additional setting."""
|
||||
new = copy.copy(self)
|
||||
new._settings = self._settings + (setting,) # pylint: disable=protected-access
|
||||
return new
|
||||
|
||||
|
||||
# Our defaults for policy.images are different from Chromium's, so we mark it as
|
||||
# mandatory setting - except on Qt 5.15.0 where we don't, so we don't get the
|
||||
# workaround warning below if the setting wasn't explicitly customized.
|
||||
# mandatory setting.
|
||||
|
||||
_DEFINITIONS: MutableMapping[Variant, _Definition] = {
|
||||
Variant.qt_515_2: _Definition(
|
||||
_Setting('enabled', 'Enabled', _BOOLS),
|
||||
_Setting('algorithm', 'InversionAlgorithm', _ALGORITHMS),
|
||||
|
||||
_Setting('policy.images', 'ImagePolicy', _IMAGE_POLICIES),
|
||||
_Setting('contrast', 'Contrast'),
|
||||
_Setting('grayscale.all', 'Grayscale', _BOOLS),
|
||||
|
||||
_Setting('policy.page', 'PagePolicy', _PAGE_POLICIES),
|
||||
_Setting('threshold.text', 'TextBrightnessThreshold'),
|
||||
_Setting('threshold.background', 'BackgroundBrightnessThreshold'),
|
||||
_Setting('grayscale.images', 'ImageGrayscale'),
|
||||
|
||||
mandatory={'enabled', 'policy.images'},
|
||||
prefix='forceDarkMode',
|
||||
),
|
||||
|
||||
Variant.qt_515_3: _Definition(
|
||||
# Different switch for settings
|
||||
_Setting('enabled', 'forceDarkModeEnabled', _BOOLS),
|
||||
|
|
@ -258,77 +290,34 @@ _DEFINITIONS: MutableMapping[Variant, _Definition] = {
|
|||
prefix='',
|
||||
switch_names={'enabled': _BLINK_SETTINGS, None: 'dark-mode-settings'},
|
||||
),
|
||||
|
||||
# Qt 5.15.1 and 5.15.2 get added below, since there are only minor differences.
|
||||
|
||||
Variant.qt_515_0: _Definition(
|
||||
# 'policy.images' not mandatory because it's broken
|
||||
_Setting('enabled', 'Enabled', _BOOLS),
|
||||
_Setting('algorithm', 'InversionAlgorithm', _ALGORITHMS),
|
||||
|
||||
_Setting('policy.images', 'ImagePolicy', _IMAGE_POLICIES),
|
||||
_Setting('contrast', 'Contrast'),
|
||||
_Setting('grayscale.all', 'Grayscale', _BOOLS),
|
||||
|
||||
_Setting('policy.page', 'PagePolicy', _PAGE_POLICIES),
|
||||
_Setting('threshold.text', 'TextBrightnessThreshold'),
|
||||
_Setting('threshold.background', 'BackgroundBrightnessThreshold'),
|
||||
_Setting('grayscale.images', 'ImageGrayscale'),
|
||||
|
||||
mandatory={'enabled'},
|
||||
prefix='darkMode',
|
||||
),
|
||||
|
||||
Variant.qt_514: _Definition(
|
||||
_Setting('algorithm', '', _ALGORITHMS), # new: kInvertLightnessLAB
|
||||
|
||||
_Setting('policy.images', 'ImagePolicy', _IMAGE_POLICIES),
|
||||
_Setting('contrast', 'Contrast'),
|
||||
_Setting('grayscale.all', 'Grayscale', _BOOLS),
|
||||
|
||||
_Setting('policy.page', 'PagePolicy', _PAGE_POLICIES),
|
||||
_Setting('threshold.text', 'TextBrightnessThreshold'),
|
||||
_Setting('threshold.background', 'BackgroundBrightnessThreshold'),
|
||||
_Setting('grayscale.images', 'ImageGrayscale'),
|
||||
|
||||
mandatory={'algorithm', 'policy.images'},
|
||||
prefix='darkMode',
|
||||
),
|
||||
|
||||
Variant.qt_511_to_513: _Definition(
|
||||
_Setting('algorithm', 'Mode', _ALGORITHMS_BEFORE_QT_514),
|
||||
|
||||
_Setting('policy.images', 'ImagePolicy', _IMAGE_POLICIES),
|
||||
_Setting('contrast', 'Contrast'),
|
||||
_Setting('grayscale.all', 'Grayscale', _BOOLS),
|
||||
|
||||
mandatory={'algorithm', 'policy.images'},
|
||||
prefix='highContrast',
|
||||
),
|
||||
}
|
||||
_DEFINITIONS[Variant.qt_515_1] = (
|
||||
_DEFINITIONS[Variant.qt_515_0].copy_with('mandatory', {'enabled', 'policy.images'}))
|
||||
_DEFINITIONS[Variant.qt_515_2] = (
|
||||
_DEFINITIONS[Variant.qt_515_1].copy_with('prefix', 'forceDarkMode'))
|
||||
_DEFINITIONS[Variant.qt_63] = _DEFINITIONS[Variant.qt_515_3].copy_add_setting(
|
||||
_Setting('increase_text_contrast', 'IncreaseTextContrast', _INT_BOOLS),
|
||||
)
|
||||
|
||||
|
||||
_PREFERRED_COLOR_SCHEME_DEFINITIONS = {
|
||||
# With older Qt versions, this is passed in qtargs.py as --force-dark-mode
|
||||
# instead.
|
||||
_SettingValType = Union[str, usertypes.Unset]
|
||||
_PREFERRED_COLOR_SCHEME_DEFINITIONS: Mapping[Variant, Mapping[_SettingValType, str]] = {
|
||||
Variant.qt_515_2: {
|
||||
# 0: no-preference (not exposed)
|
||||
"dark": "1",
|
||||
"light": "2",
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-89753
|
||||
# Fall back to "light" instead of "no preference" (which was removed from the
|
||||
# standard)
|
||||
"auto": "2",
|
||||
usertypes.UNSET: "2",
|
||||
},
|
||||
|
||||
## Qt 5.15.2
|
||||
# 0: no-preference (not exposed)
|
||||
(Variant.qt_515_2, "dark"): "1",
|
||||
(Variant.qt_515_2, "light"): "2",
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-89753
|
||||
# Fall back to "light" instead of "no preference" (which was removed from the
|
||||
# standard)
|
||||
(Variant.qt_515_2, "auto"): "2",
|
||||
(Variant.qt_515_2, usertypes.UNSET): "2",
|
||||
Variant.qt_515_3: {
|
||||
"dark": "0",
|
||||
"light": "1",
|
||||
},
|
||||
|
||||
## Qt >= 5.15.3
|
||||
(Variant.qt_515_3, "dark"): "0",
|
||||
(Variant.qt_515_3, "light"): "1",
|
||||
Variant.qt_63: {
|
||||
"dark": "0",
|
||||
"light": "1",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -341,7 +330,9 @@ def _variant(versions: version.WebEngineVersions) -> Variant:
|
|||
except KeyError:
|
||||
log.init.warning(f"Ignoring invalid QUTE_DARKMODE_VARIANT={env_var}")
|
||||
|
||||
if (versions.webengine == utils.VersionNumber(5, 15, 2) and
|
||||
if versions.webengine >= utils.VersionNumber(6, 3):
|
||||
return Variant.qt_63
|
||||
elif (versions.webengine == utils.VersionNumber(5, 15, 2) and
|
||||
versions.chromium_major == 87):
|
||||
# WORKAROUND for Gentoo packaging something newer as 5.15.2...
|
||||
return Variant.qt_515_3
|
||||
|
|
@ -349,14 +340,6 @@ def _variant(versions: version.WebEngineVersions) -> Variant:
|
|||
return Variant.qt_515_3
|
||||
elif versions.webengine >= utils.VersionNumber(5, 15, 2):
|
||||
return Variant.qt_515_2
|
||||
elif versions.webengine == utils.VersionNumber(5, 15, 1):
|
||||
return Variant.qt_515_1
|
||||
elif versions.webengine == utils.VersionNumber(5, 15):
|
||||
return Variant.qt_515_0
|
||||
elif versions.webengine >= utils.VersionNumber(5, 14):
|
||||
return Variant.qt_514
|
||||
elif versions.webengine >= utils.VersionNumber(5, 11):
|
||||
return Variant.qt_511_to_513
|
||||
raise utils.Unreachable(versions.webengine)
|
||||
|
||||
|
||||
|
|
@ -387,12 +370,11 @@ def settings(
|
|||
key, val = pair.split('=', maxsplit=1)
|
||||
result[_BLINK_SETTINGS].append((key, val))
|
||||
|
||||
preferred_color_scheme_key = (
|
||||
variant,
|
||||
config.instance.get("colors.webpage.preferred_color_scheme", fallback=False),
|
||||
)
|
||||
if preferred_color_scheme_key in _PREFERRED_COLOR_SCHEME_DEFINITIONS:
|
||||
value = _PREFERRED_COLOR_SCHEME_DEFINITIONS[preferred_color_scheme_key]
|
||||
preferred_color_scheme_key = config.instance.get(
|
||||
"colors.webpage.preferred_color_scheme", fallback=False)
|
||||
preferred_color_scheme_defs = _PREFERRED_COLOR_SCHEME_DEFINITIONS[variant]
|
||||
if preferred_color_scheme_key in preferred_color_scheme_defs:
|
||||
value = preferred_color_scheme_defs[preferred_color_scheme_key]
|
||||
result[_BLINK_SETTINGS].append(("preferredColorScheme", value))
|
||||
|
||||
if not config.val.colors.webpage.darkmode.enabled:
|
||||
|
|
@ -411,14 +393,6 @@ def settings(
|
|||
if isinstance(value, usertypes.Unset):
|
||||
continue
|
||||
|
||||
if (setting.option == 'policy.images' and value == 'smart' and
|
||||
variant == Variant.qt_515_0):
|
||||
# WORKAROUND for
|
||||
# https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211
|
||||
log.init.warning("Ignoring colors.webpage.darkmode.policy.images = smart "
|
||||
"because of Qt 5.15.0 bug")
|
||||
continue
|
||||
|
||||
result[switch_name].append(setting.chromium_tuple(value))
|
||||
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
"""A request interceptor taking care of adblocking and custom headers."""
|
||||
|
||||
from PyQt5.QtCore import QUrl, QByteArray
|
||||
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
|
||||
from qutebrowser.qt.core import QUrl, QByteArray
|
||||
from qutebrowser.qt.webenginecore import (QWebEngineUrlRequestInterceptor,
|
||||
QWebEngineUrlRequestInfo)
|
||||
|
||||
from qutebrowser.config import websettings, config
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.utils import utils, log, debug, qtutils
|
||||
from qutebrowser.utils import debug, log
|
||||
from qutebrowser.extensions import interceptors
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
|
@ -71,80 +71,71 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
|||
# extension ResourceTypes. If a ResourceType is added to Qt, this table
|
||||
# should be updated too.
|
||||
self._resource_types = {
|
||||
QWebEngineUrlRequestInfo.ResourceTypeMainFrame:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeMainFrame:
|
||||
interceptors.ResourceType.main_frame,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeSubFrame:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeSubFrame:
|
||||
interceptors.ResourceType.sub_frame,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeStylesheet:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeStylesheet:
|
||||
interceptors.ResourceType.stylesheet,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeScript:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeScript:
|
||||
interceptors.ResourceType.script,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeImage:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeImage:
|
||||
interceptors.ResourceType.image,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeFontResource:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeFontResource:
|
||||
interceptors.ResourceType.font_resource,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeSubResource:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeSubResource:
|
||||
interceptors.ResourceType.sub_resource,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeObject:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeObject:
|
||||
interceptors.ResourceType.object,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeMedia:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeMedia:
|
||||
interceptors.ResourceType.media,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeWorker:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeWorker:
|
||||
interceptors.ResourceType.worker,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeSharedWorker:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeSharedWorker:
|
||||
interceptors.ResourceType.shared_worker,
|
||||
QWebEngineUrlRequestInfo.ResourceTypePrefetch:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypePrefetch:
|
||||
interceptors.ResourceType.prefetch,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeFavicon:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeFavicon:
|
||||
interceptors.ResourceType.favicon,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeXhr:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeXhr:
|
||||
interceptors.ResourceType.xhr,
|
||||
QWebEngineUrlRequestInfo.ResourceTypePing:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypePing:
|
||||
interceptors.ResourceType.ping,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeServiceWorker:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeServiceWorker:
|
||||
interceptors.ResourceType.service_worker,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeCspReport:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeCspReport:
|
||||
interceptors.ResourceType.csp_report,
|
||||
QWebEngineUrlRequestInfo.ResourceTypePluginResource:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypePluginResource:
|
||||
interceptors.ResourceType.plugin_resource,
|
||||
QWebEngineUrlRequestInfo.ResourceTypeUnknown:
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeUnknown:
|
||||
interceptors.ResourceType.unknown,
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeNavigationPreloadMainFrame:
|
||||
interceptors.ResourceType.preload_main_frame,
|
||||
QWebEngineUrlRequestInfo.ResourceType.ResourceTypeNavigationPreloadSubFrame:
|
||||
interceptors.ResourceType.preload_sub_frame,
|
||||
}
|
||||
|
||||
try:
|
||||
preload_main_frame = (QWebEngineUrlRequestInfo.
|
||||
ResourceTypeNavigationPreloadMainFrame)
|
||||
preload_sub_frame = (QWebEngineUrlRequestInfo.
|
||||
ResourceTypeNavigationPreloadSubFrame)
|
||||
except AttributeError:
|
||||
# Added in Qt 5.14
|
||||
pass
|
||||
else:
|
||||
self._resource_types[preload_main_frame] = (
|
||||
interceptors.ResourceType.preload_main_frame)
|
||||
self._resource_types[preload_sub_frame] = (
|
||||
interceptors.ResourceType.preload_sub_frame)
|
||||
new_types = {
|
||||
"WebSocket": interceptors.ResourceType.websocket, # added in Qt 6.4
|
||||
}
|
||||
for qt_name, qb_value in new_types.items():
|
||||
qt_value = getattr(
|
||||
QWebEngineUrlRequestInfo.ResourceType,
|
||||
f"ResourceType{qt_name}",
|
||||
None,
|
||||
)
|
||||
if qt_value is not None:
|
||||
self._resource_types[qt_value] = qb_value
|
||||
|
||||
def install(self, profile):
|
||||
"""Install the interceptor on the given QWebEngineProfile."""
|
||||
try:
|
||||
# Qt >= 5.13, GUI thread
|
||||
profile.setUrlRequestInterceptor(self)
|
||||
except AttributeError:
|
||||
# Qt 5.12, IO thread
|
||||
profile.setRequestInterceptor(self)
|
||||
profile.setUrlRequestInterceptor(self)
|
||||
|
||||
# Gets called in the IO thread -> showing crash window will fail
|
||||
@utils.prevent_exceptions(None, not qtutils.version_check('5.13'))
|
||||
def interceptRequest(self, info):
|
||||
"""Handle the given request.
|
||||
|
||||
Reimplementing this virtual function and setting the interceptor on a
|
||||
profile makes it possible to intercept URL requests.
|
||||
|
||||
On Qt < 5.13, this function is executed on the IO thread, and therefore
|
||||
running long tasks here will block networking.
|
||||
|
||||
info contains the information about the URL request and will track
|
||||
internally whether its members have been altered.
|
||||
|
||||
|
|
@ -181,7 +172,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
|||
info.resourceType())))
|
||||
resource_type = interceptors.ResourceType.unknown
|
||||
|
||||
is_xhr = info.resourceType() == QWebEngineUrlRequestInfo.ResourceTypeXhr
|
||||
is_xhr = info.resourceType() == QWebEngineUrlRequestInfo.ResourceType.ResourceTypeXhr
|
||||
|
||||
if ((url.scheme(), url.host(), url.path()) ==
|
||||
('qute', 'settings', '/set')):
|
||||
|
|
@ -214,9 +205,6 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
|||
continue
|
||||
info.setHttpHeader(header, value)
|
||||
|
||||
# Note this is ignored before Qt 5.12.4 and 5.13.1 due to
|
||||
# https://bugreports.qt.io/browse/QTBUG-60203 - there, we set the
|
||||
# commandline-flag in qtargs.py instead.
|
||||
if config.cache['content.headers.referer'] == 'never':
|
||||
info.setHttpHeader(b'Referer', b'')
|
||||
|
||||
|
|
|
|||
|
|
@ -50,23 +50,24 @@ import functools
|
|||
import subprocess
|
||||
from typing import Any, List, Dict, Optional, Iterator, Type, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import (Qt, QObject, QVariant, QMetaType, QByteArray, pyqtSlot,
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import (Qt, QObject, QVariant, QMetaType, QByteArray, pyqtSlot,
|
||||
pyqtSignal, QTimer, QProcess, QUrl)
|
||||
from PyQt5.QtGui import QImage, QIcon, QPixmap
|
||||
from PyQt5.QtDBus import (QDBusConnection, QDBusInterface, QDBus, QDBusServiceWatcher,
|
||||
from qutebrowser.qt.gui import QImage, QIcon, QPixmap
|
||||
from qutebrowser.qt.dbus import (QDBusConnection, QDBusInterface, QDBus, QDBusServiceWatcher,
|
||||
QDBusArgument, QDBusMessage, QDBusError)
|
||||
from PyQt5.QtWidgets import QSystemTrayIcon
|
||||
from qutebrowser.qt.widgets import QSystemTrayIcon
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# putting these behind TYPE_CHECKING also means this module is importable
|
||||
# on installs that don't have these
|
||||
from PyQt5.QtWebEngineCore import QWebEngineNotification
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineProfile
|
||||
from qutebrowser.qt.webenginecore import QWebEngineNotification
|
||||
from qutebrowser.qt.webenginewidgets import QWebEngineProfile
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.utils import (
|
||||
qtutils, log, utils, debug, message, version, objreg, resources,
|
||||
qtutils, log, utils, debug, message, objreg, resources,
|
||||
)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
|
@ -74,12 +75,6 @@ from qutebrowser.qt import sip
|
|||
bridge: Optional['NotificationBridgePresenter'] = None
|
||||
|
||||
|
||||
def _notifications_supported() -> bool:
|
||||
"""Check whether the current QtWebEngine version has notification support."""
|
||||
versions = version.qtwebengine_versions(avoid_init=True)
|
||||
return versions.webengine >= utils.VersionNumber(5, 14)
|
||||
|
||||
|
||||
def init() -> None:
|
||||
"""Initialize the DBus notification presenter, if applicable.
|
||||
|
||||
|
|
@ -94,9 +89,6 @@ def init() -> None:
|
|||
# to its usefulness.
|
||||
return
|
||||
|
||||
if not _notifications_supported():
|
||||
return
|
||||
|
||||
global bridge
|
||||
bridge = NotificationBridgePresenter()
|
||||
|
||||
|
|
@ -140,7 +132,7 @@ class DBusError(Error):
|
|||
}
|
||||
|
||||
def __init__(self, msg: QDBusMessage) -> None:
|
||||
assert msg.type() == QDBusMessage.ErrorMessage
|
||||
assert msg.type() == QDBusMessage.MessageType.ErrorMessage
|
||||
self.error = msg.errorName()
|
||||
self.error_message = msg.errorMessage()
|
||||
self.is_fatal = self.error not in self._NON_FATAL_ERRORS
|
||||
|
|
@ -208,7 +200,6 @@ class NotificationBridgePresenter(QObject):
|
|||
"""Notification presenter which bridges notifications to an adapter.
|
||||
|
||||
Takes care of:
|
||||
- Working around bugs in PyQt 5.14
|
||||
- Storing currently shown notifications, using an ID returned by the adapter.
|
||||
- Initializing a suitable adapter when the first notification is shown.
|
||||
- Switching out adapters if the current one emitted its error signal.
|
||||
|
|
@ -216,7 +207,6 @@ class NotificationBridgePresenter(QObject):
|
|||
|
||||
def __init__(self, parent: QObject = None) -> None:
|
||||
super().__init__(parent)
|
||||
assert _notifications_supported()
|
||||
|
||||
self._active_notifications: Dict[int, 'QWebEngineNotification'] = {}
|
||||
self._adapter: Optional[AbstractNotificationAdapter] = None
|
||||
|
|
@ -280,24 +270,7 @@ class NotificationBridgePresenter(QObject):
|
|||
|
||||
def install(self, profile: "QWebEngineProfile") -> None:
|
||||
"""Set the profile to use this bridge as the presenter."""
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042916.html
|
||||
# Fixed in PyQtWebEngine 5.15.0
|
||||
# PYQT_WEBENGINE_VERSION was added with PyQtWebEngine 5.13, but if we're here,
|
||||
# we already did a version check above.
|
||||
from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION
|
||||
if PYQT_WEBENGINE_VERSION < 0x050F00:
|
||||
# PyQtWebEngine unrefs the callback after it's called, for some
|
||||
# reason. So we call setNotificationPresenter again to *increase*
|
||||
# its refcount to prevent it from getting GC'd. Otherwise, random
|
||||
# methods start getting called with the notification as `self`, or
|
||||
# segfaults happen, or other badness.
|
||||
def _present_and_reset(qt_notification: "QWebEngineNotification") -> None:
|
||||
profile.setNotificationPresenter(_present_and_reset)
|
||||
self.present(qt_notification)
|
||||
profile.setNotificationPresenter(_present_and_reset)
|
||||
else:
|
||||
profile.setNotificationPresenter(self.present)
|
||||
profile.setNotificationPresenter(self.present)
|
||||
|
||||
def present(self, qt_notification: "QWebEngineNotification") -> None:
|
||||
"""Show a notification using the configured adapter.
|
||||
|
|
@ -349,18 +322,11 @@ class NotificationBridgePresenter(QObject):
|
|||
f"Finding notification for tag {new_notification.tag()}, "
|
||||
f"origin {new_notification.origin()}")
|
||||
|
||||
try:
|
||||
for notification_id, notification in sorted(
|
||||
self._active_notifications.items(), reverse=True):
|
||||
if notification.matches(new_notification):
|
||||
log.misc.debug(f"Found match: {notification_id}")
|
||||
return notification_id
|
||||
except RuntimeError:
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042918.html
|
||||
# (also affects .matches)
|
||||
log.misc.debug(
|
||||
f"Ignoring notification tag {new_notification.tag()!r} due to PyQt bug")
|
||||
for notification_id, notification in sorted(
|
||||
self._active_notifications.items(), reverse=True):
|
||||
if notification.matches(new_notification):
|
||||
log.misc.debug(f"Found match: {notification_id}")
|
||||
return notification_id
|
||||
|
||||
log.misc.debug("Did not find match")
|
||||
return None
|
||||
|
|
@ -381,13 +347,7 @@ class NotificationBridgePresenter(QObject):
|
|||
# Notification from a different application
|
||||
return
|
||||
|
||||
try:
|
||||
notification.close()
|
||||
except RuntimeError:
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042918.html
|
||||
log.misc.debug(f"Ignoring close request for notification {notification_id} "
|
||||
"due to PyQt bug")
|
||||
notification.close()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _on_adapter_clicked(self, notification_id: int) -> None:
|
||||
|
|
@ -405,21 +365,14 @@ class NotificationBridgePresenter(QObject):
|
|||
log.misc.debug("Did not find matching notification, ignoring")
|
||||
return
|
||||
|
||||
try:
|
||||
notification.click()
|
||||
except RuntimeError:
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042918.html
|
||||
log.misc.debug(f"Ignoring click request for notification {notification_id} "
|
||||
"due to PyQt bug")
|
||||
return
|
||||
notification.click()
|
||||
self._focus_first_matching_tab(notification)
|
||||
|
||||
def _focus_first_matching_tab(self, notification: "QWebEngineNotification") -> None:
|
||||
for win_id in objreg.window_registry:
|
||||
tabbedbrowser = objreg.get("tabbed-browser", window=win_id, scope="window")
|
||||
for idx, tab in enumerate(tabbedbrowser.widgets()):
|
||||
if tab.url().matches(notification.origin(), QUrl.RemovePath):
|
||||
if tab.url().matches(notification.origin(), QUrl.UrlFormattingOption.RemovePath):
|
||||
tabbedbrowser.widget.setCurrentIndex(idx)
|
||||
return
|
||||
log.misc.debug(f"No matching tab found for {notification.origin()}")
|
||||
|
|
@ -514,7 +467,7 @@ class SystrayNotificationAdapter(AbstractNotificationAdapter):
|
|||
"""Convert a QImage to a QIcon."""
|
||||
if image.isNull():
|
||||
return QIcon()
|
||||
pixmap = QPixmap.fromImage(image, Qt.NoFormatConversion)
|
||||
pixmap = QPixmap.fromImage(image, Qt.ImageConversionFlag.NoFormatConversion)
|
||||
assert not pixmap.isNull()
|
||||
icon = QIcon(pixmap)
|
||||
assert not icon.isNull()
|
||||
|
|
@ -665,7 +618,7 @@ class HerbeNotificationAdapter(AbstractNotificationAdapter):
|
|||
signals, we can't do much - emitting self.error would just go use herbe again,
|
||||
so there's no point.
|
||||
"""
|
||||
if status == QProcess.CrashExit:
|
||||
if status == QProcess.ExitStatus.CrashExit:
|
||||
pass
|
||||
elif code == 0:
|
||||
self.click_id.emit(pid)
|
||||
|
|
@ -681,7 +634,7 @@ class HerbeNotificationAdapter(AbstractNotificationAdapter):
|
|||
|
||||
@pyqtSlot(QProcess.ProcessError)
|
||||
def _on_error(self, error: QProcess.ProcessError) -> None:
|
||||
if error == QProcess.Crashed:
|
||||
if error == QProcess.ProcessError.Crashed:
|
||||
return
|
||||
name = debug.qenum_key(QProcess.ProcessError, error)
|
||||
raise Error(f'herbe process error: {name}')
|
||||
|
|
@ -737,7 +690,13 @@ class _ServerCapabilities:
|
|||
def _as_uint32(x: int) -> QVariant:
|
||||
"""Convert the given int to an uint32 for DBus."""
|
||||
variant = QVariant(x)
|
||||
successful = variant.convert(QVariant.UInt)
|
||||
|
||||
if machinery.IS_QT5:
|
||||
target_type = QVariant.Type.UInt
|
||||
else: # Qt 6
|
||||
target_type = QMetaType(QMetaType.Type.UInt.value)
|
||||
|
||||
successful = variant.convert(target_type)
|
||||
assert successful
|
||||
return variant
|
||||
|
||||
|
|
@ -763,7 +722,6 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
|
||||
def __init__(self, parent: QObject = None) -> None:
|
||||
super().__init__(parent)
|
||||
assert _notifications_supported()
|
||||
|
||||
if utils.is_windows:
|
||||
# The QDBusConnection destructor seems to cause error messages (and
|
||||
|
|
@ -781,7 +739,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
self._watcher = QDBusServiceWatcher(
|
||||
self.SERVICE,
|
||||
bus,
|
||||
QDBusServiceWatcher.WatchForUnregistration,
|
||||
QDBusServiceWatcher.WatchModeFlag.WatchForUnregistration,
|
||||
self,
|
||||
)
|
||||
self._watcher.serviceUnregistered.connect(self._on_service_unregistered)
|
||||
|
|
@ -900,8 +858,8 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
|
||||
def _get_server_info(self) -> None:
|
||||
"""Query notification server information and set quirks."""
|
||||
reply = self.interface.call(QDBus.BlockWithGui, "GetServerInformation")
|
||||
self._verify_message(reply, "ssss", QDBusMessage.ReplyMessage)
|
||||
reply = self.interface.call(QDBus.CallMode.BlockWithGui, "GetServerInformation")
|
||||
self._verify_message(reply, "ssss", QDBusMessage.MessageType.ReplyMessage)
|
||||
name, vendor, ver, spec_version = reply.arguments()
|
||||
|
||||
log.misc.debug(
|
||||
|
|
@ -945,11 +903,11 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
Raises DBusError if the signature doesn't match.
|
||||
"""
|
||||
assert expected_type not in [
|
||||
QDBusMessage.ErrorMessage,
|
||||
QDBusMessage.InvalidMessage,
|
||||
QDBusMessage.MessageType.ErrorMessage,
|
||||
QDBusMessage.MessageType.InvalidMessage,
|
||||
], expected_type
|
||||
|
||||
if msg.type() == QDBusMessage.ErrorMessage:
|
||||
if msg.type() == QDBusMessage.MessageType.ErrorMessage:
|
||||
raise DBusError(msg)
|
||||
|
||||
signature = msg.signature()
|
||||
|
|
@ -997,7 +955,10 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
actions = []
|
||||
if self._capabilities.actions:
|
||||
actions = ['default', 'Activate'] # key, name
|
||||
return QDBusArgument(actions, QMetaType.QStringList)
|
||||
return QDBusArgument(
|
||||
actions,
|
||||
qtutils.extract_enum_val(QMetaType.Type.QStringList),
|
||||
)
|
||||
|
||||
def _get_hints_arg(self, *, origin_url: QUrl, icon: QImage) -> Dict[str, Any]:
|
||||
"""Get the hints argument for present()."""
|
||||
|
|
@ -1037,7 +998,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
) -> Any:
|
||||
"""Wrapper around DBus call to use keyword args."""
|
||||
return self.interface.call(
|
||||
QDBus.BlockWithGui,
|
||||
QDBus.CallMode.BlockWithGui,
|
||||
"Notify",
|
||||
appname,
|
||||
replaces_id,
|
||||
|
|
@ -1077,7 +1038,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
)
|
||||
|
||||
try:
|
||||
self._verify_message(reply, "u", QDBusMessage.ReplyMessage)
|
||||
self._verify_message(reply, "u", QDBusMessage.MessageType.ReplyMessage)
|
||||
except DBusError as e:
|
||||
if e.is_fatal:
|
||||
raise
|
||||
|
|
@ -1097,10 +1058,10 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
bits_per_color = 8
|
||||
has_alpha = qimage.hasAlphaChannel()
|
||||
if has_alpha:
|
||||
image_format = QImage.Format_RGBA8888
|
||||
image_format = QImage.Format.Format_RGBA8888
|
||||
channel_count = 4
|
||||
else:
|
||||
image_format = QImage.Format_RGB888
|
||||
image_format = QImage.Format.Format_RGB888
|
||||
channel_count = 3
|
||||
|
||||
qimage.convertTo(image_format)
|
||||
|
|
@ -1117,14 +1078,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
image_data.add(bits_per_color)
|
||||
image_data.add(channel_count)
|
||||
|
||||
try:
|
||||
size = qimage.sizeInBytes()
|
||||
except TypeError:
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042919.html
|
||||
# byteCount() is obsolete, but sizeInBytes() is only available with
|
||||
# SIP >= 5.3.0.
|
||||
size = qimage.byteCount()
|
||||
size = qimage.sizeInBytes()
|
||||
|
||||
# Despite the spec not mandating this, many notification daemons mandate that
|
||||
# the last scanline does not have any padding bytes.
|
||||
|
|
@ -1166,11 +1120,11 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
def _handle_close(self, msg: QDBusMessage) -> None:
|
||||
"""Handle NotificationClosed from DBus."""
|
||||
try:
|
||||
self._verify_message(msg, "uu", QDBusMessage.SignalMessage)
|
||||
self._verify_message(msg, "uu", QDBusMessage.MessageType.SignalMessage)
|
||||
except Error:
|
||||
if not self._quirks.wrong_closes_type:
|
||||
raise
|
||||
self._verify_message(msg, "ui", QDBusMessage.SignalMessage)
|
||||
self._verify_message(msg, "ui", QDBusMessage.MessageType.SignalMessage)
|
||||
|
||||
notification_id, _close_reason = msg.arguments()
|
||||
self.close_id.emit(notification_id)
|
||||
|
|
@ -1178,7 +1132,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
@pyqtSlot(QDBusMessage)
|
||||
def _handle_action(self, msg: QDBusMessage) -> None:
|
||||
"""Handle ActionInvoked from DBus."""
|
||||
self._verify_message(msg, "us", QDBusMessage.SignalMessage)
|
||||
self._verify_message(msg, "us", QDBusMessage.MessageType.SignalMessage)
|
||||
notification_id, action_key = msg.arguments()
|
||||
if action_key == "default":
|
||||
self.click_id.emit(notification_id)
|
||||
|
|
@ -1187,7 +1141,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
def on_web_closed(self, notification_id: int) -> None:
|
||||
"""Send CloseNotification if a notification was closed from JS."""
|
||||
self.interface.call(
|
||||
QDBus.NoBlock,
|
||||
QDBus.CallMode.NoBlock,
|
||||
"CloseNotification",
|
||||
_as_uint32(notification_id),
|
||||
)
|
||||
|
|
@ -1195,10 +1149,10 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
def _fetch_capabilities(self) -> None:
|
||||
"""Fetch capabilities from the notification server."""
|
||||
reply = self.interface.call(
|
||||
QDBus.BlockWithGui,
|
||||
QDBus.CallMode.BlockWithGui,
|
||||
"GetCapabilities",
|
||||
)
|
||||
self._verify_message(reply, "as", QDBusMessage.ReplyMessage)
|
||||
self._verify_message(reply, "as", QDBusMessage.MessageType.ReplyMessage)
|
||||
|
||||
caplist = reply.arguments()[0]
|
||||
self._capabilities = _ServerCapabilities.from_list(caplist)
|
||||
|
|
@ -1225,7 +1179,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
|
|||
prefix = None
|
||||
elif self._capabilities.body_markup and self._capabilities.body_hyperlinks:
|
||||
href = html.escape(
|
||||
origin_url.toString(QUrl.FullyEncoded) # type: ignore[arg-type]
|
||||
origin_url.toString(QUrl.ComponentFormattingOption.FullyEncoded) # type: ignore[arg-type]
|
||||
)
|
||||
text = html.escape(urlstr, quote=False)
|
||||
prefix = f'<a href="{href}">{text}</a>'
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""QWebHistory serializer for QtWebEngine."""
|
||||
|
||||
from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
|
||||
from qutebrowser.qt.core import QByteArray, QDataStream, QIODevice, QUrl
|
||||
|
||||
from qutebrowser.utils import qtutils
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ def serialize(items):
|
|||
segfault!
|
||||
"""
|
||||
data = QByteArray()
|
||||
stream = QDataStream(data, QIODevice.ReadWrite)
|
||||
stream = QDataStream(data, QIODevice.OpenModeFlag.ReadWrite)
|
||||
cur_user_data = None
|
||||
|
||||
current_idx = None
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ import re
|
|||
import os.path
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QObject
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import pyqtSlot, Qt, QUrl, QObject
|
||||
from qutebrowser.qt.webenginecore import QWebEngineDownloadRequest
|
||||
|
||||
from qutebrowser.browser import downloads, pdfjs
|
||||
from qutebrowser.utils import (debug, usertypes, message, log, objreg, urlutils,
|
||||
|
|
@ -33,19 +34,34 @@ from qutebrowser.utils import (debug, usertypes, message, log, objreg, urlutils,
|
|||
|
||||
class DownloadItem(downloads.AbstractDownloadItem):
|
||||
|
||||
"""A wrapper over a QWebEngineDownloadItem.
|
||||
"""A wrapper over a QWebEngineDownloadRequest.
|
||||
|
||||
Attributes:
|
||||
_qt_item: The wrapped item.
|
||||
"""
|
||||
|
||||
def __init__(self, qt_item: QWebEngineDownloadItem,
|
||||
def __init__(self, qt_item: QWebEngineDownloadRequest,
|
||||
manager: downloads.AbstractDownloadManager,
|
||||
parent: QObject = None) -> None:
|
||||
super().__init__(manager=manager, parent=manager)
|
||||
self._qt_item = qt_item
|
||||
qt_item.downloadProgress.connect(self.stats.on_download_progress)
|
||||
qt_item.stateChanged.connect(self._on_state_changed)
|
||||
if machinery.IS_QT5:
|
||||
qt_item.downloadProgress.connect(self.stats.on_download_progress)
|
||||
else: # Qt 6
|
||||
qt_item.receivedBytesChanged.connect(
|
||||
lambda: self.stats.on_download_progress(
|
||||
qt_item.receivedBytes(),
|
||||
qt_item.totalBytes(),
|
||||
)
|
||||
)
|
||||
qt_item.totalBytesChanged.connect(
|
||||
lambda: self.stats.on_download_progress(
|
||||
qt_item.receivedBytes(),
|
||||
qt_item.totalBytes(),
|
||||
)
|
||||
)
|
||||
qt_item.stateChanged.connect(
|
||||
self._on_state_changed)
|
||||
|
||||
# Ensure wrapped qt_item is deleted manually when the wrapper object
|
||||
# is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373
|
||||
|
|
@ -54,19 +70,19 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
def _is_page_download(self):
|
||||
"""Check if this item is a page (i.e. mhtml) download."""
|
||||
return (self._qt_item.savePageFormat() !=
|
||||
QWebEngineDownloadItem.UnknownSaveFormat)
|
||||
QWebEngineDownloadRequest.SavePageFormat.UnknownSaveFormat)
|
||||
|
||||
@pyqtSlot(QWebEngineDownloadItem.DownloadState)
|
||||
@pyqtSlot(QWebEngineDownloadRequest.DownloadState)
|
||||
def _on_state_changed(self, state):
|
||||
state_name = debug.qenum_key(QWebEngineDownloadItem, state)
|
||||
state_name = debug.qenum_key(QWebEngineDownloadRequest, state)
|
||||
log.downloads.debug("State for {!r} changed to {}".format(
|
||||
self, state_name))
|
||||
|
||||
if state == QWebEngineDownloadItem.DownloadRequested:
|
||||
if state == QWebEngineDownloadRequest.DownloadState.DownloadRequested:
|
||||
pass
|
||||
elif state == QWebEngineDownloadItem.DownloadInProgress:
|
||||
elif state == QWebEngineDownloadRequest.DownloadState.DownloadInProgress:
|
||||
pass
|
||||
elif state == QWebEngineDownloadItem.DownloadCompleted:
|
||||
elif state == QWebEngineDownloadRequest.DownloadState.DownloadCompleted:
|
||||
log.downloads.debug("Download {} finished".format(self.basename))
|
||||
if self._is_page_download():
|
||||
# Same logging as QtWebKit mhtml downloads.
|
||||
|
|
@ -75,12 +91,12 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
self.done = True
|
||||
self.finished.emit()
|
||||
self.stats.finish()
|
||||
elif state == QWebEngineDownloadItem.DownloadCancelled:
|
||||
elif state == QWebEngineDownloadRequest.DownloadState.DownloadCancelled:
|
||||
self.successful = False
|
||||
self.done = True
|
||||
self.cancelled.emit()
|
||||
self.stats.finish()
|
||||
elif state == QWebEngineDownloadItem.DownloadInterrupted:
|
||||
elif state == QWebEngineDownloadRequest.DownloadState.DownloadInterrupted:
|
||||
self.successful = False
|
||||
reason = self._qt_item.interruptReasonString()
|
||||
self._die(reason)
|
||||
|
|
@ -89,24 +105,28 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
"{}".format(state_name))
|
||||
|
||||
def _do_die(self):
|
||||
progress_signal = self._qt_item.downloadProgress
|
||||
progress_signal.disconnect()
|
||||
if self._qt_item.state() != QWebEngineDownloadItem.DownloadInterrupted:
|
||||
if machinery.IS_QT5:
|
||||
self._qt_item.downloadProgress.disconnect()
|
||||
else: # Qt 6
|
||||
self._qt_item.receivedBytesChanged.disconnect()
|
||||
self._qt_item.totalBytesChanged.disconnect()
|
||||
|
||||
if self._qt_item.state() != QWebEngineDownloadRequest.DownloadState.DownloadInterrupted:
|
||||
self._qt_item.cancel()
|
||||
|
||||
def _do_cancel(self):
|
||||
state = self._qt_item.state()
|
||||
state_name = debug.qenum_key(QWebEngineDownloadItem, state)
|
||||
assert state not in [QWebEngineDownloadItem.DownloadCompleted,
|
||||
QWebEngineDownloadItem.DownloadCancelled], state_name
|
||||
state_name = debug.qenum_key(QWebEngineDownloadRequest, state)
|
||||
assert state not in [QWebEngineDownloadRequest.DownloadState.DownloadCompleted,
|
||||
QWebEngineDownloadRequest.DownloadState.DownloadCancelled], state_name
|
||||
self._qt_item.cancel()
|
||||
|
||||
def retry(self):
|
||||
state = self._qt_item.state()
|
||||
if state != QWebEngineDownloadItem.DownloadInterrupted:
|
||||
if state != QWebEngineDownloadRequest.DownloadState.DownloadInterrupted:
|
||||
log.downloads.warning(
|
||||
"Refusing to retry download in state {}".format(
|
||||
debug.qenum_key(QWebEngineDownloadItem, state)))
|
||||
debug.qenum_key(QWebEngineDownloadRequest, state)))
|
||||
return
|
||||
|
||||
self._qt_item.resume()
|
||||
|
|
@ -131,8 +151,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
|
||||
def _ensure_can_set_filename(self, filename):
|
||||
state = self._qt_item.state()
|
||||
if state != QWebEngineDownloadItem.DownloadRequested:
|
||||
state_name = debug.qenum_key(QWebEngineDownloadItem, state)
|
||||
if state != QWebEngineDownloadRequest.DownloadState.DownloadRequested:
|
||||
state_name = debug.qenum_key(QWebEngineDownloadRequest, state)
|
||||
raise ValueError("Trying to set filename {} on {!r} which is "
|
||||
"state {} (not in requested state)!".format(
|
||||
filename, self, state_name))
|
||||
|
|
@ -174,12 +194,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
assert self._filename is not None
|
||||
|
||||
dirname, basename = os.path.split(self._filename)
|
||||
try:
|
||||
# Qt 5.14
|
||||
self._qt_item.setDownloadDirectory(dirname)
|
||||
self._qt_item.setDownloadFileName(basename)
|
||||
except AttributeError:
|
||||
self._qt_item.setPath(self._filename)
|
||||
self._qt_item.setDownloadDirectory(dirname)
|
||||
self._qt_item.setDownloadFileName(basename)
|
||||
|
||||
self._qt_item.accept()
|
||||
|
||||
|
|
@ -245,12 +261,12 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
|||
def install(self, profile):
|
||||
"""Set up the download manager on a QWebEngineProfile."""
|
||||
profile.downloadRequested.connect(self.handle_download,
|
||||
Qt.DirectConnection)
|
||||
Qt.ConnectionType.DirectConnection)
|
||||
|
||||
@pyqtSlot(QWebEngineDownloadItem)
|
||||
@pyqtSlot(QWebEngineDownloadRequest)
|
||||
def handle_download(self, qt_item):
|
||||
"""Start a download coming from a QWebEngineProfile."""
|
||||
qt_filename = os.path.basename(qt_item.path()) # FIXME use 5.14 API
|
||||
qt_filename = qt_item.downloadFileName()
|
||||
mime_type = qt_item.mimeType()
|
||||
url = qt_item.url()
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
from typing import (
|
||||
TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Set, Tuple, Union)
|
||||
|
||||
from PyQt5.QtCore import QRect, QEventLoop
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
|
||||
from qutebrowser.qt.core import QRect, QEventLoop
|
||||
from qutebrowser.qt.widgets import QApplication
|
||||
from qutebrowser.qt.webenginecore import QWebEngineSettings
|
||||
|
||||
from qutebrowser.utils import log, javascript, urlutils, usertypes, utils
|
||||
from qutebrowser.browser import webelem
|
||||
|
|
@ -231,7 +231,6 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
|
||||
|
||||
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
|
||||
self._tab.setFocus() # Needed as WORKAROUND on Qt 5.12
|
||||
# This actually "clicks" the element by calling focus() on it in JS.
|
||||
self._js_call('focus')
|
||||
self._move_text_cursor()
|
||||
|
|
@ -242,7 +241,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
view = self._tab._widget
|
||||
assert view is not None
|
||||
# pylint: enable=protected-access
|
||||
attribute = QWebEngineSettings.JavascriptCanOpenWindows
|
||||
attribute = QWebEngineSettings.WebAttribute.JavascriptCanOpenWindows
|
||||
could_open_windows = view.settings().testAttribute(attribute)
|
||||
view.settings().setAttribute(attribute, True)
|
||||
|
||||
|
|
@ -251,8 +250,8 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
# This is also used in Qt's tests:
|
||||
# https://github.com/qt/qtwebengine/commit/5e572e88efa7ba7c2b9138ec19e606d3e345ac90
|
||||
QApplication.processEvents(
|
||||
QEventLoop.ExcludeSocketNotifiers |
|
||||
QEventLoop.ExcludeUserInputEvents)
|
||||
QEventLoop.ProcessEventsFlag.ExcludeSocketNotifiers |
|
||||
QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents)
|
||||
|
||||
def reset_setting(_arg: Any) -> None:
|
||||
"""Set the JavascriptCanOpenWindows setting to its old value."""
|
||||
|
|
|
|||
|
|
@ -19,16 +19,17 @@
|
|||
|
||||
"""Customized QWebInspector for QtWebEngine."""
|
||||
|
||||
import pathlib
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import QLibraryInfo
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.webenginewidgets import QWebEngineView
|
||||
from qutebrowser.qt.webenginecore import QWebEnginePage
|
||||
from qutebrowser.qt.widgets import QWidget
|
||||
|
||||
from qutebrowser.browser import inspector
|
||||
from qutebrowser.browser.webengine import webenginesettings
|
||||
from qutebrowser.browser.webengine import webenginesettings, webview
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.utils import version, usertypes
|
||||
from qutebrowser.utils import version, usertypes, qtutils
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
||||
|
||||
|
|
@ -49,9 +50,14 @@ class WebEngineInspectorView(QWebEngineView):
|
|||
|
||||
See WebEngineView.createWindow for details.
|
||||
"""
|
||||
view = self.page().inspectedPage().view()
|
||||
assert isinstance(view, QWebEngineView), view
|
||||
return view.createWindow(wintype)
|
||||
inspected_page = self.page().inspectedPage()
|
||||
if machinery.IS_QT5:
|
||||
view = inspected_page.view()
|
||||
assert isinstance(view, QWebEngineView), view
|
||||
return view.createWindow(wintype)
|
||||
else: # Qt 6
|
||||
newpage = inspected_page.createWindow(wintype)
|
||||
return webview.WebEngineView.forPage(newpage)
|
||||
|
||||
|
||||
class WebEngineInspector(inspector.AbstractWebInspector):
|
||||
|
|
@ -65,12 +71,7 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
|||
parent: QWidget = None) -> None:
|
||||
super().__init__(splitter, win_id, parent)
|
||||
self._check_devtools_resources()
|
||||
|
||||
view = WebEngineInspectorView()
|
||||
self._settings = webenginesettings.WebEngineSettings(view.settings())
|
||||
self._set_widget(view)
|
||||
page = view.page()
|
||||
page.windowCloseRequested.connect(self._on_window_close_requested)
|
||||
self._settings: Optional[webenginesettings.WebEngineSettings] = None
|
||||
|
||||
def _on_window_close_requested(self) -> None:
|
||||
"""Called when the 'x' was clicked in the devtools."""
|
||||
|
|
@ -92,7 +93,7 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
|||
if dist is None or dist.parsed != version.Distribution.fedora:
|
||||
return
|
||||
|
||||
data_path = pathlib.Path(QLibraryInfo.location(QLibraryInfo.DataPath))
|
||||
data_path = qtutils.library_path(qtutils.LibraryPath.data)
|
||||
pak = data_path / 'resources' / 'qtwebengine_devtools_resources.pak'
|
||||
if not pak.exists():
|
||||
raise inspector.Error("QtWebEngine devtools resources not found, "
|
||||
|
|
@ -100,8 +101,22 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
|||
"Fedora package.")
|
||||
|
||||
def inspect(self, page: QWebEnginePage) -> None:
|
||||
if not self._widget:
|
||||
view = WebEngineInspectorView()
|
||||
inspector_page = QWebEnginePage(
|
||||
page.profile(),
|
||||
self
|
||||
)
|
||||
inspector_page.windowCloseRequested.connect(self._on_window_close_requested)
|
||||
view.setPage(inspector_page)
|
||||
self._settings = webenginesettings.WebEngineSettings(view.settings())
|
||||
self._set_widget(view)
|
||||
|
||||
inspector_page = self._widget.page()
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
"""QtWebEngine specific qute://* handlers and glue code."""
|
||||
|
||||
from PyQt5.QtCore import QBuffer, QIODevice, QUrl
|
||||
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
|
||||
from qutebrowser.qt.core import QBuffer, QIODevice, QUrl
|
||||
from qutebrowser.qt.webenginecore import (QWebEngineUrlSchemeHandler,
|
||||
QWebEngineUrlRequestJob,
|
||||
QWebEngineUrlScheme)
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||
log.network.warning("Blocking malicious request from {} to {}"
|
||||
.format(initiator.toDisplayString(),
|
||||
request_url.toDisplayString()))
|
||||
job.fail(QWebEngineUrlRequestJob.RequestDenied)
|
||||
job.fail(QWebEngineUrlRequestJob.Error.RequestDenied)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
@ -87,7 +87,7 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||
return
|
||||
|
||||
if job.requestMethod() != b'GET':
|
||||
job.fail(QWebEngineUrlRequestJob.RequestDenied)
|
||||
job.fail(QWebEngineUrlRequestJob.Error.RequestDenied)
|
||||
return
|
||||
|
||||
assert url.scheme() == 'qute'
|
||||
|
|
@ -98,15 +98,15 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||
except qutescheme.Error as e:
|
||||
errors = {
|
||||
qutescheme.NotFoundError:
|
||||
QWebEngineUrlRequestJob.UrlNotFound,
|
||||
QWebEngineUrlRequestJob.Error.UrlNotFound,
|
||||
qutescheme.UrlInvalidError:
|
||||
QWebEngineUrlRequestJob.UrlInvalid,
|
||||
QWebEngineUrlRequestJob.Error.UrlInvalid,
|
||||
qutescheme.RequestDeniedError:
|
||||
QWebEngineUrlRequestJob.RequestDenied,
|
||||
QWebEngineUrlRequestJob.Error.RequestDenied,
|
||||
qutescheme.SchemeOSError:
|
||||
QWebEngineUrlRequestJob.UrlNotFound,
|
||||
QWebEngineUrlRequestJob.Error.UrlNotFound,
|
||||
qutescheme.Error:
|
||||
QWebEngineUrlRequestJob.RequestFailed,
|
||||
QWebEngineUrlRequestJob.Error.RequestFailed,
|
||||
}
|
||||
exctype = type(e)
|
||||
log.network.error(f"{exctype.__name__} while handling qute://* URL: {e}")
|
||||
|
|
@ -121,7 +121,7 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||
# because that somehow segfaults...
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
|
||||
buf = QBuffer(parent=self)
|
||||
buf.open(QIODevice.WriteOnly)
|
||||
buf.open(QIODevice.OpenModeFlag.WriteOnly)
|
||||
buf.write(data)
|
||||
buf.seek(0)
|
||||
buf.close()
|
||||
|
|
@ -138,6 +138,6 @@ def init():
|
|||
assert not QWebEngineUrlScheme.schemeByName(b'qute').name()
|
||||
scheme = QWebEngineUrlScheme(b'qute')
|
||||
scheme.setFlags(
|
||||
QWebEngineUrlScheme.LocalScheme |
|
||||
QWebEngineUrlScheme.LocalAccessAllowed)
|
||||
QWebEngineUrlScheme.Flag.LocalScheme |
|
||||
QWebEngineUrlScheme.Flag.LocalAccessAllowed)
|
||||
QWebEngineUrlScheme.registerScheme(scheme)
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@ Module attributes:
|
|||
|
||||
import os
|
||||
import operator
|
||||
import pathlib
|
||||
from typing import cast, Any, List, Optional, Tuple, Union, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.gui import QFont
|
||||
from qutebrowser.qt.widgets import QApplication
|
||||
from qutebrowser.qt.webenginecore import QWebEngineSettings, QWebEngineProfile
|
||||
|
||||
from qutebrowser.browser import history
|
||||
from qutebrowser.browser.webengine import (spell, webenginequtescheme, cookies,
|
||||
|
|
@ -110,91 +112,91 @@ class WebEngineSettings(websettings.AbstractSettings):
|
|||
|
||||
_ATTRIBUTES = {
|
||||
'content.xss_auditing':
|
||||
Attr(QWebEngineSettings.XSSAuditingEnabled),
|
||||
Attr(QWebEngineSettings.WebAttribute.XSSAuditingEnabled),
|
||||
'content.images':
|
||||
Attr(QWebEngineSettings.AutoLoadImages),
|
||||
Attr(QWebEngineSettings.WebAttribute.AutoLoadImages),
|
||||
'content.javascript.enabled':
|
||||
Attr(QWebEngineSettings.JavascriptEnabled),
|
||||
Attr(QWebEngineSettings.WebAttribute.JavascriptEnabled),
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attr(QWebEngineSettings.JavascriptCanOpenWindows),
|
||||
Attr(QWebEngineSettings.WebAttribute.JavascriptCanOpenWindows),
|
||||
'content.plugins':
|
||||
Attr(QWebEngineSettings.PluginsEnabled),
|
||||
Attr(QWebEngineSettings.WebAttribute.PluginsEnabled),
|
||||
'content.hyperlink_auditing':
|
||||
Attr(QWebEngineSettings.HyperlinkAuditingEnabled),
|
||||
Attr(QWebEngineSettings.WebAttribute.HyperlinkAuditingEnabled),
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attr(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
|
||||
Attr(QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls),
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attr(QWebEngineSettings.LocalContentCanAccessFileUrls),
|
||||
Attr(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls),
|
||||
'content.webgl':
|
||||
Attr(QWebEngineSettings.WebGLEnabled),
|
||||
Attr(QWebEngineSettings.WebAttribute.WebGLEnabled),
|
||||
'content.local_storage':
|
||||
Attr(QWebEngineSettings.LocalStorageEnabled),
|
||||
Attr(QWebEngineSettings.WebAttribute.LocalStorageEnabled),
|
||||
'content.desktop_capture':
|
||||
Attr(QWebEngineSettings.ScreenCaptureEnabled,
|
||||
Attr(QWebEngineSettings.WebAttribute.ScreenCaptureEnabled,
|
||||
converter=lambda val: True if val == 'ask' else val),
|
||||
# 'ask' is handled via the permission system
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attr(QWebEngineSettings.SpatialNavigationEnabled),
|
||||
Attr(QWebEngineSettings.WebAttribute.SpatialNavigationEnabled),
|
||||
'input.links_included_in_focus_chain':
|
||||
Attr(QWebEngineSettings.LinksIncludedInFocusChain),
|
||||
Attr(QWebEngineSettings.WebAttribute.LinksIncludedInFocusChain),
|
||||
|
||||
'scrolling.smooth':
|
||||
Attr(QWebEngineSettings.ScrollAnimatorEnabled),
|
||||
Attr(QWebEngineSettings.WebAttribute.ScrollAnimatorEnabled),
|
||||
|
||||
'content.print_element_backgrounds':
|
||||
Attr(QWebEngineSettings.PrintElementBackgrounds),
|
||||
Attr(QWebEngineSettings.WebAttribute.PrintElementBackgrounds),
|
||||
|
||||
'content.autoplay':
|
||||
Attr(QWebEngineSettings.PlaybackRequiresUserGesture,
|
||||
Attr(QWebEngineSettings.WebAttribute.PlaybackRequiresUserGesture,
|
||||
converter=operator.not_),
|
||||
|
||||
'content.dns_prefetch':
|
||||
Attr(QWebEngineSettings.DnsPrefetchEnabled),
|
||||
Attr(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled),
|
||||
|
||||
'tabs.favicons.show':
|
||||
Attr(QWebEngineSettings.AutoLoadIconsForPage,
|
||||
Attr(QWebEngineSettings.WebAttribute.AutoLoadIconsForPage,
|
||||
converter=lambda val: val != 'never'),
|
||||
}
|
||||
|
||||
_FONT_SIZES = {
|
||||
'fonts.web.size.minimum':
|
||||
QWebEngineSettings.MinimumFontSize,
|
||||
QWebEngineSettings.FontSize.MinimumFontSize,
|
||||
'fonts.web.size.minimum_logical':
|
||||
QWebEngineSettings.MinimumLogicalFontSize,
|
||||
QWebEngineSettings.FontSize.MinimumLogicalFontSize,
|
||||
'fonts.web.size.default':
|
||||
QWebEngineSettings.DefaultFontSize,
|
||||
QWebEngineSettings.FontSize.DefaultFontSize,
|
||||
'fonts.web.size.default_fixed':
|
||||
QWebEngineSettings.DefaultFixedFontSize,
|
||||
QWebEngineSettings.FontSize.DefaultFixedFontSize,
|
||||
}
|
||||
|
||||
_FONT_FAMILIES = {
|
||||
'fonts.web.family.standard': QWebEngineSettings.StandardFont,
|
||||
'fonts.web.family.fixed': QWebEngineSettings.FixedFont,
|
||||
'fonts.web.family.serif': QWebEngineSettings.SerifFont,
|
||||
'fonts.web.family.sans_serif': QWebEngineSettings.SansSerifFont,
|
||||
'fonts.web.family.cursive': QWebEngineSettings.CursiveFont,
|
||||
'fonts.web.family.fantasy': QWebEngineSettings.FantasyFont,
|
||||
'fonts.web.family.standard': QWebEngineSettings.FontFamily.StandardFont,
|
||||
'fonts.web.family.fixed': QWebEngineSettings.FontFamily.FixedFont,
|
||||
'fonts.web.family.serif': QWebEngineSettings.FontFamily.SerifFont,
|
||||
'fonts.web.family.sans_serif': QWebEngineSettings.FontFamily.SansSerifFont,
|
||||
'fonts.web.family.cursive': QWebEngineSettings.FontFamily.CursiveFont,
|
||||
'fonts.web.family.fantasy': QWebEngineSettings.FontFamily.FantasyFont,
|
||||
}
|
||||
|
||||
_UNKNOWN_URL_SCHEME_POLICY = {
|
||||
'disallow':
|
||||
QWebEngineSettings.DisallowUnknownUrlSchemes,
|
||||
QWebEngineSettings.UnknownUrlSchemePolicy.DisallowUnknownUrlSchemes,
|
||||
'allow-from-user-interaction':
|
||||
QWebEngineSettings.AllowUnknownUrlSchemesFromUserInteraction,
|
||||
QWebEngineSettings.UnknownUrlSchemePolicy.AllowUnknownUrlSchemesFromUserInteraction,
|
||||
'allow-all':
|
||||
QWebEngineSettings.AllowAllUnknownUrlSchemes,
|
||||
QWebEngineSettings.UnknownUrlSchemePolicy.AllowAllUnknownUrlSchemes,
|
||||
}
|
||||
|
||||
# Mapping from WebEngineSettings::initDefaults in
|
||||
# qtwebengine/src/core/web_engine_settings.cpp
|
||||
_FONT_TO_QFONT = {
|
||||
QWebEngineSettings.StandardFont: QFont.Serif,
|
||||
QWebEngineSettings.FixedFont: QFont.Monospace,
|
||||
QWebEngineSettings.SerifFont: QFont.Serif,
|
||||
QWebEngineSettings.SansSerifFont: QFont.SansSerif,
|
||||
QWebEngineSettings.CursiveFont: QFont.Cursive,
|
||||
QWebEngineSettings.FantasyFont: QFont.Fantasy,
|
||||
QWebEngineSettings.FontFamily.StandardFont: QFont.StyleHint.Serif,
|
||||
QWebEngineSettings.FontFamily.FixedFont: QFont.StyleHint.Monospace,
|
||||
QWebEngineSettings.FontFamily.SerifFont: QFont.StyleHint.Serif,
|
||||
QWebEngineSettings.FontFamily.SansSerifFont: QFont.StyleHint.SansSerif,
|
||||
QWebEngineSettings.FontFamily.CursiveFont: QFont.StyleHint.Cursive,
|
||||
QWebEngineSettings.FontFamily.FantasyFont: QFont.StyleHint.Fantasy,
|
||||
}
|
||||
|
||||
_JS_CLIPBOARD_SETTINGS = {
|
||||
|
|
@ -256,15 +258,10 @@ class ProfileSetter:
|
|||
'content.cache.size': self.set_http_cache_size,
|
||||
'content.cookies.store': self.set_persistent_cookie_policy,
|
||||
'spellcheck.languages': self.set_dictionary_language,
|
||||
'content.headers.user_agent': self.set_http_headers,
|
||||
'content.headers.accept_language': self.set_http_headers,
|
||||
}
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
|
||||
# (note this isn't actually fixed properly before Qt 5.15)
|
||||
header_bug_fixed = qtutils.version_check('5.15', compiled=False)
|
||||
if header_bug_fixed:
|
||||
for name in ['user_agent', 'accept_language']:
|
||||
self._name_to_method[f'content.headers.{name}'] = self.set_http_headers
|
||||
|
||||
def update_setting(self, name):
|
||||
"""Update a setting based on its name."""
|
||||
try:
|
||||
|
|
@ -286,15 +283,10 @@ class ProfileSetter:
|
|||
settings = self._profile.settings()
|
||||
|
||||
settings.setAttribute(
|
||||
QWebEngineSettings.FullScreenSupportEnabled, True)
|
||||
QWebEngineSettings.WebAttribute.FullScreenSupportEnabled, True)
|
||||
settings.setAttribute(
|
||||
QWebEngineSettings.FocusOnNavigationEnabled, False)
|
||||
|
||||
try:
|
||||
settings.setAttribute(QWebEngineSettings.PdfViewerEnabled, False)
|
||||
except AttributeError:
|
||||
# Added in Qt 5.13
|
||||
pass
|
||||
QWebEngineSettings.WebAttribute.FocusOnNavigationEnabled, False)
|
||||
settings.setAttribute(QWebEngineSettings.WebAttribute.PdfViewerEnabled, False)
|
||||
|
||||
def set_http_headers(self):
|
||||
"""Set the user agent and accept-language for the given profile.
|
||||
|
|
@ -326,9 +318,9 @@ class ProfileSetter:
|
|||
if self._profile.isOffTheRecord():
|
||||
return
|
||||
if config.val.content.cookies.store:
|
||||
value = QWebEngineProfile.AllowPersistentCookies
|
||||
value = QWebEngineProfile.PersistentCookiesPolicy.AllowPersistentCookies
|
||||
else:
|
||||
value = QWebEngineProfile.NoPersistentCookies
|
||||
value = QWebEngineProfile.PersistentCookiesPolicy.NoPersistentCookies
|
||||
self._profile.setPersistentCookiesPolicy(value)
|
||||
|
||||
def set_dictionary_language(self):
|
||||
|
|
@ -397,7 +389,11 @@ def _init_default_profile():
|
|||
"""Init the default QWebEngineProfile."""
|
||||
global default_profile
|
||||
|
||||
default_profile = QWebEngineProfile.defaultProfile()
|
||||
if machinery.IS_QT6:
|
||||
default_profile = QWebEngineProfile("Default")
|
||||
else:
|
||||
default_profile = QWebEngineProfile.defaultProfile()
|
||||
assert not default_profile.isOffTheRecord()
|
||||
|
||||
assert parsed_user_agent is None # avoid earlier profile initialization
|
||||
non_ua_version = version.qtwebengine_versions(avoid_init=True)
|
||||
|
|
@ -470,6 +466,7 @@ def _init_site_specific_quirks():
|
|||
# Needed because Slack adds an error which prevents using it relatively
|
||||
# aggressively, despite things actually working fine.
|
||||
# September 2020: Qt 5.12 works, but Qt <= 5.11 shows the error.
|
||||
# FIXME:qt6 Still needed?
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/4669
|
||||
("ua-slack", 'https://*.slack.com/*', new_chrome_ua),
|
||||
]
|
||||
|
|
@ -489,20 +486,37 @@ def _init_site_specific_quirks():
|
|||
)
|
||||
|
||||
|
||||
def _init_devtools_settings():
|
||||
"""Make sure the devtools always get images/JS permissions."""
|
||||
settings: List[Tuple[str, Any]] = [
|
||||
def _init_default_settings():
|
||||
"""Set permissions required for internal functionality.
|
||||
|
||||
- Make sure the devtools always get images/JS permissions.
|
||||
- On Qt 6, make sure files in the data path can load external resources.
|
||||
"""
|
||||
devtools_settings: List[Tuple[str, Any]] = [
|
||||
('content.javascript.enabled', True),
|
||||
('content.images', True),
|
||||
('content.cookies.accept', 'all'),
|
||||
]
|
||||
|
||||
for setting, value in settings:
|
||||
for setting, value in devtools_settings:
|
||||
for pattern in ['chrome-devtools://*', 'devtools://*']:
|
||||
config.instance.set_obj(setting, value,
|
||||
pattern=urlmatch.UrlPattern(pattern),
|
||||
hide_userconfig=True)
|
||||
|
||||
if machinery.IS_QT6:
|
||||
userscripts_settings: List[Tuple[str, Any]] = [
|
||||
("content.local_content_can_access_remote_urls", True),
|
||||
("content.local_content_can_access_file_urls", False),
|
||||
]
|
||||
# https://codereview.qt-project.org/c/qt/qtwebengine/+/375672
|
||||
url = pathlib.Path(standarddir.data(), "userscripts").as_uri()
|
||||
for setting, value in userscripts_settings:
|
||||
config.instance.set_obj(setting,
|
||||
value,
|
||||
pattern=urlmatch.UrlPattern(f"{url}/*"),
|
||||
hide_userconfig=True)
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize the global QWebSettings."""
|
||||
|
|
@ -543,7 +557,7 @@ def init():
|
|||
|
||||
log.init.debug("Misc initialization...")
|
||||
_init_site_specific_quirks()
|
||||
_init_devtools_settings()
|
||||
_init_default_settings()
|
||||
|
||||
|
||||
def shutdown():
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ import re
|
|||
import html as html_utils
|
||||
from typing import cast, Union, Optional
|
||||
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QTimer, QUrl,
|
||||
from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QTimer, QUrl,
|
||||
QObject)
|
||||
from PyQt5.QtNetwork import QAuthenticator
|
||||
from PyQt5.QtWebEngineWidgets import (QWebEnginePage, QWebEngineView, QWebEngineScript,
|
||||
QWebEngineHistory)
|
||||
from qutebrowser.qt.network import QAuthenticator
|
||||
from qutebrowser.qt.webenginewidgets import QWebEngineView
|
||||
from qutebrowser.qt.webenginecore import QWebEnginePage, QWebEngineScript, QWebEngineHistory
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.browser import browsertab, eventfilter, shared, webelem, greasemonkey
|
||||
|
|
@ -40,16 +40,16 @@ 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
|
||||
|
||||
|
||||
# Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs.
|
||||
_JS_WORLD_MAP = {
|
||||
usertypes.JsWorld.main: QWebEngineScript.MainWorld,
|
||||
usertypes.JsWorld.application: QWebEngineScript.ApplicationWorld,
|
||||
usertypes.JsWorld.user: QWebEngineScript.UserWorld,
|
||||
usertypes.JsWorld.jseval: QWebEngineScript.UserWorld + 1,
|
||||
usertypes.JsWorld.main: QWebEngineScript.ScriptWorldId.MainWorld,
|
||||
usertypes.JsWorld.application: QWebEngineScript.ScriptWorldId.ApplicationWorld,
|
||||
usertypes.JsWorld.user: QWebEngineScript.ScriptWorldId.UserWorld,
|
||||
usertypes.JsWorld.jseval: QWebEngineScript.ScriptWorldId.UserWorld + 1,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -58,23 +58,21 @@ class WebEngineAction(browsertab.AbstractAction):
|
|||
"""QtWebEngine implementations related to web actions."""
|
||||
|
||||
_widget: webview.WebEngineView
|
||||
|
||||
action_class = QWebEnginePage
|
||||
action_base = QWebEnginePage.WebAction
|
||||
|
||||
def exit_fullscreen(self):
|
||||
self._widget.triggerPageAction(QWebEnginePage.ExitFullScreen)
|
||||
self._widget.triggerPageAction(QWebEnginePage.WebAction.ExitFullScreen)
|
||||
|
||||
def save_page(self):
|
||||
"""Save the current page."""
|
||||
self._widget.triggerPageAction(QWebEnginePage.SavePage)
|
||||
self._widget.triggerPageAction(QWebEnginePage.WebAction.SavePage)
|
||||
|
||||
def show_source(self, pygments=False):
|
||||
if pygments:
|
||||
self._show_source_pygments()
|
||||
return
|
||||
|
||||
self._widget.triggerPageAction(QWebEnginePage.ViewSource)
|
||||
self._widget.triggerPageAction(QWebEnginePage.WebAction.ViewSource)
|
||||
|
||||
|
||||
class WebEnginePrinting(browsertab.AbstractPrinting):
|
||||
|
|
@ -83,6 +81,14 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
|
|||
|
||||
_widget: webview.WebEngineView
|
||||
|
||||
def connect_signals(self):
|
||||
"""Called from WebEngineTab.connect_signals."""
|
||||
page = self._widget.page()
|
||||
page.pdfPrintingFinished.connect(self.pdf_printing_finished)
|
||||
if machinery.IS_QT6:
|
||||
self._widget.printFinished.connect(self.printing_finished)
|
||||
# Qt 5 uses callbacks instead
|
||||
|
||||
def check_pdf_support(self):
|
||||
pass
|
||||
|
||||
|
|
@ -93,8 +99,11 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
|
|||
def to_pdf(self, filename):
|
||||
self._widget.page().printToPdf(filename)
|
||||
|
||||
def to_printer(self, printer, callback=lambda ok: None):
|
||||
self._widget.page().print(printer, callback)
|
||||
def to_printer(self, printer):
|
||||
if machinery.IS_QT5:
|
||||
self._widget.page().print(printer, self.printing_finished.emit)
|
||||
else: # Qt 6
|
||||
self._widget.print(printer)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
|
@ -159,22 +168,6 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||
|
||||
def connect_signals(self):
|
||||
"""Connect the signals necessary for this class to function."""
|
||||
# The API necessary to stop wrapping was added in this version
|
||||
if not qtutils.version_check("5.14"):
|
||||
return
|
||||
|
||||
try:
|
||||
# pylint: disable=unused-import
|
||||
from PyQt5.QtWebEngineCore import QWebEngineFindTextResult
|
||||
except ImportError:
|
||||
# WORKAROUND for some odd PyQt/packaging bug where the
|
||||
# findTextResult signal is available, but QWebEngineFindTextResult
|
||||
# is not. Seems to happen on e.g. Gentoo.
|
||||
log.webview.warning("Could not import QWebEngineFindTextResult "
|
||||
"despite running on Qt 5.14. You might need "
|
||||
"to rebuild PyQtWebEngine.")
|
||||
return
|
||||
|
||||
self._widget.page().findTextFinished.connect(self._on_find_finished)
|
||||
|
||||
def _find(self, text, flags, callback, caller):
|
||||
|
|
@ -182,7 +175,7 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||
self.search_displayed = True
|
||||
self._pending_searches += 1
|
||||
|
||||
def wrapped_callback(found):
|
||||
def wrapped_callback(cb_arg):
|
||||
"""Wrap the callback to do debug logging."""
|
||||
self._pending_searches -= 1
|
||||
if self._pending_searches > 0:
|
||||
|
|
@ -200,6 +193,11 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||
"widget")
|
||||
return
|
||||
|
||||
# bool in Qt 5, QWebEngineFindTextResult in Qt 6
|
||||
# Once we drop Qt 5, we might also want to call callbacks with the
|
||||
# QWebEngineFindTextResult instead of the bool.
|
||||
found = cb_arg if isinstance(cb_arg, bool) else cb_arg.numberOfMatches() > 0
|
||||
|
||||
found_text = 'found' if found else "didn't find"
|
||||
if flags:
|
||||
flag_text = f'with flags {flags}'
|
||||
|
|
@ -400,6 +398,7 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||
# https://bugreports.qt.io/browse/QTBUG-53134
|
||||
# Even on Qt 5.10 selectedText() seems to work poorly, see
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3523
|
||||
# FIXME:qt6 Reevaluate?
|
||||
self._tab.run_js_async(javascript.assemble('caret', 'getSelection'),
|
||||
callback)
|
||||
|
||||
|
|
@ -506,7 +505,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
|
|||
page = widget.page()
|
||||
page.scrollPositionChanged.connect(self._update_pos)
|
||||
|
||||
def _repeated_key_press(self, key, count=1, modifier=Qt.NoModifier):
|
||||
def _repeated_key_press(self, key, count=1, modifier=Qt.KeyboardModifier.NoModifier):
|
||||
"""Send count fake key presses to this scroller's WebEngineTab."""
|
||||
for _ in range(min(count, 1000)):
|
||||
self._tab.fake_key_press(key, modifier)
|
||||
|
|
@ -585,28 +584,28 @@ class WebEngineScroller(browsertab.AbstractScroller):
|
|||
self._tab.run_js_async(js_code)
|
||||
|
||||
def up(self, count=1):
|
||||
self._repeated_key_press(Qt.Key_Up, count)
|
||||
self._repeated_key_press(Qt.Key.Key_Up, count)
|
||||
|
||||
def down(self, count=1):
|
||||
self._repeated_key_press(Qt.Key_Down, count)
|
||||
self._repeated_key_press(Qt.Key.Key_Down, count)
|
||||
|
||||
def left(self, count=1):
|
||||
self._repeated_key_press(Qt.Key_Left, count)
|
||||
self._repeated_key_press(Qt.Key.Key_Left, count)
|
||||
|
||||
def right(self, count=1):
|
||||
self._repeated_key_press(Qt.Key_Right, count)
|
||||
self._repeated_key_press(Qt.Key.Key_Right, count)
|
||||
|
||||
def top(self):
|
||||
self._tab.fake_key_press(Qt.Key_Home)
|
||||
self._tab.fake_key_press(Qt.Key.Key_Home)
|
||||
|
||||
def bottom(self):
|
||||
self._tab.fake_key_press(Qt.Key_End)
|
||||
self._tab.fake_key_press(Qt.Key.Key_End)
|
||||
|
||||
def page_up(self, count=1):
|
||||
self._repeated_key_press(Qt.Key_PageUp, count)
|
||||
self._repeated_key_press(Qt.Key.Key_PageUp, count)
|
||||
|
||||
def page_down(self, count=1):
|
||||
self._repeated_key_press(Qt.Key_PageDown, count)
|
||||
self._repeated_key_press(Qt.Key.Key_PageDown, count)
|
||||
|
||||
def at_top(self):
|
||||
return self.pos_px().y() == 0
|
||||
|
|
@ -650,11 +649,13 @@ class WebEngineHistoryPrivate(browsertab.AbstractHistoryPrivate):
|
|||
self._tab.load_url(url)
|
||||
|
||||
def load_items(self, items):
|
||||
webengine_version = version.qtwebengine_versions().webengine
|
||||
if webengine_version >= utils.VersionNumber(5, 15):
|
||||
self._load_items_workaround(items)
|
||||
return
|
||||
self._load_items_workaround(items)
|
||||
|
||||
def _load_items_proper(self, items):
|
||||
"""Load session items properly.
|
||||
|
||||
Currently unused, but should be revived.
|
||||
"""
|
||||
if items:
|
||||
self._tab.before_load_started.emit(items[-1].url)
|
||||
|
||||
|
|
@ -813,7 +814,7 @@ class WebEngineAudio(browsertab.AbstractAudio):
|
|||
self._overridden = False
|
||||
|
||||
# Implements the intended two-second delay specified at
|
||||
# https://doc.qt.io/qt-5/qwebenginepage.html#recentlyAudibleChanged
|
||||
# https://doc.qt.io/archives/qt-5.14/qwebenginepage.html#recentlyAudibleChanged
|
||||
delay_ms = 2000
|
||||
self._silence_timer = QTimer(self)
|
||||
self._silence_timer.setSingleShot(True)
|
||||
|
|
@ -845,15 +846,10 @@ class WebEngineAudio(browsertab.AbstractAudio):
|
|||
timer.start()
|
||||
|
||||
def set_muted(self, muted: bool, override: bool = False) -> None:
|
||||
was_muted = self.is_muted()
|
||||
self._overridden = override
|
||||
assert self._widget is not None
|
||||
page = self._widget.page()
|
||||
page.setAudioMuted(muted)
|
||||
if was_muted != muted and qtutils.version_check('5.15'):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-85118
|
||||
# so that the tab title at least updates the muted indicator
|
||||
self.muted_changed.emit(muted)
|
||||
|
||||
def is_muted(self):
|
||||
page = self._widget.page()
|
||||
|
|
@ -881,29 +877,26 @@ class _WebEnginePermissions(QObject):
|
|||
|
||||
_widget: webview.WebEngineView
|
||||
|
||||
# Using 0 as WORKAROUND for:
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2019-July/041903.html
|
||||
|
||||
_options = {
|
||||
0: 'content.notifications.enabled',
|
||||
QWebEnginePage.Geolocation: 'content.geolocation',
|
||||
QWebEnginePage.MediaAudioCapture: 'content.media.audio_capture',
|
||||
QWebEnginePage.MediaVideoCapture: 'content.media.video_capture',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'content.media.audio_video_capture',
|
||||
QWebEnginePage.MouseLock: 'content.mouse_lock',
|
||||
QWebEnginePage.DesktopVideoCapture: 'content.desktop_capture',
|
||||
QWebEnginePage.DesktopAudioVideoCapture: 'content.desktop_capture',
|
||||
QWebEnginePage.Feature.Notifications: 'content.notifications.enabled',
|
||||
QWebEnginePage.Feature.Geolocation: 'content.geolocation',
|
||||
QWebEnginePage.Feature.MediaAudioCapture: 'content.media.audio_capture',
|
||||
QWebEnginePage.Feature.MediaVideoCapture: 'content.media.video_capture',
|
||||
QWebEnginePage.Feature.MediaAudioVideoCapture: 'content.media.audio_video_capture',
|
||||
QWebEnginePage.Feature.MouseLock: 'content.mouse_lock',
|
||||
QWebEnginePage.Feature.DesktopVideoCapture: 'content.desktop_capture',
|
||||
QWebEnginePage.Feature.DesktopAudioVideoCapture: 'content.desktop_capture',
|
||||
}
|
||||
|
||||
_messages = {
|
||||
0: 'show notifications',
|
||||
QWebEnginePage.Geolocation: 'access your location',
|
||||
QWebEnginePage.MediaAudioCapture: 'record audio',
|
||||
QWebEnginePage.MediaVideoCapture: 'record video',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
|
||||
QWebEnginePage.MouseLock: 'hide your mouse pointer',
|
||||
QWebEnginePage.DesktopVideoCapture: 'capture your desktop',
|
||||
QWebEnginePage.DesktopAudioVideoCapture: 'capture your desktop and audio',
|
||||
QWebEnginePage.Feature.Notifications: 'show notifications',
|
||||
QWebEnginePage.Feature.Geolocation: 'access your location',
|
||||
QWebEnginePage.Feature.MediaAudioCapture: 'record audio',
|
||||
QWebEnginePage.Feature.MediaVideoCapture: 'record video',
|
||||
QWebEnginePage.Feature.MediaAudioVideoCapture: 'record audio/video',
|
||||
QWebEnginePage.Feature.MouseLock: 'hide your mouse pointer',
|
||||
QWebEnginePage.Feature.DesktopVideoCapture: 'capture your desktop',
|
||||
QWebEnginePage.Feature.DesktopAudioVideoCapture: 'capture your desktop and audio',
|
||||
}
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
|
|
@ -944,22 +937,15 @@ class _WebEnginePermissions(QObject):
|
|||
page = self._widget.page()
|
||||
grant_permission = functools.partial(
|
||||
page.setFeaturePermission, url, feature,
|
||||
QWebEnginePage.PermissionGrantedByUser)
|
||||
QWebEnginePage.PermissionPolicy.PermissionGrantedByUser)
|
||||
deny_permission = functools.partial(
|
||||
page.setFeaturePermission, url, feature,
|
||||
QWebEnginePage.PermissionDeniedByUser)
|
||||
QWebEnginePage.PermissionPolicy.PermissionDeniedByUser)
|
||||
|
||||
permission_str = debug.qenum_key(QWebEnginePage, feature)
|
||||
|
||||
if not url.isValid():
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-85116
|
||||
is_qtbug = (qtutils.version_check('5.15.0',
|
||||
compiled=False,
|
||||
exact=True) and
|
||||
self._tab.is_private and
|
||||
feature == QWebEnginePage.Notifications)
|
||||
logger = log.webview.debug if is_qtbug else log.webview.warning
|
||||
logger("Ignoring feature permission {} for invalid URL {}".format(
|
||||
log.webview.warning("Ignoring feature permission {} for invalid URL {}".format(
|
||||
permission_str, url))
|
||||
deny_permission()
|
||||
return
|
||||
|
|
@ -970,20 +956,8 @@ class _WebEnginePermissions(QObject):
|
|||
deny_permission()
|
||||
return
|
||||
|
||||
if (
|
||||
feature in [QWebEnginePage.DesktopVideoCapture,
|
||||
QWebEnginePage.DesktopAudioVideoCapture] and
|
||||
qtutils.version_check('5.13', compiled=False) and
|
||||
not qtutils.version_check('5.13.2', compiled=False)
|
||||
):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-78016
|
||||
log.webview.warning("Ignoring desktop sharing request due to "
|
||||
"crashes in Qt < 5.13.2")
|
||||
deny_permission()
|
||||
return
|
||||
|
||||
question = shared.feature_permission(
|
||||
url=url.adjusted(QUrl.RemovePath),
|
||||
url=url.adjusted(QUrl.UrlFormattingOption.RemovePath),
|
||||
option=self._options[feature], msg=self._messages[feature],
|
||||
yes_action=grant_permission, no_action=deny_permission,
|
||||
abort_on=[self._tab.abort_questions])
|
||||
|
|
@ -1010,7 +984,7 @@ class _WebEnginePermissions(QObject):
|
|||
def _on_quota_requested(self, request):
|
||||
size = utils.format_size(request.requestedSize())
|
||||
shared.feature_permission(
|
||||
url=request.origin().adjusted(QUrl.RemovePath),
|
||||
url=request.origin().adjusted(QUrl.UrlFormattingOption.RemovePath),
|
||||
option='content.persistent_storage',
|
||||
msg='use {} of persistent storage'.format(size),
|
||||
yes_action=request.accept, no_action=request.reject,
|
||||
|
|
@ -1019,7 +993,7 @@ class _WebEnginePermissions(QObject):
|
|||
|
||||
def _on_register_protocol_handler_requested(self, request):
|
||||
shared.feature_permission(
|
||||
url=request.origin().adjusted(QUrl.RemovePath),
|
||||
url=request.origin().adjusted(QUrl.UrlFormattingOption.RemovePath),
|
||||
option='content.register_protocol_handler',
|
||||
msg='open all {} links'.format(request.scheme()),
|
||||
yes_action=request.accept, no_action=request.reject,
|
||||
|
|
@ -1032,8 +1006,8 @@ class _Quirk:
|
|||
|
||||
filename: str
|
||||
injection_point: QWebEngineScript.InjectionPoint = (
|
||||
QWebEngineScript.DocumentCreation)
|
||||
world: QWebEngineScript.ScriptWorldId = QWebEngineScript.MainWorld
|
||||
QWebEngineScript.InjectionPoint.DocumentCreation)
|
||||
world: QWebEngineScript.ScriptWorldId = QWebEngineScript.ScriptWorldId.MainWorld
|
||||
predicate: bool = True
|
||||
name: Optional[str] = None
|
||||
|
||||
|
|
@ -1074,8 +1048,8 @@ class _WebEngineScripts(QObject):
|
|||
self._tab.run_js_async(code)
|
||||
|
||||
def _inject_js(self, name, js_code, *,
|
||||
world=QWebEngineScript.ApplicationWorld,
|
||||
injection_point=QWebEngineScript.DocumentCreation,
|
||||
world=QWebEngineScript.ScriptWorldId.ApplicationWorld,
|
||||
injection_point=QWebEngineScript.InjectionPoint.DocumentCreation,
|
||||
subframes=False):
|
||||
"""Inject the given script to run early on a page load."""
|
||||
script = QWebEngineScript()
|
||||
|
|
@ -1089,9 +1063,13 @@ class _WebEngineScripts(QObject):
|
|||
def _remove_js(self, name):
|
||||
"""Remove an early QWebEngineScript."""
|
||||
scripts = self._widget.page().scripts()
|
||||
script = scripts.findScript(f'_qute_{name}')
|
||||
if not script.isNull():
|
||||
scripts.remove(script)
|
||||
if machinery.IS_QT6:
|
||||
for script in scripts.find(f'_qute_{name}'):
|
||||
scripts.remove(script)
|
||||
else: # Qt 5
|
||||
script = scripts.findScript(f'_qute_{name}')
|
||||
if not script.isNull():
|
||||
scripts.remove(script)
|
||||
|
||||
def init(self):
|
||||
"""Initialize global qutebrowser JavaScript."""
|
||||
|
|
@ -1184,13 +1162,13 @@ class _WebEngineScripts(QObject):
|
|||
|
||||
# Corresponds to "@run-at document-end" which is the default according to
|
||||
# https://wiki.greasespot.net/Metadata_Block#.40run-at - however,
|
||||
# QtWebEngine uses QWebEngineScript.Deferred (@run-at document-idle) as
|
||||
# QtWebEngine uses QWebEngineScript.InjectionPoint.Deferred (@run-at document-idle) as
|
||||
# default.
|
||||
#
|
||||
# NOTE that this needs to be done before setSourceCode, so that
|
||||
# QtWebEngine's parsing of GreaseMonkey tags will override it if there is a
|
||||
# @run-at comment.
|
||||
new_script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
||||
new_script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
|
||||
|
||||
new_script.setSourceCode(script.code())
|
||||
new_script.setName(script.full_name())
|
||||
|
|
@ -1199,22 +1177,20 @@ class _WebEngineScripts(QObject):
|
|||
if script.needs_document_end_workaround():
|
||||
log.greasemonkey.debug(
|
||||
f"Forcing @run-at document-end for {script.name}")
|
||||
new_script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
||||
new_script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
|
||||
|
||||
log.greasemonkey.debug(f'adding script: {new_script.name()}')
|
||||
page_scripts.insert(new_script)
|
||||
|
||||
def _inject_site_specific_quirks(self):
|
||||
"""Add site-specific quirk scripts."""
|
||||
if not config.val.content.site_specific_quirks.enabled:
|
||||
return
|
||||
|
||||
def _get_quirks(self):
|
||||
"""Get a list of all available JS quirks."""
|
||||
versions = version.qtwebengine_versions()
|
||||
quirks = [
|
||||
return [
|
||||
# FIXME:qt6 Double check which of those are still required
|
||||
_Quirk(
|
||||
'whatsapp_web',
|
||||
injection_point=QWebEngineScript.DocumentReady,
|
||||
world=QWebEngineScript.ApplicationWorld,
|
||||
injection_point=QWebEngineScript.InjectionPoint.DocumentReady,
|
||||
world=QWebEngineScript.ScriptWorldId.ApplicationWorld,
|
||||
),
|
||||
_Quirk('discord'),
|
||||
_Quirk(
|
||||
|
|
@ -1222,25 +1198,23 @@ class _WebEngineScripts(QObject):
|
|||
# will be an UA quirk once we set the JS UA as well
|
||||
name='ua-googledocs',
|
||||
),
|
||||
|
||||
_Quirk(
|
||||
'string_replaceall',
|
||||
predicate=versions.webengine < utils.VersionNumber(5, 15, 3),
|
||||
),
|
||||
_Quirk(
|
||||
'globalthis',
|
||||
predicate=versions.webengine < utils.VersionNumber(5, 13),
|
||||
),
|
||||
_Quirk(
|
||||
'object_fromentries',
|
||||
predicate=versions.webengine < utils.VersionNumber(5, 13),
|
||||
),
|
||||
_Quirk(
|
||||
'array_at',
|
||||
predicate=versions.webengine < utils.VersionNumber(6, 2),
|
||||
predicate=versions.webengine < utils.VersionNumber(6, 3),
|
||||
),
|
||||
]
|
||||
|
||||
for quirk in quirks:
|
||||
def _inject_site_specific_quirks(self):
|
||||
"""Add site-specific quirk scripts."""
|
||||
if not config.val.content.site_specific_quirks.enabled:
|
||||
return
|
||||
|
||||
for quirk in self._get_quirks():
|
||||
if not quirk.predicate:
|
||||
continue
|
||||
src = resources.read_file(f'javascript/quirks/{quirk.filename}.user.js')
|
||||
|
|
@ -1297,6 +1271,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
_widget: QWebEngineView
|
||||
search: WebEngineSearch
|
||||
audio: WebEngineAudio
|
||||
printing: WebEnginePrinting
|
||||
|
||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||
super().__init__(win_id=win_id,
|
||||
|
|
@ -1311,7 +1286,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
tab=self, parent=self)
|
||||
self.zoom = WebEngineZoom(tab=self, parent=self)
|
||||
self.search = WebEngineSearch(tab=self, parent=self)
|
||||
self.printing = WebEnginePrinting(tab=self)
|
||||
self.printing = WebEnginePrinting(tab=self, parent=self)
|
||||
self.elements = WebEngineElements(tab=self)
|
||||
self.action = WebEngineAction(tab=self)
|
||||
self.audio = WebEngineAudio(tab=self, parent=self)
|
||||
|
|
@ -1327,6 +1302,9 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
self._child_event_filter = None
|
||||
self._saved_zoom = None
|
||||
self._scripts.init()
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||
self._needs_qtbug65223_workaround = (
|
||||
version.qtwebengine_versions().webengine < utils.VersionNumber(5, 15, 5))
|
||||
|
||||
def _set_widget(self, widget):
|
||||
# pylint: disable=protected-access
|
||||
|
|
@ -1384,7 +1362,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
def run_js_async(self, code, callback=None, *, world=None):
|
||||
world_id_type = Union[QWebEngineScript.ScriptWorldId, int]
|
||||
if world is None:
|
||||
world_id: world_id_type = QWebEngineScript.ApplicationWorld
|
||||
world_id: world_id_type = QWebEngineScript.ScriptWorldId.ApplicationWorld
|
||||
elif isinstance(world, int):
|
||||
world_id = world
|
||||
if not 0 <= world_id <= qtutils.MAX_WORLD_ID:
|
||||
|
|
@ -1401,9 +1379,9 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
|
||||
def reload(self, *, force=False):
|
||||
if force:
|
||||
action = QWebEnginePage.ReloadAndBypassCache
|
||||
action = QWebEnginePage.WebAction.ReloadAndBypassCache
|
||||
else:
|
||||
action = QWebEnginePage.Reload
|
||||
action = QWebEnginePage.WebAction.Reload
|
||||
self._widget.triggerPageAction(action)
|
||||
|
||||
def stop(self):
|
||||
|
|
@ -1412,13 +1390,9 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
def title(self):
|
||||
return self._widget.title()
|
||||
|
||||
def renderer_process_pid(self) -> Optional[int]:
|
||||
def renderer_process_pid(self) -> int:
|
||||
page = self._widget.page()
|
||||
try:
|
||||
return page.renderProcessPid()
|
||||
except AttributeError:
|
||||
# Added in Qt 5.15
|
||||
return None
|
||||
return page.renderProcessPid()
|
||||
|
||||
def icon(self):
|
||||
return self._widget.icon()
|
||||
|
|
@ -1460,7 +1434,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
title_url = QUrl(url)
|
||||
title_url.setScheme('')
|
||||
title_url_str = title_url.toDisplayString(
|
||||
QUrl.RemoveScheme) # type: ignore[arg-type]
|
||||
QUrl.UrlFormattingOption.RemoveScheme) # type: ignore[arg-type]
|
||||
if title == title_url_str.strip('/'):
|
||||
title = ""
|
||||
|
||||
|
|
@ -1477,7 +1451,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
"""Called when a proxy needs authentication."""
|
||||
msg = "<b>{}</b> requires a username and password.".format(
|
||||
html_utils.escape(proxy_host))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded)
|
||||
answer = message.ask(
|
||||
title="Proxy authentication required", text=msg,
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
|
|
@ -1526,19 +1500,19 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
@pyqtSlot(QWebEnginePage.RenderProcessTerminationStatus, int)
|
||||
def _on_render_process_terminated(self, status, exitcode):
|
||||
"""Show an error when the renderer process terminated."""
|
||||
if (status == QWebEnginePage.AbnormalTerminationStatus and
|
||||
if (status == QWebEnginePage.RenderProcessTerminationStatus.AbnormalTerminationStatus and
|
||||
exitcode == 256):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58697
|
||||
status = QWebEnginePage.CrashedTerminationStatus
|
||||
status = QWebEnginePage.RenderProcessTerminationStatus.CrashedTerminationStatus
|
||||
|
||||
status_map = {
|
||||
QWebEnginePage.NormalTerminationStatus:
|
||||
QWebEnginePage.RenderProcessTerminationStatus.NormalTerminationStatus:
|
||||
browsertab.TerminationStatus.normal,
|
||||
QWebEnginePage.AbnormalTerminationStatus:
|
||||
QWebEnginePage.RenderProcessTerminationStatus.AbnormalTerminationStatus:
|
||||
browsertab.TerminationStatus.abnormal,
|
||||
QWebEnginePage.CrashedTerminationStatus:
|
||||
QWebEnginePage.RenderProcessTerminationStatus.CrashedTerminationStatus:
|
||||
browsertab.TerminationStatus.crashed,
|
||||
QWebEnginePage.KilledTerminationStatus:
|
||||
QWebEnginePage.RenderProcessTerminationStatus.KilledTerminationStatus:
|
||||
browsertab.TerminationStatus.killed,
|
||||
-1:
|
||||
browsertab.TerminationStatus.unknown,
|
||||
|
|
@ -1568,24 +1542,25 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
|
||||
@pyqtSlot(int)
|
||||
def _on_load_progress(self, perc: int) -> None:
|
||||
"""QtWebEngine-specific loadProgress workarounds.
|
||||
|
||||
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||
"""
|
||||
"""QtWebEngine-specific loadProgress workarounds."""
|
||||
super()._on_load_progress(perc)
|
||||
if (perc == 100 and
|
||||
self.load_status() != usertypes.LoadStatus.error):
|
||||
if (
|
||||
self._needs_qtbug65223_workaround and
|
||||
perc == 100 and
|
||||
self.load_status() != usertypes.LoadStatus.error
|
||||
):
|
||||
self._update_load_status(ok=True)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_load_finished(self, ok: bool) -> None:
|
||||
"""QtWebEngine-specific loadFinished workarounds."""
|
||||
"""QtWebEngine-specific loadFinished code."""
|
||||
super()._on_load_finished(ok)
|
||||
|
||||
if not ok:
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||
if not self._needs_qtbug65223_workaround or not ok:
|
||||
# With the workaround, this should only run with ok=False
|
||||
self._update_load_status(ok)
|
||||
|
||||
if not ok:
|
||||
self.dump_async(functools.partial(
|
||||
self._error_page_workaround,
|
||||
self.settings.test_attribute('content.javascript.enabled')))
|
||||
|
|
@ -1605,7 +1580,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
log.network.debug("First party URL: {}".format(first_party_url))
|
||||
|
||||
if error.is_overridable():
|
||||
error.ignore = shared.ignore_certificate_error(
|
||||
shared.handle_certificate_error(
|
||||
request_url=url,
|
||||
first_party_url=first_party_url,
|
||||
error=error,
|
||||
|
|
@ -1618,28 +1593,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
log.network.debug("ignore {}, URL {}, requested {}".format(
|
||||
error.ignore, url, self.url(requested=True)))
|
||||
|
||||
# WORKAROUND for https://codereview.qt-project.org/c/qt/qtwebengine/+/270556
|
||||
show_non_overr_cert_error = (
|
||||
not error.is_overridable() and (
|
||||
# Affected Qt versions:
|
||||
# 5.13 before 5.13.2
|
||||
# 5.12 before 5.12.6
|
||||
# < 5.12 (which is unsupported)
|
||||
(qtutils.version_check('5.13') and
|
||||
not qtutils.version_check('5.13.2')) or
|
||||
(qtutils.version_check('5.12') and
|
||||
not qtutils.version_check('5.12.6'))
|
||||
)
|
||||
)
|
||||
|
||||
# We can't really know when to show an error page, as the error might
|
||||
# have happened when loading some resource.
|
||||
is_resource = (
|
||||
first_party_url.isValid() and
|
||||
url.matches(first_party_url, QUrl.RemoveScheme))
|
||||
if show_non_overr_cert_error and is_resource:
|
||||
self._show_error_page(url, str(error))
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_print_requested(self):
|
||||
"""Slot for window.print() in JS."""
|
||||
|
|
@ -1648,27 +1601,30 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
except browsertab.WebTabError as e:
|
||||
message.error(str(e))
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_url_changed(self, url: QUrl) -> None:
|
||||
"""Update settings for the current URL.
|
||||
|
||||
Normally this is done below in _on_navigation_request, but we also need
|
||||
to do it here as WORKAROUND for
|
||||
https://bugreports.qt.io/browse/QTBUG-77137
|
||||
|
||||
Since update_for_url() is idempotent, it doesn't matter much if we end
|
||||
up doing it twice.
|
||||
"""
|
||||
super()._on_url_changed(url)
|
||||
if (url.isValid() and
|
||||
qtutils.version_check('5.13') and
|
||||
not qtutils.version_check('5.14')):
|
||||
self.settings.update_for_url(url)
|
||||
|
||||
@pyqtSlot(usertypes.NavigationRequest)
|
||||
def _on_navigation_request(self, navigation):
|
||||
super()._on_navigation_request(navigation)
|
||||
|
||||
local_schemes = {"qute", "file"}
|
||||
qtwe_ver = version.qtwebengine_versions().webengine
|
||||
if (
|
||||
navigation.accepted and
|
||||
self.url().scheme().lower() in local_schemes and
|
||||
navigation.url.scheme().lower() not in local_schemes and
|
||||
(navigation.navigation_type ==
|
||||
usertypes.NavigationRequest.Type.link_clicked) and
|
||||
navigation.is_main_frame and
|
||||
(utils.VersionNumber(6, 2) <= qtwe_ver < utils.VersionNumber(6, 2, 5) or
|
||||
utils.VersionNumber(6, 3) <= qtwe_ver < utils.VersionNumber(6, 3, 1))
|
||||
):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-103778
|
||||
log.webview.debug(
|
||||
"Working around blocked request from local page "
|
||||
f"{self.url().toDisplayString()}"
|
||||
)
|
||||
navigation.accepted = False
|
||||
self.load_url(navigation.url)
|
||||
|
||||
if not navigation.accepted or not navigation.is_main_frame:
|
||||
return
|
||||
|
||||
|
|
@ -1721,16 +1677,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
page.contentsSizeChanged.connect(self.contents_size_changed)
|
||||
page.navigation_request.connect(self._on_navigation_request)
|
||||
page.printRequested.connect(self._on_print_requested)
|
||||
|
||||
try:
|
||||
# pylint: disable=unused-import
|
||||
from PyQt5.QtWebEngineWidgets import (
|
||||
QWebEngineClientCertificateSelection)
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
page.selectClientCertificate.connect(
|
||||
self._on_select_client_certificate)
|
||||
page.selectClientCertificate.connect(self._on_select_client_certificate)
|
||||
|
||||
view.titleChanged.connect(self.title_changed)
|
||||
view.urlChanged.connect(self._on_url_changed)
|
||||
|
|
@ -1741,12 +1688,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
page.loadFinished.connect(self._on_history_trigger)
|
||||
page.loadFinished.connect(self._restore_zoom)
|
||||
page.loadFinished.connect(self._on_load_finished)
|
||||
|
||||
try:
|
||||
page.renderProcessPidChanged.connect(self._on_renderer_process_pid_changed)
|
||||
except AttributeError:
|
||||
# Added in Qt 5.15.0
|
||||
pass
|
||||
page.renderProcessPidChanged.connect(self._on_renderer_process_pid_changed)
|
||||
|
||||
self.shutting_down.connect(self.abort_questions)
|
||||
self.load_started.connect(self.abort_questions)
|
||||
|
|
@ -1754,5 +1696,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
# pylint: disable=protected-access
|
||||
self.audio._connect_signals()
|
||||
self.search.connect_signals()
|
||||
self.printing.connect_signals()
|
||||
self._permissions.connect_signals()
|
||||
self._scripts.connect_signals()
|
||||
|
|
|
|||
|
|
@ -21,9 +21,11 @@
|
|||
|
||||
from typing import List, Iterable
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
from qutebrowser.qt import machinery
|
||||
from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QUrl
|
||||
from qutebrowser.qt.gui import QPalette
|
||||
from qutebrowser.qt.webenginewidgets import QWebEngineView
|
||||
from qutebrowser.qt.webenginecore import QWebEnginePage, QWebEngineCertificateError
|
||||
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import webenginesettings, certificateerror
|
||||
|
|
@ -32,8 +34,8 @@ from qutebrowser.utils import log, debug, usertypes
|
|||
|
||||
|
||||
_QB_FILESELECTION_MODES = {
|
||||
QWebEnginePage.FileSelectOpen: shared.FileSelectionMode.single_file,
|
||||
QWebEnginePage.FileSelectOpenMultiple: shared.FileSelectionMode.multiple_files,
|
||||
QWebEnginePage.FileSelectionMode.FileSelectOpen: shared.FileSelectionMode.single_file,
|
||||
QWebEnginePage.FileSelectionMode.FileSelectOpenMultiple: shared.FileSelectionMode.multiple_files,
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-91489
|
||||
#
|
||||
# QtWebEngine doesn't expose this value from its internal
|
||||
|
|
@ -54,7 +56,7 @@ class WebEngineView(QWebEngineView):
|
|||
self._win_id = win_id
|
||||
self._tabdata = tabdata
|
||||
|
||||
theme_color = self.style().standardPalette().color(QPalette.Base)
|
||||
theme_color = self.style().standardPalette().color(QPalette.ColorRole.Base)
|
||||
if private:
|
||||
assert webenginesettings.private_profile is not None
|
||||
profile = webenginesettings.private_profile
|
||||
|
|
@ -106,21 +108,21 @@ class WebEngineView(QWebEngineView):
|
|||
log.webview.debug("createWindow with type {}, background {}".format(
|
||||
debug_type, background))
|
||||
|
||||
if wintype == QWebEnginePage.WebBrowserWindow:
|
||||
if wintype == QWebEnginePage.WebWindowType.WebBrowserWindow:
|
||||
# Shift-Alt-Click
|
||||
target = usertypes.ClickTarget.window
|
||||
elif wintype == QWebEnginePage.WebDialog:
|
||||
elif wintype == QWebEnginePage.WebWindowType.WebDialog:
|
||||
log.webview.warning("{} requested, but we don't support "
|
||||
"that!".format(debug_type))
|
||||
target = usertypes.ClickTarget.tab
|
||||
elif wintype == QWebEnginePage.WebBrowserTab:
|
||||
elif wintype == QWebEnginePage.WebWindowType.WebBrowserTab:
|
||||
# Middle-click / Ctrl-Click with Shift
|
||||
# FIXME:qtwebengine this also affects target=_blank links...
|
||||
if background:
|
||||
target = usertypes.ClickTarget.tab
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
elif wintype == QWebEnginePage.WebBrowserBackgroundTab:
|
||||
elif wintype == QWebEnginePage.WebWindowType.WebBrowserBackgroundTab:
|
||||
# Middle-click / Ctrl-Click
|
||||
if background:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
|
|
@ -150,8 +152,9 @@ class WebEnginePage(QWebEnginePage):
|
|||
|
||||
Signals:
|
||||
certificate_error: Emitted on certificate errors.
|
||||
Needs to be directly connected to a slot setting the
|
||||
'ignore' attribute.
|
||||
Needs to be directly connected to a slot calling
|
||||
.accept_certificate(), .reject_certificate, or
|
||||
.defer().
|
||||
shutting_down: Emitted when the page is shutting down.
|
||||
navigation_request: Emitted on acceptNavigationRequest.
|
||||
"""
|
||||
|
|
@ -160,12 +163,43 @@ class WebEnginePage(QWebEnginePage):
|
|||
shutting_down = pyqtSignal()
|
||||
navigation_request = pyqtSignal(usertypes.NavigationRequest)
|
||||
|
||||
_JS_LOG_LEVEL_MAPPING = {
|
||||
QWebEnginePage.JavaScriptConsoleMessageLevel.InfoMessageLevel:
|
||||
usertypes.JsLogLevel.info,
|
||||
QWebEnginePage.JavaScriptConsoleMessageLevel.WarningMessageLevel:
|
||||
usertypes.JsLogLevel.warning,
|
||||
QWebEnginePage.JavaScriptConsoleMessageLevel.ErrorMessageLevel:
|
||||
usertypes.JsLogLevel.error,
|
||||
}
|
||||
|
||||
_NAVIGATION_TYPE_MAPPING = {
|
||||
QWebEnginePage.NavigationType.NavigationTypeLinkClicked:
|
||||
usertypes.NavigationRequest.Type.link_clicked,
|
||||
QWebEnginePage.NavigationType.NavigationTypeTyped:
|
||||
usertypes.NavigationRequest.Type.typed,
|
||||
QWebEnginePage.NavigationType.NavigationTypeFormSubmitted:
|
||||
usertypes.NavigationRequest.Type.form_submitted,
|
||||
QWebEnginePage.NavigationType.NavigationTypeBackForward:
|
||||
usertypes.NavigationRequest.Type.back_forward,
|
||||
QWebEnginePage.NavigationType.NavigationTypeReload:
|
||||
usertypes.NavigationRequest.Type.reload,
|
||||
QWebEnginePage.NavigationType.NavigationTypeOther:
|
||||
usertypes.NavigationRequest.Type.other,
|
||||
QWebEnginePage.NavigationType.NavigationTypeRedirect:
|
||||
usertypes.NavigationRequest.Type.redirect,
|
||||
}
|
||||
|
||||
def __init__(self, *, theme_color, profile, parent=None):
|
||||
super().__init__(profile, parent)
|
||||
self._is_shutting_down = False
|
||||
self._theme_color = theme_color
|
||||
self._set_bg_color()
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
if machinery.IS_QT6:
|
||||
self.certificateError.connect( # pylint: disable=no-member
|
||||
self._handle_certificate_error
|
||||
)
|
||||
# Qt 5: Overridden method instead of signal
|
||||
|
||||
@config.change_filter('colors.webpage.bg')
|
||||
def _set_bg_color(self):
|
||||
|
|
@ -178,11 +212,17 @@ class WebEnginePage(QWebEnginePage):
|
|||
self._is_shutting_down = True
|
||||
self.shutting_down.emit()
|
||||
|
||||
def certificateError(self, error):
|
||||
@pyqtSlot(QWebEngineCertificateError)
|
||||
def _handle_certificate_error(self, qt_error):
|
||||
"""Handle certificate errors coming from Qt."""
|
||||
error = certificateerror.CertificateErrorWrapper(error)
|
||||
error = certificateerror.CertificateErrorWrapper(qt_error)
|
||||
self.certificate_error.emit(error)
|
||||
return error.ignore
|
||||
# Right now, we never defer accepting, due to a PyQt bug
|
||||
return error.certificate_was_accepted()
|
||||
|
||||
if machinery.IS_QT5:
|
||||
# Overridden method instead of signal
|
||||
certificateError = _handle_certificate_error # noqa: N815
|
||||
|
||||
def javaScriptConfirm(self, url, js_msg):
|
||||
"""Override javaScriptConfirm to use qutebrowser prompts."""
|
||||
|
|
@ -216,42 +256,16 @@ class WebEnginePage(QWebEnginePage):
|
|||
|
||||
def javaScriptConsoleMessage(self, level, msg, line, source):
|
||||
"""Log javascript messages to qutebrowser's log."""
|
||||
level_map = {
|
||||
QWebEnginePage.InfoMessageLevel: usertypes.JsLogLevel.info,
|
||||
QWebEnginePage.WarningMessageLevel: usertypes.JsLogLevel.warning,
|
||||
QWebEnginePage.ErrorMessageLevel: usertypes.JsLogLevel.error,
|
||||
}
|
||||
shared.javascript_log_message(level_map[level], source, line, msg)
|
||||
shared.javascript_log_message(self._JS_LOG_LEVEL_MAPPING[level], source, line, msg)
|
||||
|
||||
def acceptNavigationRequest(self,
|
||||
url: QUrl,
|
||||
typ: QWebEnginePage.NavigationType,
|
||||
is_main_frame: bool) -> bool:
|
||||
"""Override acceptNavigationRequest to forward it to the tab API."""
|
||||
type_map = {
|
||||
QWebEnginePage.NavigationTypeLinkClicked:
|
||||
usertypes.NavigationRequest.Type.link_clicked,
|
||||
QWebEnginePage.NavigationTypeTyped:
|
||||
usertypes.NavigationRequest.Type.typed,
|
||||
QWebEnginePage.NavigationTypeFormSubmitted:
|
||||
usertypes.NavigationRequest.Type.form_submitted,
|
||||
QWebEnginePage.NavigationTypeBackForward:
|
||||
usertypes.NavigationRequest.Type.back_forward,
|
||||
QWebEnginePage.NavigationTypeReload:
|
||||
usertypes.NavigationRequest.Type.reloaded,
|
||||
QWebEnginePage.NavigationTypeOther:
|
||||
usertypes.NavigationRequest.Type.other,
|
||||
}
|
||||
try:
|
||||
type_map[QWebEnginePage.NavigationTypeRedirect] = (
|
||||
usertypes.NavigationRequest.Type.redirect)
|
||||
except AttributeError:
|
||||
# Added in Qt 5.14
|
||||
pass
|
||||
|
||||
navigation = usertypes.NavigationRequest(
|
||||
url=url,
|
||||
navigation_type=type_map.get(
|
||||
navigation_type=self._NAVIGATION_TYPE_MAPPING.get(
|
||||
typ, usertypes.NavigationRequest.Type.other),
|
||||
is_main_frame=is_main_frame)
|
||||
self.navigation_request.emit(navigation)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
from typing import cast
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkDiskCache
|
||||
from qutebrowser.qt.network import QNetworkDiskCache
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, standarddir
|
||||
|
|
|
|||
|
|
@ -19,19 +19,25 @@
|
|||
|
||||
"""A wrapper over a list of QSslErrors."""
|
||||
|
||||
from typing import Sequence
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from PyQt5.QtNetwork import QSslError
|
||||
from qutebrowser.qt.network import QSslError, QNetworkReply
|
||||
|
||||
from qutebrowser.utils import usertypes, utils, debug, jinja
|
||||
from qutebrowser.utils import usertypes, utils, debug, jinja, urlutils
|
||||
|
||||
|
||||
class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
||||
|
||||
"""A wrapper over a list of QSslErrors."""
|
||||
|
||||
def __init__(self, errors: Sequence[QSslError]) -> None:
|
||||
def __init__(self, reply: QNetworkReply, errors: Sequence[QSslError]) -> None:
|
||||
super().__init__()
|
||||
self._reply = reply
|
||||
self._errors = tuple(errors) # needs to be hashable
|
||||
try:
|
||||
self._host_tpl: Optional[urlutils.HostTupleType] = urlutils.host_tuple(reply.url())
|
||||
except ValueError:
|
||||
self._host_tpl = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '\n'.join(err.errorString() for err in self._errors)
|
||||
|
|
@ -43,16 +49,25 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
|||
string=str(self))
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._errors)
|
||||
return hash((self._host_tpl, self._errors))
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, CertificateErrorWrapper):
|
||||
return NotImplemented
|
||||
return self._errors == other._errors
|
||||
return self._errors == other._errors and self._host_tpl == other._host_tpl
|
||||
|
||||
def is_overridable(self) -> bool:
|
||||
return True
|
||||
|
||||
def defer(self) -> None:
|
||||
raise usertypes.UndeferrableError("Never deferrable")
|
||||
|
||||
def accept_certificate(self) -> None:
|
||||
super().accept_certificate()
|
||||
self._reply.ignoreSslErrors()
|
||||
|
||||
# Not overriding reject_certificate because that's default in QNetworkReply
|
||||
|
||||
def html(self):
|
||||
if len(self._errors) == 1:
|
||||
return super().html()
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
from typing import Sequence
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkCookie, QNetworkCookieJar
|
||||
from PyQt5.QtCore import pyqtSignal, QDateTime
|
||||
from qutebrowser.qt.network import QNetworkCookie, QNetworkCookieJar
|
||||
from qutebrowser.qt.core import pyqtSignal, QDateTime
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, standarddir, objreg, log
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import dataclasses
|
|||
import os.path
|
||||
from typing import Type
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest
|
||||
from qutebrowser.qt.network import QNetworkRequest
|
||||
|
||||
from qutebrowser.utils import log, utils
|
||||
|
||||
|
|
@ -191,7 +191,7 @@ def parse_content_type(reply):
|
|||
A [mimetype, rest] list, or [None, None] if unset.
|
||||
Rest can be None.
|
||||
"""
|
||||
content_type = reply.header(QNetworkRequest.ContentTypeHeader)
|
||||
content_type = reply.header(QNetworkRequest.KnownHeaders.ContentTypeHeader)
|
||||
if content_type is None:
|
||||
return [None, None]
|
||||
if ';' in content_type:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import quopri
|
|||
import dataclasses
|
||||
from typing import MutableMapping, Set, Tuple, Callable
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from qutebrowser.qt.core import QUrl
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.browser.webkit import webkitelem
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import html
|
|||
import dataclasses
|
||||
from typing import TYPE_CHECKING, Dict, MutableMapping, Optional, Set
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QUrl, QByteArray
|
||||
from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslConfiguration,
|
||||
from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QUrl, QByteArray
|
||||
from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkReply, QSslConfiguration,
|
||||
QNetworkProxy)
|
||||
|
||||
from qutebrowser.config import config
|
||||
|
|
@ -79,7 +79,7 @@ def _is_secure_cipher(cipher):
|
|||
return False
|
||||
# OpenSSL should already protect against this in a better way
|
||||
# elif (('CBC3' in tokens or 'CBC' in tokens) and (cipher.protocol() not in
|
||||
# [QSsl.TlsV1_0, QSsl.TlsV1_1, QSsl.TlsV1_2])):
|
||||
# [QSsl.SslProtocol.TlsV1_0, QSsl.SslProtocol.TlsV1_1, QSsl.SslProtocol.TlsV1_2])):
|
||||
# # https://en.wikipedia.org/wiki/POODLE
|
||||
# return False
|
||||
### These things should never happen as those are already filtered out by
|
||||
|
|
@ -103,8 +103,8 @@ def _is_secure_cipher(cipher):
|
|||
|
||||
def init():
|
||||
"""Disable insecure SSL ciphers on old Qt versions."""
|
||||
sslconfig = QSslConfiguration.defaultConfiguration()
|
||||
default_ciphers = sslconfig.ciphers()
|
||||
ssl_config = QSslConfiguration.defaultConfiguration()
|
||||
default_ciphers = ssl_config.ciphers()
|
||||
log.init.vdebug( # type: ignore[attr-defined]
|
||||
"Default Qt ciphers: {}".format(
|
||||
', '.join(c.name() for c in default_ciphers)))
|
||||
|
|
@ -120,7 +120,7 @@ def init():
|
|||
if bad_ciphers:
|
||||
log.init.debug("Disabling bad ciphers: {}".format(
|
||||
', '.join(c.name() for c in bad_ciphers)))
|
||||
sslconfig.setCiphers(good_ciphers)
|
||||
ssl_config.setCiphers(good_ciphers)
|
||||
|
||||
|
||||
_SavedErrorsType = MutableMapping[
|
||||
|
|
@ -239,12 +239,16 @@ class NetworkManager(QNetworkAccessManager):
|
|||
|
||||
def shutdown(self):
|
||||
"""Abort all running requests."""
|
||||
self.setNetworkAccessible(QNetworkAccessManager.NotAccessible)
|
||||
try:
|
||||
self.setNetworkAccessible(QNetworkAccessManager.NetworkAccessibility.NotAccessible)
|
||||
except AttributeError:
|
||||
# Qt 5 only, deprecated seemingly without replacement.
|
||||
pass
|
||||
self.shutting_down.emit()
|
||||
|
||||
# No @pyqtSlot here, see
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2213
|
||||
def on_ssl_errors(self, reply, qt_errors): # noqa: C901 pragma: no mccabe
|
||||
def on_ssl_errors(self, reply, qt_errors):
|
||||
"""Decide if SSL errors should be ignored or not.
|
||||
|
||||
This slot is called on SSL/TLS errors by the self.sslErrors signal.
|
||||
|
|
@ -253,7 +257,7 @@ class NetworkManager(QNetworkAccessManager):
|
|||
reply: The QNetworkReply that is encountering the errors.
|
||||
qt_errors: A list of errors.
|
||||
"""
|
||||
errors = certificateerror.CertificateErrorWrapper(qt_errors)
|
||||
errors = certificateerror.CertificateErrorWrapper(reply, qt_errors)
|
||||
log.network.debug("Certificate errors: {!r}".format(errors))
|
||||
try:
|
||||
host_tpl: Optional[urlutils.HostTupleType] = urlutils.host_tuple(
|
||||
|
|
@ -281,14 +285,14 @@ class NetworkManager(QNetworkAccessManager):
|
|||
tab = self._get_tab()
|
||||
first_party_url = QUrl() if tab is None else tab.data.last_navigation.url
|
||||
|
||||
ignore = shared.ignore_certificate_error(
|
||||
shared.handle_certificate_error(
|
||||
request_url=reply.url(),
|
||||
first_party_url=first_party_url,
|
||||
error=errors,
|
||||
abort_on=abort_on,
|
||||
)
|
||||
if ignore:
|
||||
reply.ignoreSslErrors()
|
||||
|
||||
if errors.certificate_was_accepted():
|
||||
if host_tpl is not None:
|
||||
self._accepted_ssl_errors[host_tpl].add(errors)
|
||||
elif host_tpl is not None:
|
||||
|
|
@ -406,14 +410,14 @@ class NetworkManager(QNetworkAccessManager):
|
|||
proxy_error = proxymod.application_factory.get_error()
|
||||
if proxy_error is not None:
|
||||
return networkreply.ErrorNetworkReply(
|
||||
req, proxy_error, QNetworkReply.UnknownProxyError,
|
||||
req, proxy_error, QNetworkReply.NetworkError.UnknownProxyError,
|
||||
self)
|
||||
|
||||
if not req.url().isValid():
|
||||
log.network.debug("Ignoring invalid requested URL: {}".format(
|
||||
req.url().errorString()))
|
||||
return networkreply.ErrorNetworkReply(
|
||||
req, "Invalid request URL", QNetworkReply.HostNotFoundError,
|
||||
req, "Invalid request URL", QNetworkReply.NetworkError.HostNotFoundError,
|
||||
self)
|
||||
|
||||
for header, value in shared.custom_headers(url=req.url()):
|
||||
|
|
@ -433,7 +437,7 @@ class NetworkManager(QNetworkAccessManager):
|
|||
interceptors.run(request)
|
||||
if request.is_blocked:
|
||||
return networkreply.ErrorNetworkReply(
|
||||
req, HOSTBLOCK_ERROR_STRING, QNetworkReply.ContentAccessDenied,
|
||||
req, HOSTBLOCK_ERROR_STRING, QNetworkReply.NetworkError.ContentAccessDenied,
|
||||
self)
|
||||
|
||||
if 'log-requests' in objects.debug_flags:
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@
|
|||
|
||||
"""Special network replies.."""
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
from PyQt5.QtCore import pyqtSlot, QIODevice, QByteArray, QTimer
|
||||
from qutebrowser.qt.network import QNetworkReply, QNetworkRequest
|
||||
from qutebrowser.qt.core import pyqtSlot, QIODevice, QByteArray, QTimer
|
||||
|
||||
|
||||
class FixedDataNetworkReply(QNetworkReply):
|
||||
|
|
@ -49,13 +49,13 @@ class FixedDataNetworkReply(QNetworkReply):
|
|||
|
||||
self.setRequest(request)
|
||||
self.setUrl(request.url())
|
||||
self.setOpenMode(QIODevice.ReadOnly)
|
||||
self.setOpenMode(QIODevice.OpenModeFlag.ReadOnly)
|
||||
|
||||
self.setHeader(QNetworkRequest.ContentTypeHeader, mimeType)
|
||||
self.setHeader(QNetworkRequest.ContentLengthHeader,
|
||||
self.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, mimeType)
|
||||
self.setHeader(QNetworkRequest.KnownHeaders.ContentLengthHeader,
|
||||
QByteArray.number(len(fileData)))
|
||||
self.setAttribute(QNetworkRequest.HttpStatusCodeAttribute, 200)
|
||||
self.setAttribute(QNetworkRequest.HttpReasonPhraseAttribute, 'OK')
|
||||
self.setAttribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute, 200)
|
||||
self.setAttribute(QNetworkRequest.Attribute.HttpReasonPhraseAttribute, 'OK')
|
||||
# For some reason, a segfault will be triggered if these lambdas aren't
|
||||
# there.
|
||||
# pylint: disable=unnecessary-lambda
|
||||
|
|
@ -114,9 +114,9 @@ class ErrorNetworkReply(QNetworkReply):
|
|||
self.setUrl(req.url())
|
||||
# We don't actually want to read anything, but we still need to open
|
||||
# the device to avoid getting a warning.
|
||||
self.setOpenMode(QIODevice.ReadOnly)
|
||||
self.setOpenMode(QIODevice.OpenModeFlag.ReadOnly)
|
||||
self.setError(error, errorstring)
|
||||
QTimer.singleShot(0, lambda: self.error.emit(error))
|
||||
QTimer.singleShot(0, lambda: self.errorOccurred.emit(error))
|
||||
QTimer.singleShot(0, lambda: self.finished.emit())
|
||||
|
||||
def abort(self):
|
||||
|
|
@ -143,7 +143,7 @@ class RedirectNetworkReply(QNetworkReply):
|
|||
|
||||
def __init__(self, new_url, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url)
|
||||
self.setAttribute(QNetworkRequest.Attribute.RedirectionTargetAttribute, new_url)
|
||||
QTimer.singleShot(0, lambda: self.finished.emit())
|
||||
|
||||
def abort(self):
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
"""QtWebKit specific qute://* handlers and glue code."""
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkAccessManager
|
||||
from qutebrowser.qt.core import QUrl
|
||||
from qutebrowser.qt.network import QNetworkReply, QNetworkAccessManager
|
||||
|
||||
from qutebrowser.browser import qutescheme
|
||||
from qutebrowser.browser.webkit.network import networkreply
|
||||
|
|
@ -38,10 +38,10 @@ def handler(request, operation, current_url):
|
|||
Return:
|
||||
A QNetworkReply.
|
||||
"""
|
||||
if operation != QNetworkAccessManager.GetOperation:
|
||||
if operation != QNetworkAccessManager.Operation.GetOperation:
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, "Unsupported request type",
|
||||
QNetworkReply.ContentOperationNotPermittedError)
|
||||
QNetworkReply.NetworkError.ContentOperationNotPermittedError)
|
||||
|
||||
url = request.url()
|
||||
|
||||
|
|
@ -53,22 +53,22 @@ def handler(request, operation, current_url):
|
|||
url.toDisplayString()))
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, "Invalid qute://settings request",
|
||||
QNetworkReply.ContentAccessDenied)
|
||||
QNetworkReply.NetworkError.ContentAccessDenied)
|
||||
|
||||
try:
|
||||
mimetype, data = qutescheme.data_for_url(url)
|
||||
except qutescheme.Error as e:
|
||||
errors = {
|
||||
qutescheme.NotFoundError:
|
||||
QNetworkReply.ContentNotFoundError,
|
||||
QNetworkReply.NetworkError.ContentNotFoundError,
|
||||
qutescheme.UrlInvalidError:
|
||||
QNetworkReply.ContentOperationNotPermittedError,
|
||||
QNetworkReply.NetworkError.ContentOperationNotPermittedError,
|
||||
qutescheme.RequestDeniedError:
|
||||
QNetworkReply.ContentAccessDenied,
|
||||
QNetworkReply.NetworkError.ContentAccessDenied,
|
||||
qutescheme.SchemeOSError:
|
||||
QNetworkReply.ContentNotFoundError,
|
||||
QNetworkReply.NetworkError.ContentNotFoundError,
|
||||
qutescheme.Error:
|
||||
QNetworkReply.InternalServerError,
|
||||
QNetworkReply.NetworkError.InternalServerError,
|
||||
}
|
||||
exctype = type(e)
|
||||
log.misc.error("{} while handling qute://* URL".format(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from typing import Any, List, Mapping
|
||||
|
||||
from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
|
||||
from qutebrowser.qt.core import QByteArray, QDataStream, QIODevice, QUrl
|
||||
|
||||
from qutebrowser.utils import qtutils
|
||||
|
||||
|
|
@ -50,10 +50,10 @@ def _serialize_items(items, current_idx, stream):
|
|||
|
||||
def _serialize_item(item):
|
||||
data = {
|
||||
'originalURLString': item.original_url.toString(QUrl.FullyEncoded),
|
||||
'originalURLString': item.original_url.toString(QUrl.ComponentFormattingOption.FullyEncoded),
|
||||
'scrollPosition': {'x': 0, 'y': 0},
|
||||
'title': item.title,
|
||||
'urlString': item.url.toString(QUrl.FullyEncoded),
|
||||
'urlString': item.url.toString(QUrl.ComponentFormattingOption.FullyEncoded),
|
||||
}
|
||||
try:
|
||||
data['scrollPosition']['x'] = item.user_data['scroll-pos'].x()
|
||||
|
|
@ -80,7 +80,7 @@ def serialize(items):
|
|||
segfault!
|
||||
"""
|
||||
data = QByteArray()
|
||||
stream = QDataStream(data, QIODevice.ReadWrite)
|
||||
stream = QDataStream(data, QIODevice.OpenModeFlag.ReadWrite)
|
||||
user_data: List[Mapping[str, Any]] = []
|
||||
|
||||
current_idx = None
|
||||
|
|
|
|||
|
|
@ -17,13 +17,16 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# 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."""
|
||||
|
||||
from typing import cast, TYPE_CHECKING, Iterator, List, Optional, Set
|
||||
|
||||
from PyQt5.QtCore import QRect, Qt
|
||||
from PyQt5.QtWebKit import QWebElement, QWebSettings
|
||||
from PyQt5.QtWebKitWidgets import QWebFrame
|
||||
from qutebrowser.qt.core import QRect, Qt
|
||||
from qutebrowser.qt.webkit import QWebElement, QWebSettings
|
||||
from qutebrowser.qt.webkitwidgets import QWebFrame
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log, utils, javascript, usertypes
|
||||
|
|
@ -276,7 +279,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
def _is_hidden_css(self) -> bool:
|
||||
"""Check if the given element is hidden via CSS."""
|
||||
attr_values = {
|
||||
attr: self._elem.styleProperty(attr, QWebElement.ComputedStyle)
|
||||
attr: self._elem.styleProperty(attr, QWebElement.StyleResolveStrategy.ComputedStyle)
|
||||
for attr in ['visibility', 'display', 'opacity']
|
||||
}
|
||||
invisible = attr_values['visibility'] == 'hidden'
|
||||
|
|
@ -362,7 +365,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
|
||||
def _click_js(self, click_target: usertypes.ClickTarget) -> None:
|
||||
settings = QWebSettings.globalSettings()
|
||||
attribute = QWebSettings.JavascriptCanOpenWindows
|
||||
attribute = QWebSettings.WebAttribute.JavascriptCanOpenWindows
|
||||
could_open_windows = settings.testAttribute(attribute)
|
||||
settings.setAttribute(attribute, True)
|
||||
ok = self._elem.evaluateJavaScript('this.click(); true;')
|
||||
|
|
@ -372,7 +375,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
self._click_fake_event(click_target)
|
||||
|
||||
def _click_fake_event(self, click_target: usertypes.ClickTarget,
|
||||
button: Qt.MouseButton = Qt.LeftButton) -> None:
|
||||
button: Qt.MouseButton = Qt.MouseButton.LeftButton) -> None:
|
||||
self._tab.data.override_target = click_target
|
||||
super()._click_fake_event(click_target)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,14 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# 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."""
|
||||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtWebKit import QWebHistoryInterface
|
||||
from qutebrowser.qt.webkit import QWebHistoryInterface
|
||||
|
||||
from qutebrowser.utils import debug
|
||||
from qutebrowser.misc import debugcachestats
|
||||
|
|
|
|||
|
|
@ -17,11 +17,14 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# FIXME:qt6 (lint)
|
||||
# pylint: disable=no-name-in-module
|
||||
|
||||
"""Customized QWebInspector for QtWebKit."""
|
||||
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtWebKitWidgets import QWebInspector, QWebPage
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from qutebrowser.qt.webkit import QWebSettings
|
||||
from qutebrowser.qt.webkitwidgets import QWebInspector, QWebPage
|
||||
from qutebrowser.qt.widgets import QWidget
|
||||
|
||||
from qutebrowser.browser import inspector
|
||||
from qutebrowser.misc import miscwidgets
|
||||
|
|
@ -40,5 +43,5 @@ class WebKitInspector(inspector.AbstractWebInspector):
|
|||
|
||||
def inspect(self, page: QWebPage) -> None:
|
||||
settings = QWebSettings.globalSettings()
|
||||
settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
|
||||
settings.setAttribute(QWebSettings.WebAttribute.DeveloperExtrasEnabled, True)
|
||||
self._widget.setPage(page)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# 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.
|
||||
|
||||
Module attributes:
|
||||
|
|
@ -27,10 +30,10 @@ Module attributes:
|
|||
from typing import cast
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtWebKitWidgets import QWebPage
|
||||
from qutebrowser.qt.core import QUrl
|
||||
from qutebrowser.qt.gui import QFont
|
||||
from qutebrowser.qt.webkit import QWebSettings
|
||||
from qutebrowser.qt.webkitwidgets import QWebPage
|
||||
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.config.websettings import AttributeInfo as Attr
|
||||
|
|
@ -50,82 +53,82 @@ class WebKitSettings(websettings.AbstractSettings):
|
|||
|
||||
_ATTRIBUTES = {
|
||||
'content.images':
|
||||
Attr(QWebSettings.AutoLoadImages),
|
||||
Attr(QWebSettings.WebAttribute.AutoLoadImages),
|
||||
'content.javascript.enabled':
|
||||
Attr(QWebSettings.JavascriptEnabled),
|
||||
Attr(QWebSettings.WebAttribute.JavascriptEnabled),
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attr(QWebSettings.JavascriptCanOpenWindows),
|
||||
Attr(QWebSettings.WebAttribute.JavascriptCanOpenWindows),
|
||||
'content.javascript.can_close_tabs':
|
||||
Attr(QWebSettings.JavascriptCanCloseWindows),
|
||||
Attr(QWebSettings.WebAttribute.JavascriptCanCloseWindows),
|
||||
'content.javascript.clipboard':
|
||||
Attr(QWebSettings.JavascriptCanAccessClipboard,
|
||||
Attr(QWebSettings.WebAttribute.JavascriptCanAccessClipboard,
|
||||
converter=lambda val: val != "none"),
|
||||
'content.plugins':
|
||||
Attr(QWebSettings.PluginsEnabled),
|
||||
Attr(QWebSettings.WebAttribute.PluginsEnabled),
|
||||
'content.webgl':
|
||||
Attr(QWebSettings.WebGLEnabled),
|
||||
Attr(QWebSettings.WebAttribute.WebGLEnabled),
|
||||
'content.hyperlink_auditing':
|
||||
Attr(QWebSettings.HyperlinkAuditingEnabled),
|
||||
Attr(QWebSettings.WebAttribute.HyperlinkAuditingEnabled),
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attr(QWebSettings.LocalContentCanAccessRemoteUrls),
|
||||
Attr(QWebSettings.WebAttribute.LocalContentCanAccessRemoteUrls),
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attr(QWebSettings.LocalContentCanAccessFileUrls),
|
||||
Attr(QWebSettings.WebAttribute.LocalContentCanAccessFileUrls),
|
||||
'content.dns_prefetch':
|
||||
Attr(QWebSettings.DnsPrefetchEnabled),
|
||||
Attr(QWebSettings.WebAttribute.DnsPrefetchEnabled),
|
||||
'content.frame_flattening':
|
||||
Attr(QWebSettings.FrameFlatteningEnabled),
|
||||
Attr(QWebSettings.WebAttribute.FrameFlatteningEnabled),
|
||||
'content.cache.appcache':
|
||||
Attr(QWebSettings.OfflineWebApplicationCacheEnabled),
|
||||
Attr(QWebSettings.WebAttribute.OfflineWebApplicationCacheEnabled),
|
||||
'content.local_storage':
|
||||
Attr(QWebSettings.LocalStorageEnabled,
|
||||
QWebSettings.OfflineStorageDatabaseEnabled),
|
||||
Attr(QWebSettings.WebAttribute.LocalStorageEnabled,
|
||||
QWebSettings.WebAttribute.OfflineStorageDatabaseEnabled),
|
||||
'content.print_element_backgrounds':
|
||||
Attr(QWebSettings.PrintElementBackgrounds),
|
||||
Attr(QWebSettings.WebAttribute.PrintElementBackgrounds),
|
||||
'content.xss_auditing':
|
||||
Attr(QWebSettings.XSSAuditingEnabled),
|
||||
Attr(QWebSettings.WebAttribute.XSSAuditingEnabled),
|
||||
'content.site_specific_quirks.enabled':
|
||||
Attr(QWebSettings.SiteSpecificQuirksEnabled),
|
||||
Attr(QWebSettings.WebAttribute.SiteSpecificQuirksEnabled),
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attr(QWebSettings.SpatialNavigationEnabled),
|
||||
Attr(QWebSettings.WebAttribute.SpatialNavigationEnabled),
|
||||
'input.links_included_in_focus_chain':
|
||||
Attr(QWebSettings.LinksIncludedInFocusChain),
|
||||
Attr(QWebSettings.WebAttribute.LinksIncludedInFocusChain),
|
||||
|
||||
'zoom.text_only':
|
||||
Attr(QWebSettings.ZoomTextOnly),
|
||||
Attr(QWebSettings.WebAttribute.ZoomTextOnly),
|
||||
'scrolling.smooth':
|
||||
Attr(QWebSettings.ScrollAnimatorEnabled),
|
||||
Attr(QWebSettings.WebAttribute.ScrollAnimatorEnabled),
|
||||
}
|
||||
|
||||
_FONT_SIZES = {
|
||||
'fonts.web.size.minimum':
|
||||
QWebSettings.MinimumFontSize,
|
||||
QWebSettings.FontSize.MinimumFontSize,
|
||||
'fonts.web.size.minimum_logical':
|
||||
QWebSettings.MinimumLogicalFontSize,
|
||||
QWebSettings.FontSize.MinimumLogicalFontSize,
|
||||
'fonts.web.size.default':
|
||||
QWebSettings.DefaultFontSize,
|
||||
QWebSettings.FontSize.DefaultFontSize,
|
||||
'fonts.web.size.default_fixed':
|
||||
QWebSettings.DefaultFixedFontSize,
|
||||
QWebSettings.FontSize.DefaultFixedFontSize,
|
||||
}
|
||||
|
||||
_FONT_FAMILIES = {
|
||||
'fonts.web.family.standard': QWebSettings.StandardFont,
|
||||
'fonts.web.family.fixed': QWebSettings.FixedFont,
|
||||
'fonts.web.family.serif': QWebSettings.SerifFont,
|
||||
'fonts.web.family.sans_serif': QWebSettings.SansSerifFont,
|
||||
'fonts.web.family.cursive': QWebSettings.CursiveFont,
|
||||
'fonts.web.family.fantasy': QWebSettings.FantasyFont,
|
||||
'fonts.web.family.standard': QWebSettings.FontFamily.StandardFont,
|
||||
'fonts.web.family.fixed': QWebSettings.FontFamily.FixedFont,
|
||||
'fonts.web.family.serif': QWebSettings.FontFamily.SerifFont,
|
||||
'fonts.web.family.sans_serif': QWebSettings.FontFamily.SansSerifFont,
|
||||
'fonts.web.family.cursive': QWebSettings.FontFamily.CursiveFont,
|
||||
'fonts.web.family.fantasy': QWebSettings.FontFamily.FantasyFont,
|
||||
}
|
||||
|
||||
# Mapping from QWebSettings::QWebSettings() in
|
||||
# qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp
|
||||
_FONT_TO_QFONT = {
|
||||
QWebSettings.StandardFont: QFont.Serif,
|
||||
QWebSettings.FixedFont: QFont.Monospace,
|
||||
QWebSettings.SerifFont: QFont.Serif,
|
||||
QWebSettings.SansSerifFont: QFont.SansSerif,
|
||||
QWebSettings.CursiveFont: QFont.Cursive,
|
||||
QWebSettings.FantasyFont: QFont.Fantasy,
|
||||
QWebSettings.FontFamily.StandardFont: QFont.StyleHint.Serif,
|
||||
QWebSettings.FontFamily.FixedFont: QFont.StyleHint.Monospace,
|
||||
QWebSettings.FontFamily.SerifFont: QFont.StyleHint.Serif,
|
||||
QWebSettings.FontFamily.SansSerifFont: QFont.StyleHint.SansSerif,
|
||||
QWebSettings.FontFamily.CursiveFont: QFont.StyleHint.Cursive,
|
||||
QWebSettings.FontFamily.FantasyFont: QFont.StyleHint.Fantasy,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -139,10 +142,10 @@ def _set_user_stylesheet(settings):
|
|||
def _set_cookie_accept_policy(settings):
|
||||
"""Update the content.cookies.accept setting."""
|
||||
mapping = {
|
||||
'all': QWebSettings.AlwaysAllowThirdPartyCookies,
|
||||
'no-3rdparty': QWebSettings.AlwaysBlockThirdPartyCookies,
|
||||
'never': QWebSettings.AlwaysBlockThirdPartyCookies,
|
||||
'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies,
|
||||
'all': QWebSettings.ThirdPartyCookiePolicy.AlwaysAllowThirdPartyCookies,
|
||||
'no-3rdparty': QWebSettings.ThirdPartyCookiePolicy.AlwaysBlockThirdPartyCookies,
|
||||
'never': QWebSettings.ThirdPartyCookiePolicy.AlwaysBlockThirdPartyCookies,
|
||||
'no-unknown-3rdparty': QWebSettings.ThirdPartyCookiePolicy.AllowThirdPartyWithExistingCookies,
|
||||
}
|
||||
value = config.val.content.cookies.accept
|
||||
settings.setThirdPartyCookiePolicy(mapping[value])
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# 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."""
|
||||
|
||||
import re
|
||||
|
|
@ -24,12 +27,12 @@ import functools
|
|||
import xml.etree.ElementTree
|
||||
from typing import cast, Iterable, Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QPoint, QTimer, QSizeF, QSize
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
||||
from PyQt5.QtWebKit import QWebSettings, QWebHistory, QWebElement
|
||||
from PyQt5.QtPrintSupport import QPrinter
|
||||
from qutebrowser.qt.core import pyqtSlot, Qt, QUrl, QPoint, QTimer, QSizeF, QSize
|
||||
from qutebrowser.qt.gui import QIcon
|
||||
from qutebrowser.qt.widgets import QWidget
|
||||
from qutebrowser.qt.webkitwidgets import QWebPage, QWebFrame
|
||||
from qutebrowser.qt.webkit import QWebSettings, QWebHistory, QWebElement
|
||||
from qutebrowser.qt.printsupport import QPrinter
|
||||
|
||||
from qutebrowser.browser import browsertab, shared
|
||||
from qutebrowser.browser.webkit import (webview, webpage, tabhistory, webkitelem,
|
||||
|
|
@ -44,7 +47,6 @@ class WebKitAction(browsertab.AbstractAction):
|
|||
|
||||
"""QtWebKit implementations related to web actions."""
|
||||
|
||||
action_class = QWebPage
|
||||
action_base = QWebPage.WebAction
|
||||
|
||||
_widget: webview.WebView
|
||||
|
|
@ -67,9 +69,9 @@ class WebKitAction(browsertab.AbstractAction):
|
|||
"""
|
||||
new_actions = {
|
||||
# https://github.com/qtwebkit/qtwebkit/commit/a96d9ef5d24b02d996ad14ff050d0e485c9ddc97
|
||||
'RequestClose': QWebPage.ToggleVideoFullscreen + 1,
|
||||
'RequestClose': QWebPage.WebAction.ToggleVideoFullscreen + 1,
|
||||
# https://github.com/qtwebkit/qtwebkit/commit/96b9ba6269a5be44343635a7aaca4a153ea0366b
|
||||
'Unselect': QWebPage.ToggleVideoFullscreen + 2,
|
||||
'Unselect': QWebPage.WebAction.ToggleVideoFullscreen + 2,
|
||||
}
|
||||
if name in new_actions:
|
||||
self._widget.triggerPageAction(new_actions[name]) # type: ignore[arg-type]
|
||||
|
|
@ -93,13 +95,14 @@ class WebKitPrinting(browsertab.AbstractPrinting):
|
|||
def to_pdf(self, filename):
|
||||
printer = QPrinter()
|
||||
printer.setOutputFileName(filename)
|
||||
self.to_printer(printer)
|
||||
|
||||
def to_printer(self, printer, callback=None):
|
||||
self._widget.print(printer)
|
||||
# Can't find out whether there was an error...
|
||||
if callback is not None:
|
||||
callback(True)
|
||||
self.pdf_printing_finished.emit(filename, True)
|
||||
|
||||
def to_printer(self, printer):
|
||||
self._widget.print(printer)
|
||||
# Can't find out whether there was an error...
|
||||
self.printing_finished.emit(True)
|
||||
|
||||
|
||||
class WebKitSearch(browsertab.AbstractSearch):
|
||||
|
|
@ -118,9 +121,9 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
def _args_to_flags(self, reverse, ignore_case):
|
||||
flags = self._empty_flags()
|
||||
if self._is_case_sensitive(ignore_case):
|
||||
flags |= QWebPage.FindCaseSensitively
|
||||
flags |= QWebPage.FindFlag.FindCaseSensitively
|
||||
if reverse:
|
||||
flags |= QWebPage.FindBackward
|
||||
flags |= QWebPage.FindFlag.FindBackward
|
||||
return flags
|
||||
|
||||
def _call_cb(self, callback, found, text, flags, caller):
|
||||
|
|
@ -139,7 +142,7 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
# Removing FindWrapsAroundDocument to get the same logging as with
|
||||
# QtWebEngine
|
||||
debug_flags = debug.qflags_key(
|
||||
QWebPage, flags & ~QWebPage.FindWrapsAroundDocument,
|
||||
QWebPage, flags & ~QWebPage.FindFlag.FindWrapsAroundDocument,
|
||||
klass=QWebPage.FindFlag)
|
||||
if debug_flags != '0x0000':
|
||||
flag_text = 'with flags {}'.format(debug_flags)
|
||||
|
|
@ -171,7 +174,7 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
# We first clear the marked text, then the highlights
|
||||
self._widget.findText('')
|
||||
self._widget.findText(
|
||||
'', QWebPage.HighlightAllOccurrences) # type: ignore[arg-type]
|
||||
'', QWebPage.FindFlag.HighlightAllOccurrences) # type: ignore[arg-type]
|
||||
|
||||
def search(self, text, *, ignore_case=usertypes.IgnoreCase.never,
|
||||
reverse=False, result_cb=None):
|
||||
|
|
@ -192,7 +195,7 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
# to get a mark so we can navigate.
|
||||
found = self._widget.findText(text, self._flags)
|
||||
self._widget.findText(text,
|
||||
self._flags | QWebPage.HighlightAllOccurrences)
|
||||
self._flags | QWebPage.FindFlag.HighlightAllOccurrences)
|
||||
self._call_cb(result_cb, found, text, self._flags, 'search')
|
||||
|
||||
def next_result(self, *, wrap=False, callback=None):
|
||||
|
|
@ -202,7 +205,7 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
int(self._flags)) # type: ignore[call-overload]
|
||||
|
||||
if wrap:
|
||||
flags |= QWebPage.FindWrapsAroundDocument
|
||||
flags |= QWebPage.FindFlag.FindWrapsAroundDocument
|
||||
|
||||
found = self._widget.findText(self.text, flags) # type: ignore[arg-type]
|
||||
self._call_cb(callback, found, self.text, flags, 'next_result')
|
||||
|
|
@ -213,13 +216,13 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
flags = QWebPage.FindFlags(
|
||||
int(self._flags)) # type: ignore[call-overload]
|
||||
|
||||
if flags & QWebPage.FindBackward:
|
||||
flags &= ~QWebPage.FindBackward
|
||||
if flags & QWebPage.FindFlag.FindBackward:
|
||||
flags &= ~QWebPage.FindFlag.FindBackward
|
||||
else:
|
||||
flags |= QWebPage.FindBackward
|
||||
flags |= QWebPage.FindFlag.FindBackward
|
||||
|
||||
if wrap:
|
||||
flags |= QWebPage.FindWrapsAroundDocument
|
||||
flags |= QWebPage.FindFlag.FindWrapsAroundDocument
|
||||
|
||||
found = self._widget.findText(self.text, flags) # type: ignore[arg-type]
|
||||
self._call_cb(callback, found, self.text, flags, 'prev_result')
|
||||
|
|
@ -249,13 +252,13 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
self._selection_state = browsertab.SelectionState.none
|
||||
self.selection_toggled.emit(self._selection_state)
|
||||
settings = self._widget.settings()
|
||||
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
|
||||
settings.setAttribute(QWebSettings.WebAttribute.CaretBrowsingEnabled, True)
|
||||
|
||||
if self._widget.isVisible():
|
||||
# Sometimes the caret isn't immediately visible, but unfocusing
|
||||
# and refocusing it fixes that.
|
||||
self._widget.clearFocus()
|
||||
self._widget.setFocus(Qt.OtherFocusReason)
|
||||
self._widget.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
|
||||
# Move the caret to the first element in the viewport if there
|
||||
# isn't any text which is already selected.
|
||||
|
|
@ -269,19 +272,19 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_left(self, _mode):
|
||||
settings = self._widget.settings()
|
||||
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
|
||||
if settings.testAttribute(QWebSettings.WebAttribute.CaretBrowsingEnabled):
|
||||
if (self._selection_state is not browsertab.SelectionState.none and
|
||||
self._widget.hasSelection()):
|
||||
# Remove selection if it exists
|
||||
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
|
||||
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False)
|
||||
self._widget.triggerPageAction(QWebPage.WebAction.MoveToNextChar)
|
||||
settings.setAttribute(QWebSettings.WebAttribute.CaretBrowsingEnabled, False)
|
||||
self._selection_state = browsertab.SelectionState.none
|
||||
|
||||
def move_to_next_line(self, count=1):
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = QWebPage.SelectNextLine
|
||||
act = QWebPage.WebAction.SelectNextLine
|
||||
else:
|
||||
act = QWebPage.MoveToNextLine
|
||||
act = QWebPage.WebAction.MoveToNextLine
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
|
|
@ -289,9 +292,9 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
|
||||
def move_to_prev_line(self, count=1):
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = QWebPage.SelectPreviousLine
|
||||
act = QWebPage.WebAction.SelectPreviousLine
|
||||
else:
|
||||
act = QWebPage.MoveToPreviousLine
|
||||
act = QWebPage.WebAction.MoveToPreviousLine
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
|
|
@ -299,89 +302,89 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
|
||||
def move_to_next_char(self, count=1):
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectNextChar
|
||||
act = QWebPage.WebAction.SelectNextChar
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToNextChar
|
||||
act = QWebPage.WebAction.MoveToNextChar
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_prev_char(self, count=1):
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectPreviousChar
|
||||
act = QWebPage.WebAction.SelectPreviousChar
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToPreviousChar
|
||||
act = QWebPage.WebAction.MoveToPreviousChar
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_end_of_word(self, count=1):
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = [QWebPage.SelectNextWord]
|
||||
act = [QWebPage.WebAction.SelectNextWord]
|
||||
if utils.is_windows: # pragma: no cover
|
||||
act.append(QWebPage.SelectPreviousChar)
|
||||
act.append(QWebPage.WebAction.SelectPreviousChar)
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
act = [QWebPage.WebAction.MoveToNextWord]
|
||||
if utils.is_windows: # pragma: no cover
|
||||
act.append(QWebPage.MoveToPreviousChar)
|
||||
act.append(QWebPage.WebAction.MoveToPreviousChar)
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
|
||||
def move_to_next_word(self, count=1):
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = [QWebPage.SelectNextWord]
|
||||
act = [QWebPage.WebAction.SelectNextWord]
|
||||
if not utils.is_windows: # pragma: no branch
|
||||
act.append(QWebPage.SelectNextChar)
|
||||
act.append(QWebPage.WebAction.SelectNextChar)
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
act = [QWebPage.WebAction.MoveToNextWord]
|
||||
if not utils.is_windows: # pragma: no branch
|
||||
act.append(QWebPage.MoveToNextChar)
|
||||
act.append(QWebPage.WebAction.MoveToNextChar)
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
|
||||
def move_to_prev_word(self, count=1):
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectPreviousWord
|
||||
act = QWebPage.WebAction.SelectPreviousWord
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToPreviousWord
|
||||
act = QWebPage.WebAction.MoveToPreviousWord
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_start_of_line(self):
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectStartOfLine
|
||||
act = QWebPage.WebAction.SelectStartOfLine
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToStartOfLine
|
||||
act = QWebPage.WebAction.MoveToStartOfLine
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_end_of_line(self):
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectEndOfLine
|
||||
act = QWebPage.WebAction.SelectEndOfLine
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToEndOfLine
|
||||
act = QWebPage.WebAction.MoveToEndOfLine
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_start_of_next_block(self, count=1):
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = [QWebPage.SelectNextLine,
|
||||
QWebPage.SelectStartOfBlock]
|
||||
act = [QWebPage.WebAction.SelectNextLine,
|
||||
QWebPage.WebAction.SelectStartOfBlock]
|
||||
else:
|
||||
act = [QWebPage.MoveToNextLine,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
act = [QWebPage.WebAction.MoveToNextLine,
|
||||
QWebPage.WebAction.MoveToStartOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
|
|
@ -390,11 +393,11 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
|
||||
def move_to_start_of_prev_block(self, count=1):
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = [QWebPage.SelectPreviousLine,
|
||||
QWebPage.SelectStartOfBlock]
|
||||
act = [QWebPage.WebAction.SelectPreviousLine,
|
||||
QWebPage.WebAction.SelectStartOfBlock]
|
||||
else:
|
||||
act = [QWebPage.MoveToPreviousLine,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
act = [QWebPage.WebAction.MoveToPreviousLine,
|
||||
QWebPage.WebAction.MoveToStartOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
|
|
@ -403,11 +406,11 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
|
||||
def move_to_end_of_next_block(self, count=1):
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = [QWebPage.SelectNextLine,
|
||||
QWebPage.SelectEndOfBlock]
|
||||
act = [QWebPage.WebAction.SelectNextLine,
|
||||
QWebPage.WebAction.SelectEndOfBlock]
|
||||
else:
|
||||
act = [QWebPage.MoveToNextLine,
|
||||
QWebPage.MoveToEndOfBlock]
|
||||
act = [QWebPage.WebAction.MoveToNextLine,
|
||||
QWebPage.WebAction.MoveToEndOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
|
|
@ -416,9 +419,9 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
|
||||
def move_to_end_of_prev_block(self, count=1):
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = [QWebPage.SelectPreviousLine, QWebPage.SelectEndOfBlock]
|
||||
act = [QWebPage.WebAction.SelectPreviousLine, QWebPage.WebAction.SelectEndOfBlock]
|
||||
else:
|
||||
act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
|
||||
act = [QWebPage.WebAction.MoveToPreviousLine, QWebPage.WebAction.MoveToEndOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
|
|
@ -427,18 +430,18 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
|
||||
def move_to_start_of_document(self):
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = QWebPage.SelectStartOfDocument
|
||||
act = QWebPage.WebAction.SelectStartOfDocument
|
||||
else:
|
||||
act = QWebPage.MoveToStartOfDocument
|
||||
act = QWebPage.WebAction.MoveToStartOfDocument
|
||||
self._widget.triggerPageAction(act)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
self._select_line()
|
||||
|
||||
def move_to_end_of_document(self):
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = QWebPage.SelectEndOfDocument
|
||||
act = QWebPage.WebAction.SelectEndOfDocument
|
||||
else:
|
||||
act = QWebPage.MoveToEndOfDocument
|
||||
act = QWebPage.WebAction.MoveToEndOfDocument
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def toggle_selection(self, line=False):
|
||||
|
|
@ -455,7 +458,7 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
self.selection_toggled.emit(self._selection_state)
|
||||
|
||||
def drop_selection(self):
|
||||
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
|
||||
self._widget.triggerPageAction(QWebPage.WebAction.MoveToNextChar)
|
||||
|
||||
def selection(self, callback):
|
||||
callback(self._widget.selectedText())
|
||||
|
|
@ -470,9 +473,9 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
}""")
|
||||
|
||||
def _select_line(self):
|
||||
self._widget.triggerPageAction(QWebPage.SelectStartOfLine)
|
||||
self._widget.triggerPageAction(QWebPage.WebAction.SelectStartOfLine)
|
||||
self.reverse_selection()
|
||||
self._widget.triggerPageAction(QWebPage.SelectEndOfLine)
|
||||
self._widget.triggerPageAction(QWebPage.WebAction.SelectEndOfLine)
|
||||
self.reverse_selection()
|
||||
|
||||
def _select_line_to_end(self):
|
||||
|
|
@ -480,11 +483,11 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
# of focus) has to be checked before moving selection
|
||||
# to the end of line
|
||||
if self._js_selection_left_to_right():
|
||||
self._widget.triggerPageAction(QWebPage.SelectEndOfLine)
|
||||
self._widget.triggerPageAction(QWebPage.WebAction.SelectEndOfLine)
|
||||
|
||||
def _select_line_to_start(self):
|
||||
if not self._js_selection_left_to_right():
|
||||
self._widget.triggerPageAction(QWebPage.SelectStartOfLine)
|
||||
self._widget.triggerPageAction(QWebPage.WebAction.SelectStartOfLine)
|
||||
|
||||
def _js_selection_left_to_right(self):
|
||||
"""Return True iff the selection's direction is left to right."""
|
||||
|
|
@ -497,7 +500,7 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
|
||||
def _follow_selected(self, *, tab=False):
|
||||
if QWebSettings.globalSettings().testAttribute(
|
||||
QWebSettings.JavascriptEnabled):
|
||||
QWebSettings.WebAttribute.JavascriptEnabled):
|
||||
if tab:
|
||||
self._tab.data.override_target = usertypes.ClickTarget.tab
|
||||
self._tab.run_js_async("""
|
||||
|
|
@ -599,7 +602,7 @@ class WebKitScroller(browsertab.AbstractScroller):
|
|||
elif x is None and y == 100:
|
||||
self.bottom()
|
||||
else:
|
||||
for val, orientation in [(x, Qt.Horizontal), (y, Qt.Vertical)]:
|
||||
for val, orientation in [(x, Qt.Orientation.Horizontal), (y, Qt.Orientation.Vertical)]:
|
||||
if val is not None:
|
||||
frame = self._widget.page().mainFrame()
|
||||
maximum = frame.scrollBarMaximum(orientation)
|
||||
|
|
@ -624,36 +627,36 @@ class WebKitScroller(browsertab.AbstractScroller):
|
|||
self._tab.fake_key_press(key)
|
||||
|
||||
def up(self, count=1):
|
||||
self._key_press(Qt.Key_Up, count, 'scrollBarMinimum', Qt.Vertical)
|
||||
self._key_press(Qt.Key.Key_Up, count, 'scrollBarMinimum', Qt.Orientation.Vertical)
|
||||
|
||||
def down(self, count=1):
|
||||
self._key_press(Qt.Key_Down, count, 'scrollBarMaximum', Qt.Vertical)
|
||||
self._key_press(Qt.Key.Key_Down, count, 'scrollBarMaximum', Qt.Orientation.Vertical)
|
||||
|
||||
def left(self, count=1):
|
||||
self._key_press(Qt.Key_Left, count, 'scrollBarMinimum', Qt.Horizontal)
|
||||
self._key_press(Qt.Key.Key_Left, count, 'scrollBarMinimum', Qt.Orientation.Horizontal)
|
||||
|
||||
def right(self, count=1):
|
||||
self._key_press(Qt.Key_Right, count, 'scrollBarMaximum', Qt.Horizontal)
|
||||
self._key_press(Qt.Key.Key_Right, count, 'scrollBarMaximum', Qt.Orientation.Horizontal)
|
||||
|
||||
def top(self):
|
||||
self._key_press(Qt.Key_Home)
|
||||
self._key_press(Qt.Key.Key_Home)
|
||||
|
||||
def bottom(self):
|
||||
self._key_press(Qt.Key_End)
|
||||
self._key_press(Qt.Key.Key_End)
|
||||
|
||||
def page_up(self, count=1):
|
||||
self._key_press(Qt.Key_PageUp, count, 'scrollBarMinimum', Qt.Vertical)
|
||||
self._key_press(Qt.Key.Key_PageUp, count, 'scrollBarMinimum', Qt.Orientation.Vertical)
|
||||
|
||||
def page_down(self, count=1):
|
||||
self._key_press(Qt.Key_PageDown, count, 'scrollBarMaximum',
|
||||
Qt.Vertical)
|
||||
self._key_press(Qt.Key.Key_PageDown, count, 'scrollBarMaximum',
|
||||
Qt.Orientation.Vertical)
|
||||
|
||||
def at_top(self):
|
||||
return self.pos_px().y() == 0
|
||||
|
||||
def at_bottom(self):
|
||||
frame = self._widget.page().currentFrame()
|
||||
return self.pos_px().y() >= frame.scrollBarMaximum(Qt.Vertical)
|
||||
return self.pos_px().y() >= frame.scrollBarMaximum(Qt.Orientation.Vertical)
|
||||
|
||||
|
||||
class WebKitHistoryPrivate(browsertab.AbstractHistoryPrivate):
|
||||
|
|
@ -882,7 +885,7 @@ class WebKitTab(browsertab.AbstractTab):
|
|||
tab=self, parent=self)
|
||||
self.zoom = WebKitZoom(tab=self, parent=self)
|
||||
self.search = WebKitSearch(tab=self, parent=self)
|
||||
self.printing = WebKitPrinting(tab=self)
|
||||
self.printing = WebKitPrinting(tab=self, parent=self)
|
||||
self.elements = WebKitElements(tab=self)
|
||||
self.action = WebKitAction(tab=self)
|
||||
self.audio = WebKitAudio(tab=self, parent=self)
|
||||
|
|
@ -899,7 +902,7 @@ class WebKitTab(browsertab.AbstractTab):
|
|||
|
||||
def _make_private(self, widget):
|
||||
settings = widget.settings()
|
||||
settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
|
||||
settings.setAttribute(QWebSettings.WebAttribute.PrivateBrowsingEnabled, True)
|
||||
|
||||
def load_url(self, url):
|
||||
self._load_url_prepare(url)
|
||||
|
|
@ -931,9 +934,9 @@ class WebKitTab(browsertab.AbstractTab):
|
|||
|
||||
def reload(self, *, force=False):
|
||||
if force:
|
||||
action = QWebPage.ReloadAndBypassCache
|
||||
action = QWebPage.WebAction.ReloadAndBypassCache
|
||||
else:
|
||||
action = QWebPage.Reload
|
||||
action = QWebPage.WebAction.Reload
|
||||
self._widget.triggerPageAction(action)
|
||||
|
||||
def stop(self):
|
||||
|
|
|
|||
|
|
@ -17,17 +17,20 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# FIXME:qt6 (lint)
|
||||
# pylint: disable=no-name-in-module
|
||||
|
||||
"""The main browser widgets."""
|
||||
|
||||
import html
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
from PyQt5.QtWidgets import QFileDialog
|
||||
from PyQt5.QtPrintSupport import QPrintDialog
|
||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
||||
from qutebrowser.qt.core import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
||||
from qutebrowser.qt.gui import QDesktopServices
|
||||
from qutebrowser.qt.network import QNetworkReply, QNetworkRequest
|
||||
from qutebrowser.qt.widgets import QFileDialog
|
||||
from qutebrowser.qt.printsupport import QPrintDialog
|
||||
from qutebrowser.qt.webkitwidgets import QWebPage, QWebFrame
|
||||
|
||||
from qutebrowser.config import websettings, config
|
||||
from qutebrowser.browser import pdfjs, shared, downloads, greasemonkey
|
||||
|
|
@ -67,8 +70,8 @@ class BrowserPage(QWebPage):
|
|||
self._tabdata = tabdata
|
||||
self._is_shutting_down = False
|
||||
self._extension_handlers = {
|
||||
QWebPage.ErrorPageExtension: self._handle_errorpage,
|
||||
QWebPage.ChooseMultipleFilesExtension: self._handle_multiple_files,
|
||||
QWebPage.Extension.ErrorPageExtension: self._handle_errorpage,
|
||||
QWebPage.Extension.ChooseMultipleFilesExtension: self._handle_multiple_files,
|
||||
}
|
||||
self._ignore_load_started = False
|
||||
self.error_occurred = False
|
||||
|
|
@ -134,16 +137,16 @@ class BrowserPage(QWebPage):
|
|||
False if no error page should be displayed, True otherwise.
|
||||
"""
|
||||
ignored_errors = [
|
||||
(QWebPage.QtNetwork, QNetworkReply.OperationCanceledError),
|
||||
(QWebPage.ErrorDomain.QtNetwork, QNetworkReply.NetworkError.OperationCanceledError),
|
||||
# "Loading is handled by the media engine"
|
||||
(QWebPage.WebKit, 203),
|
||||
(QWebPage.ErrorDomain.WebKit, 203),
|
||||
# "Frame load interrupted by policy change"
|
||||
(QWebPage.WebKit, 102),
|
||||
(QWebPage.ErrorDomain.WebKit, 102),
|
||||
]
|
||||
errpage.baseUrl = info.url
|
||||
urlstr = info.url.toDisplayString()
|
||||
if (info.domain, info.error) == (QWebPage.QtNetwork,
|
||||
QNetworkReply.ProtocolUnknownError):
|
||||
if (info.domain, info.error) == (QWebPage.ErrorDomain.QtNetwork,
|
||||
QNetworkReply.NetworkError.ProtocolUnknownError):
|
||||
# For some reason, we get a segfault when we use
|
||||
# QDesktopServices::openUrl with info.url directly - however it
|
||||
# works when we construct a copy of it.
|
||||
|
|
@ -154,7 +157,7 @@ class BrowserPage(QWebPage):
|
|||
text="URL: <b>{}</b>".format(
|
||||
html.escape(url.toDisplayString())),
|
||||
yes_action=functools.partial(QDesktopServices.openUrl, url),
|
||||
url=info.url.toString(QUrl.RemovePassword | QUrl.FullyEncoded))
|
||||
url=info.url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded))
|
||||
return True
|
||||
elif (info.domain, info.error) in ignored_errors:
|
||||
log.webview.debug("Ignored error on {}: {} (error domain: {}, "
|
||||
|
|
@ -251,7 +254,7 @@ class BrowserPage(QWebPage):
|
|||
def on_print_requested(self, frame):
|
||||
"""Handle printing when requested via javascript."""
|
||||
printdiag = QPrintDialog()
|
||||
printdiag.setAttribute(Qt.WA_DeleteOnClose)
|
||||
printdiag.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||
printdiag.open(lambda: frame.print(printdiag.printer()))
|
||||
|
||||
def on_download_requested(self, request):
|
||||
|
|
@ -357,24 +360,24 @@ class BrowserPage(QWebPage):
|
|||
return
|
||||
|
||||
options = {
|
||||
QWebPage.Notifications: 'content.notifications.enabled',
|
||||
QWebPage.Geolocation: 'content.geolocation',
|
||||
QWebPage.Feature.Notifications: 'content.notifications.enabled',
|
||||
QWebPage.Feature.Geolocation: 'content.geolocation',
|
||||
}
|
||||
messages = {
|
||||
QWebPage.Notifications: 'show notifications',
|
||||
QWebPage.Geolocation: 'access your location',
|
||||
QWebPage.Feature.Notifications: 'show notifications',
|
||||
QWebPage.Feature.Geolocation: 'access your location',
|
||||
}
|
||||
yes_action = functools.partial(
|
||||
self.setFeaturePermission, frame, feature,
|
||||
QWebPage.PermissionGrantedByUser)
|
||||
QWebPage.PermissionPolicy.PermissionGrantedByUser)
|
||||
no_action = functools.partial(
|
||||
self.setFeaturePermission, frame, feature,
|
||||
QWebPage.PermissionDeniedByUser)
|
||||
QWebPage.PermissionPolicy.PermissionDeniedByUser)
|
||||
|
||||
url = frame.url().adjusted(QUrl.RemoveUserInfo | # type: ignore[operator]
|
||||
QUrl.RemovePath |
|
||||
QUrl.RemoveQuery |
|
||||
QUrl.RemoveFragment)
|
||||
url = frame.url().adjusted(QUrl.UrlFormattingOption.RemoveUserInfo | # type: ignore[operator]
|
||||
QUrl.UrlFormattingOption.RemovePath |
|
||||
QUrl.UrlFormattingOption.RemoveQuery |
|
||||
QUrl.UrlFormattingOption.RemoveFragment)
|
||||
question = shared.feature_permission(
|
||||
url=url,
|
||||
option=options[feature], msg=messages[feature],
|
||||
|
|
@ -507,17 +510,17 @@ class BrowserPage(QWebPage):
|
|||
and then conditionally opens the URL here or in another tab/window.
|
||||
"""
|
||||
type_map = {
|
||||
QWebPage.NavigationTypeLinkClicked:
|
||||
QWebPage.NavigationType.NavigationTypeLinkClicked:
|
||||
usertypes.NavigationRequest.Type.link_clicked,
|
||||
QWebPage.NavigationTypeFormSubmitted:
|
||||
QWebPage.NavigationType.NavigationTypeFormSubmitted:
|
||||
usertypes.NavigationRequest.Type.form_submitted,
|
||||
QWebPage.NavigationTypeFormResubmitted:
|
||||
QWebPage.NavigationType.NavigationTypeFormResubmitted:
|
||||
usertypes.NavigationRequest.Type.form_resubmitted,
|
||||
QWebPage.NavigationTypeBackOrForward:
|
||||
QWebPage.NavigationType.NavigationTypeBackOrForward:
|
||||
usertypes.NavigationRequest.Type.back_forward,
|
||||
QWebPage.NavigationTypeReload:
|
||||
usertypes.NavigationRequest.Type.reloaded,
|
||||
QWebPage.NavigationTypeOther:
|
||||
QWebPage.NavigationType.NavigationTypeReload:
|
||||
usertypes.NavigationRequest.Type.reload,
|
||||
QWebPage.NavigationType.NavigationTypeOther:
|
||||
usertypes.NavigationRequest.Type.other,
|
||||
}
|
||||
is_main_frame = frame is self.mainFrame()
|
||||
|
|
@ -525,7 +528,7 @@ class BrowserPage(QWebPage):
|
|||
navigation_type=type_map[typ],
|
||||
is_main_frame=is_main_frame)
|
||||
|
||||
if navigation.navigation_type == navigation.Type.reloaded:
|
||||
if navigation.navigation_type == navigation.Type.reload:
|
||||
self.reloading.emit(navigation.url)
|
||||
|
||||
self.navigation_request.emit(navigation)
|
||||
|
|
|
|||
|
|
@ -17,11 +17,14 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# FIXME:qt6 (lint)
|
||||
# pylint: disable=no-name-in-module
|
||||
|
||||
"""The main browser widgets."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, Qt, QUrl
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||
from qutebrowser.qt.core import pyqtSignal, Qt, QUrl
|
||||
from qutebrowser.qt.webkit import QWebSettings
|
||||
from qutebrowser.qt.webkitwidgets import QWebView, QWebPage
|
||||
|
||||
from qutebrowser.config import config, stylesheet
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
|
@ -74,15 +77,15 @@ class WebView(QWebView):
|
|||
tabdata=tab.data, private=private,
|
||||
parent=self)
|
||||
page.setVisibilityState(
|
||||
QWebPage.VisibilityStateVisible if self.isVisible()
|
||||
else QWebPage.VisibilityStateHidden)
|
||||
QWebPage.VisibilityState.VisibilityStateVisible if self.isVisible()
|
||||
else QWebPage.VisibilityState.VisibilityStateHidden)
|
||||
|
||||
self.setPage(page)
|
||||
|
||||
stylesheet.set_register(self)
|
||||
|
||||
def __repr__(self):
|
||||
flags = QUrl.EncodeUnicode
|
||||
flags = QUrl.ComponentFormattingOption.EncodeUnicode
|
||||
urlstr = self.url().toDisplayString(flags) # type: ignore[arg-type]
|
||||
url = utils.elide(urlstr, 100)
|
||||
return utils.get_repr(self, tab_id=self._tab_id, url=url)
|
||||
|
|
@ -107,7 +110,7 @@ class WebView(QWebView):
|
|||
# quitting it seems.
|
||||
log.destroy.debug("Shutting down {!r}.".format(self))
|
||||
settings = self.settings()
|
||||
settings.setAttribute(QWebSettings.JavascriptEnabled, False)
|
||||
settings.setAttribute(QWebSettings.WebAttribute.JavascriptEnabled, False)
|
||||
self.stop()
|
||||
page = self.page()
|
||||
assert isinstance(page, webpage.BrowserPage), page
|
||||
|
|
@ -134,7 +137,7 @@ class WebView(QWebView):
|
|||
"""
|
||||
debug_type = debug.qenum_key(QWebPage, wintype)
|
||||
log.webview.debug("createWindow with type {}".format(debug_type))
|
||||
if wintype == QWebPage.WebModalDialog:
|
||||
if wintype == QWebPage.WebWindowType.WebModalDialog:
|
||||
log.webview.warning("WebModalDialog requested, but we don't "
|
||||
"support that!")
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
|
|
@ -156,12 +159,12 @@ class WebView(QWebView):
|
|||
e: The QPaintEvent.
|
||||
"""
|
||||
frame = self.page().mainFrame()
|
||||
new_pos = (frame.scrollBarValue(Qt.Horizontal),
|
||||
frame.scrollBarValue(Qt.Vertical))
|
||||
new_pos = (frame.scrollBarValue(Qt.Orientation.Horizontal),
|
||||
frame.scrollBarValue(Qt.Orientation.Vertical))
|
||||
if self._old_scroll_pos != new_pos:
|
||||
self._old_scroll_pos = new_pos
|
||||
m = (frame.scrollBarMaximum(Qt.Horizontal),
|
||||
frame.scrollBarMaximum(Qt.Vertical))
|
||||
m = (frame.scrollBarMaximum(Qt.Orientation.Horizontal),
|
||||
frame.scrollBarMaximum(Qt.Orientation.Vertical))
|
||||
perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0,
|
||||
round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0)
|
||||
self.scroll_pos = perc
|
||||
|
|
@ -187,7 +190,7 @@ class WebView(QWebView):
|
|||
e: The QShowEvent.
|
||||
"""
|
||||
super().showEvent(e)
|
||||
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
|
||||
self.page().setVisibilityState(QWebPage.VisibilityState.VisibilityStateVisible)
|
||||
|
||||
def hideEvent(self, e):
|
||||
"""Extend hideEvent to set the page visibility state to hidden.
|
||||
|
|
@ -196,16 +199,16 @@ class WebView(QWebView):
|
|||
e: The QHideEvent.
|
||||
"""
|
||||
super().hideEvent(e)
|
||||
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
|
||||
self.page().setVisibilityState(QWebPage.VisibilityState.VisibilityStateHidden)
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
"""Set the tabdata ClickTarget on a mousepress.
|
||||
|
||||
This is implemented here as we don't need it for QtWebEngine.
|
||||
"""
|
||||
if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
|
||||
if e.button() == Qt.MouseButton.MidButton or e.modifiers() & Qt.KeyboardModifier.ControlModifier:
|
||||
background = config.val.tabs.background
|
||||
if e.modifiers() & Qt.ShiftModifier:
|
||||
if e.modifiers() & Qt.KeyboardModifier.ShiftModifier:
|
||||
background = not background
|
||||
if background:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import argparse
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from qutebrowser.qt.core import QUrl
|
||||
|
||||
from qutebrowser.commands import cmdexc
|
||||
from qutebrowser.utils import utils, objreg, log
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import re
|
|||
import contextlib
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Iterator, Mapping, MutableMapping
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
|
||||
from qutebrowser.qt.core import pyqtSlot, QUrl, QObject
|
||||
|
||||
from qutebrowser.api import cmdutils
|
||||
from qutebrowser.commands import cmdexc, parser
|
||||
|
|
@ -55,9 +55,9 @@ def _init_variable_replacements() -> Mapping[str, _ReplacementFunction]:
|
|||
"""Return a dict from variable replacements to fns processing them."""
|
||||
replacements: Dict[str, _ReplacementFunction] = {
|
||||
'url': lambda tb: _url(tb).toString(
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword),
|
||||
QUrl.ComponentFormattingOption.FullyEncoded | QUrl.UrlFormattingOption.RemovePassword),
|
||||
'url:pretty': lambda tb: _url(tb).toString(
|
||||
QUrl.DecodeReserved | QUrl.RemovePassword),
|
||||
QUrl.ComponentFormattingOption.DecodeReserved | QUrl.UrlFormattingOption.RemovePassword),
|
||||
'url:domain': lambda tb: "{}://{}{}".format(
|
||||
_url(tb).scheme(), _url(tb).host(),
|
||||
":" + str(_url(tb).port()) if _url(tb).port() != -1 else ""),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import os.path
|
|||
import tempfile
|
||||
from typing import cast, Any, MutableMapping, Tuple
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QSocketNotifier
|
||||
from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QSocketNotifier
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.utils import message, log, objreg, standarddir, utils
|
||||
|
|
@ -62,7 +62,7 @@ class _QtFIFOReader(QObject):
|
|||
# pylint: enable=no-member,useless-suppression
|
||||
self._fifo = os.fdopen(fd, 'r')
|
||||
self._notifier = QSocketNotifier(cast(sip.voidptr, fd),
|
||||
QSocketNotifier.Read, self)
|
||||
QSocketNotifier.Type.Read, self)
|
||||
self._notifier.activated.connect(self.read_line)
|
||||
|
||||
@pyqtSlot()
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import dataclasses
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
|
||||
from qutebrowser.qt.core import pyqtSlot, QObject, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import parser, cmdexc
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ We use this to be able to highlight parts of the text.
|
|||
import re
|
||||
import html
|
||||
|
||||
from PyQt5.QtWidgets import QStyle, QStyleOptionViewItem, QStyledItemDelegate
|
||||
from PyQt5.QtCore import QRectF, QRegularExpression, QSize, Qt
|
||||
from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption,
|
||||
from qutebrowser.qt.widgets import QStyle, QStyleOptionViewItem, QStyledItemDelegate
|
||||
from qutebrowser.qt.core import QRectF, QRegularExpression, QSize, Qt
|
||||
from qutebrowser.qt.gui import (QIcon, QPalette, QTextDocument, QTextOption,
|
||||
QAbstractTextDocumentLayout, QSyntaxHighlighter,
|
||||
QTextCharFormat)
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ class _Highlighter(QSyntaxHighlighter):
|
|||
words.sort(key=len, reverse=True)
|
||||
pat = "|".join(re.escape(word) for word in words)
|
||||
self._expression = QRegularExpression(
|
||||
pat, QRegularExpression.CaseInsensitiveOption
|
||||
pat, QRegularExpression.PatternOption.CaseInsensitiveOption
|
||||
)
|
||||
qtutils.ensure_valid(self._expression)
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
"""Draw the background of an ItemViewItem."""
|
||||
assert self._opt is not None
|
||||
assert self._style is not None
|
||||
self._style.drawPrimitive(self._style.PE_PanelItemViewItem, self._opt,
|
||||
self._style.drawPrimitive(QStyle.PrimitiveElement.PE_PanelItemViewItem, self._opt,
|
||||
self._painter, self._opt.widget)
|
||||
|
||||
def _draw_icon(self):
|
||||
|
|
@ -104,18 +104,18 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
assert self._style is not None
|
||||
|
||||
icon_rect = self._style.subElementRect(
|
||||
self._style.SE_ItemViewItemDecoration, self._opt, self._opt.widget)
|
||||
QStyle.SubElement.SE_ItemViewItemDecoration, self._opt, self._opt.widget)
|
||||
if not icon_rect.isValid():
|
||||
# The rect seems to be wrong in all kind of ways if no icon should
|
||||
# be displayed.
|
||||
return
|
||||
|
||||
mode = QIcon.Normal
|
||||
if not self._opt.state & QStyle.State_Enabled:
|
||||
mode = QIcon.Disabled
|
||||
elif self._opt.state & QStyle.State_Selected:
|
||||
mode = QIcon.Selected
|
||||
state = QIcon.On if self._opt.state & QStyle.State_Open else QIcon.Off
|
||||
mode = QIcon.Mode.Normal
|
||||
if not self._opt.state & QStyle.StateFlag.State_Enabled:
|
||||
mode = QIcon.Mode.Disabled
|
||||
elif self._opt.state & QStyle.StateFlag.State_Selected:
|
||||
mode = QIcon.Mode.Selected
|
||||
state = QIcon.State.On if self._opt.state & QStyle.StateFlag.State_Open else QIcon.State.Off
|
||||
self._opt.icon.paint(self._painter, icon_rect,
|
||||
self._opt.decorationAlignment, mode, state)
|
||||
|
||||
|
|
@ -135,9 +135,9 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
return
|
||||
|
||||
text_rect_ = self._style.subElementRect(
|
||||
self._style.SE_ItemViewItemText, self._opt, self._opt.widget)
|
||||
QStyle.SubElement.SE_ItemViewItemText, self._opt, self._opt.widget)
|
||||
qtutils.ensure_valid(text_rect_)
|
||||
margin = self._style.pixelMetric(QStyle.PM_FocusFrameHMargin,
|
||||
margin = self._style.pixelMetric(QStyle.PixelMetric.PM_FocusFrameHMargin,
|
||||
self._opt, self._opt.widget) + 1
|
||||
# remove width padding
|
||||
text_rect = text_rect_.adjusted(margin, 0, -margin, 0)
|
||||
|
|
@ -149,24 +149,24 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
text_rect.adjust(0, -2, 0, -2)
|
||||
self._painter.save()
|
||||
state = self._opt.state
|
||||
if state & QStyle.State_Enabled and state & QStyle.State_Active:
|
||||
cg = QPalette.Normal
|
||||
elif state & QStyle.State_Enabled:
|
||||
cg = QPalette.Inactive
|
||||
if state & QStyle.StateFlag.State_Enabled and state & QStyle.StateFlag.State_Active:
|
||||
cg = QPalette.ColorGroup.Normal
|
||||
elif state & QStyle.StateFlag.State_Enabled:
|
||||
cg = QPalette.ColorGroup.Inactive
|
||||
else:
|
||||
cg = QPalette.Disabled
|
||||
cg = QPalette.ColorGroup.Disabled
|
||||
|
||||
if state & QStyle.State_Selected:
|
||||
if state & QStyle.StateFlag.State_Selected:
|
||||
self._painter.setPen(self._opt.palette.color(
|
||||
cg, QPalette.HighlightedText))
|
||||
cg, QPalette.ColorRole.HighlightedText))
|
||||
# This is a dirty fix for the text jumping by one pixel for
|
||||
# whatever reason.
|
||||
text_rect.adjust(0, -1, 0, 0)
|
||||
else:
|
||||
self._painter.setPen(self._opt.palette.color(cg, QPalette.Text))
|
||||
self._painter.setPen(self._opt.palette.color(cg, QPalette.ColorRole.Text))
|
||||
|
||||
if state & QStyle.State_Editing:
|
||||
self._painter.setPen(self._opt.palette.color(cg, QPalette.Text))
|
||||
if state & QStyle.StateFlag.State_Editing:
|
||||
self._painter.setPen(self._opt.palette.color(cg, QPalette.ColorRole.Text))
|
||||
self._painter.drawRect(text_rect_.adjusted(0, 0, -1, -1))
|
||||
|
||||
self._painter.translate(text_rect.left(), text_rect.top())
|
||||
|
|
@ -188,9 +188,9 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
clip = QRectF(0, 0, rect.width(), rect.height())
|
||||
self._painter.save()
|
||||
|
||||
if self._opt.state & QStyle.State_Selected:
|
||||
if self._opt.state & QStyle.StateFlag.State_Selected:
|
||||
color = config.cache['colors.completion.item.selected.fg']
|
||||
elif not self._opt.state & QStyle.State_Enabled:
|
||||
elif not self._opt.state & QStyle.StateFlag.State_Enabled:
|
||||
color = config.cache['colors.completion.category.fg']
|
||||
else:
|
||||
colors = config.cache['colors.completion.fg']
|
||||
|
|
@ -199,7 +199,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
self._painter.setPen(color)
|
||||
|
||||
ctx = QAbstractTextDocumentLayout.PaintContext()
|
||||
ctx.palette.setColor(QPalette.Text, self._painter.pen().color())
|
||||
ctx.palette.setColor(QPalette.ColorRole.Text, self._painter.pen().color())
|
||||
if clip.isValid():
|
||||
self._painter.setClipRect(clip)
|
||||
ctx.clip = clip
|
||||
|
|
@ -217,10 +217,10 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
# qcommonstyle.cpp:viewItemDrawText
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/118
|
||||
text_option = QTextOption()
|
||||
if self._opt.features & QStyleOptionViewItem.WrapText:
|
||||
text_option.setWrapMode(QTextOption.WordWrap)
|
||||
if self._opt.features & QStyleOptionViewItem.ViewItemFeature.WrapText:
|
||||
text_option.setWrapMode(QTextOption.WrapMode.WordWrap)
|
||||
else:
|
||||
text_option.setWrapMode(QTextOption.ManualWrap)
|
||||
text_option.setWrapMode(QTextOption.WrapMode.ManualWrap)
|
||||
text_option.setTextDirection(self._opt.direction)
|
||||
text_option.setAlignment(QStyle.visualAlignment(
|
||||
self._opt.direction, self._opt.displayAlignment))
|
||||
|
|
@ -238,7 +238,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
pattern = view.pattern
|
||||
columns_to_filter = index.model().columns_to_filter(index)
|
||||
if index.column() in columns_to_filter and pattern:
|
||||
if self._opt.state & QStyle.State_Selected:
|
||||
if self._opt.state & QStyle.StateFlag.State_Selected:
|
||||
color = config.val.colors.completion.item.selected.match.fg
|
||||
else:
|
||||
color = config.val.colors.completion.match.fg
|
||||
|
|
@ -255,23 +255,23 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
assert self._opt is not None
|
||||
assert self._style is not None
|
||||
state = self._opt.state
|
||||
if not state & QStyle.State_HasFocus:
|
||||
if not state & QStyle.StateFlag.State_HasFocus:
|
||||
return
|
||||
o = self._opt
|
||||
o.rect = self._style.subElementRect(
|
||||
self._style.SE_ItemViewItemFocusRect, self._opt, self._opt.widget)
|
||||
o.state |= int(QStyle.State_KeyboardFocusChange | QStyle.State_Item)
|
||||
QStyle.SubElement.SE_ItemViewItemFocusRect, self._opt, self._opt.widget)
|
||||
o.state |= QStyle.StateFlag.State_KeyboardFocusChange | QStyle.StateFlag.State_Item
|
||||
qtutils.ensure_valid(o.rect)
|
||||
if state & QStyle.State_Enabled:
|
||||
cg = QPalette.Normal
|
||||
if state & QStyle.StateFlag.State_Enabled:
|
||||
cg = QPalette.ColorGroup.Normal
|
||||
else:
|
||||
cg = QPalette.Disabled
|
||||
if state & QStyle.State_Selected:
|
||||
role = QPalette.Highlight
|
||||
cg = QPalette.ColorGroup.Disabled
|
||||
if state & QStyle.StateFlag.State_Selected:
|
||||
role = QPalette.ColorRole.Highlight
|
||||
else:
|
||||
role = QPalette.Window
|
||||
role = QPalette.ColorRole.Window
|
||||
o.backgroundColor = self._opt.palette.color(cg, role)
|
||||
self._style.drawPrimitive(QStyle.PE_FrameFocusRect, o, self._painter,
|
||||
self._style.drawPrimitive(QStyle.PrimitiveElement.PE_FrameFocusRect, o, self._painter,
|
||||
self._opt.widget)
|
||||
|
||||
def sizeHint(self, option, index):
|
||||
|
|
@ -287,7 +287,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
Return:
|
||||
A QSize with the recommended size.
|
||||
"""
|
||||
value = index.data(Qt.SizeHintRole)
|
||||
value = index.data(Qt.ItemDataRole.SizeHintRole)
|
||||
if value is not None:
|
||||
return value
|
||||
self._opt = QStyleOptionViewItem(option)
|
||||
|
|
@ -298,7 +298,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
assert self._doc is not None
|
||||
|
||||
docsize = self._doc.size().toSize()
|
||||
size = self._style.sizeFromContents(QStyle.CT_ItemViewItem, self._opt,
|
||||
size = self._style.sizeFromContents(QStyle.ContentsType.CT_ItemViewItem, self._opt,
|
||||
docsize, self._opt.widget)
|
||||
qtutils.ensure_valid(size)
|
||||
return size + QSize(10, 3)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ subclasses to provide completions.
|
|||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from PyQt5.QtWidgets import QTreeView, QSizePolicy, QStyleFactory, QWidget
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
|
||||
from qutebrowser.qt.widgets import QTreeView, QSizePolicy, QStyleFactory, QWidget
|
||||
from qutebrowser.qt.core import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
|
||||
|
||||
from qutebrowser.config import config, stylesheet
|
||||
from qutebrowser.completion import completiondelegate
|
||||
|
|
@ -127,14 +127,14 @@ class CompletionView(QTreeView):
|
|||
self.setItemDelegate(self._delegate)
|
||||
self.setStyle(QStyleFactory.create('Fusion'))
|
||||
stylesheet.set_register(self)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.setHeaderHidden(True)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setIndentation(0)
|
||||
self.setItemsExpandable(False)
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
self.setAnimated(False)
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
# WORKAROUND
|
||||
# This is a workaround for weird race conditions with invalid
|
||||
# item indexes leading to segfaults in Qt.
|
||||
|
|
@ -277,18 +277,20 @@ class CompletionView(QTreeView):
|
|||
direction = -1 if upwards else 1
|
||||
while True:
|
||||
idx = idx.sibling(idx.row() + direction, 0)
|
||||
if not idx.isValid() and upwards:
|
||||
|
||||
if idx.isValid():
|
||||
child = model.index(0, 0, idx)
|
||||
if child.isValid():
|
||||
self.scrollTo(idx) # scroll to ensure the category is visible
|
||||
return child
|
||||
elif upwards:
|
||||
# wrap around to the first item of the last category
|
||||
return model.last_item().sibling(0, 0)
|
||||
elif not idx.isValid() and not upwards:
|
||||
else:
|
||||
# wrap around to the first item of the first category
|
||||
idx = model.first_item()
|
||||
self.scrollTo(idx.parent())
|
||||
return idx
|
||||
elif idx.isValid() and idx.child(0, 0).isValid():
|
||||
# scroll to ensure the category is visible
|
||||
self.scrollTo(idx)
|
||||
return idx.child(0, 0)
|
||||
|
||||
raise utils.Unreachable
|
||||
|
||||
|
|
@ -339,8 +341,8 @@ class CompletionView(QTreeView):
|
|||
|
||||
selmodel.setCurrentIndex(
|
||||
idx,
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Rows)
|
||||
QItemSelectionModel.SelectionFlag.ClearAndSelect |
|
||||
QItemSelectionModel.SelectionFlag.Rows)
|
||||
|
||||
# if the last item is focused, try to fetch more
|
||||
next_idx = self.indexBelow(idx)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from typing import MutableSequence
|
||||
|
||||
from PyQt5.QtCore import Qt, QModelIndex, QAbstractItemModel
|
||||
from qutebrowser.qt.core import Qt, QModelIndex, QAbstractItemModel
|
||||
|
||||
from qutebrowser.utils import log, qtutils, utils
|
||||
from qutebrowser.api import cmdutils
|
||||
|
|
@ -63,7 +63,7 @@ class CompletionModel(QAbstractItemModel):
|
|||
"""Add a completion category to the model."""
|
||||
self._categories.append(cat)
|
||||
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
|
||||
"""Return the item data for index.
|
||||
|
||||
Override QAbstractItemModel::data.
|
||||
|
|
@ -74,7 +74,7 @@ class CompletionModel(QAbstractItemModel):
|
|||
|
||||
Return: The item data, or None on an invalid index.
|
||||
"""
|
||||
if role != Qt.DisplayRole:
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return None
|
||||
cat = self._cat_from_idx(index)
|
||||
if cat:
|
||||
|
|
@ -94,17 +94,17 @@ class CompletionModel(QAbstractItemModel):
|
|||
|
||||
Override QAbstractItemModel::flags.
|
||||
|
||||
Return: The item flags, or Qt.NoItemFlags on error.
|
||||
Return: The item flags, or Qt.ItemFlag.NoItemFlags on error.
|
||||
"""
|
||||
if not index.isValid():
|
||||
return Qt.NoItemFlags
|
||||
return Qt.ItemFlag.NoItemFlags
|
||||
if index.parent().isValid():
|
||||
# item
|
||||
return (Qt.ItemIsEnabled | Qt.ItemIsSelectable |
|
||||
Qt.ItemNeverHasChildren)
|
||||
return (Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable |
|
||||
Qt.ItemFlag.ItemNeverHasChildren)
|
||||
else:
|
||||
# category
|
||||
return Qt.NoItemFlags
|
||||
return Qt.ItemFlag.NoItemFlags
|
||||
|
||||
def index(self, row, col, parent=QModelIndex()):
|
||||
"""Get an index into the model.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import os
|
|||
import os.path
|
||||
from typing import List, Optional, Iterable
|
||||
|
||||
from PyQt5.QtCore import QAbstractListModel, QModelIndex, QObject, Qt, QUrl
|
||||
from qutebrowser.qt.core import QAbstractListModel, QModelIndex, QObject, Qt, QUrl
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log
|
||||
|
|
@ -100,9 +100,9 @@ class FilePathCategory(QAbstractListModel):
|
|||
paths = self._glob(expanded)
|
||||
self._paths = sorted(self._contract_user(val, path) for path in paths)
|
||||
|
||||
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Optional[str]:
|
||||
def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Optional[str]:
|
||||
"""Implement abstract method in QAbstractListModel."""
|
||||
if role == Qt.DisplayRole and index.column() == 0:
|
||||
if role == Qt.ItemDataRole.DisplayRole and index.column() == 0:
|
||||
return self._paths[index.row()]
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtSql import QSqlQueryModel
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from qutebrowser.qt.sql import QSqlQueryModel
|
||||
from qutebrowser.qt.widgets import QWidget
|
||||
|
||||
from qutebrowser.misc import sql
|
||||
from qutebrowser.utils import debug, message, log
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
import re
|
||||
from typing import Iterable, Tuple
|
||||
|
||||
from PyQt5.QtCore import QSortFilterProxyModel, QRegularExpression
|
||||
from PyQt5.QtGui import QStandardItem, QStandardItemModel
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from qutebrowser.qt.core import QSortFilterProxyModel, QRegularExpression
|
||||
from qutebrowser.qt.gui import QStandardItem, QStandardItemModel
|
||||
from qutebrowser.qt.widgets import QWidget
|
||||
|
||||
from qutebrowser.completion.models import util
|
||||
from qutebrowser.utils import qtutils, log
|
||||
|
|
@ -66,7 +66,7 @@ class ListCategory(QSortFilterProxyModel):
|
|||
val = re.sub(r' +', r' ', val) # See #1919
|
||||
val = re.escape(val)
|
||||
val = val.replace(r'\ ', '.*')
|
||||
rx = QRegularExpression(val, QRegularExpression.CaseInsensitiveOption)
|
||||
rx = QRegularExpression(val, QRegularExpression.PatternOption.CaseInsensitiveOption)
|
||||
qtutils.ensure_valid(rx)
|
||||
self.setFilterRegularExpression(rx)
|
||||
self.invalidate()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from typing import Dict, Sequence
|
||||
|
||||
from PyQt5.QtCore import QAbstractItemModel
|
||||
from qutebrowser.qt.core import QAbstractItemModel
|
||||
|
||||
from qutebrowser.completion.models import (completionmodel, filepathcategory,
|
||||
listcategory, histcategory)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import contextlib
|
|||
import subprocess
|
||||
from typing import Optional, IO, Iterator
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from qutebrowser.qt.core import QUrl
|
||||
|
||||
from qutebrowser.api import (
|
||||
hook,
|
||||
|
|
@ -109,6 +109,7 @@ _RESOURCE_TYPE_STRINGS = {
|
|||
ResourceType.plugin_resource: "other",
|
||||
ResourceType.preload_main_frame: "other",
|
||||
ResourceType.preload_sub_frame: "other",
|
||||
ResourceType.websocket: "websocket",
|
||||
ResourceType.unknown: "other",
|
||||
None: "",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import logging
|
|||
import pathlib
|
||||
from typing import cast, IO, Set
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from qutebrowser.qt.core import QUrl
|
||||
|
||||
from qutebrowser.api import (
|
||||
hook,
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ try:
|
|||
except ImportError:
|
||||
hunter = None
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||
from qutebrowser.qt.core import Qt
|
||||
from qutebrowser.qt.printsupport import QPrintPreviewDialog
|
||||
|
||||
from qutebrowser.api import cmdutils, apitypes, message, config
|
||||
|
||||
|
|
@ -81,11 +81,11 @@ def _print_preview(tab: apitypes.Tab) -> None:
|
|||
|
||||
tab.printing.check_preview_support()
|
||||
diag = QPrintPreviewDialog(tab)
|
||||
diag.setAttribute(Qt.WA_DeleteOnClose)
|
||||
diag.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||
diag.setWindowFlags(
|
||||
diag.windowFlags() |
|
||||
Qt.WindowMaximizeButtonHint |
|
||||
Qt.WindowMinimizeButtonHint)
|
||||
Qt.WindowType.WindowMaximizeButtonHint |
|
||||
Qt.WindowType.WindowMinimizeButtonHint)
|
||||
diag.paintRequested.connect(functools.partial(
|
||||
tab.printing.to_printer, callback=print_callback))
|
||||
diag.exec()
|
||||
|
|
@ -324,7 +324,7 @@ def debug_webaction(tab: apitypes.Tab, action: str, count: int = 1) -> None:
|
|||
|
||||
Available actions:
|
||||
https://doc.qt.io/archives/qt-5.5/qwebpage.html#WebAction-enum (WebKit)
|
||||
https://doc.qt.io/qt-5/qwebenginepage.html#WebAction-enum (WebEngine)
|
||||
https://doc.qt.io/qt-6/qwebenginepage.html#WebAction-enum (WebEngine)
|
||||
|
||||
Args:
|
||||
action: The action to execute, e.g. MoveToNextChar.
|
||||
|
|
@ -365,7 +365,7 @@ def message_error(text: str, rich: bool = False) -> None:
|
|||
Args:
|
||||
text: The text to show.
|
||||
rich: Render the given text as
|
||||
https://doc.qt.io/qt-5/richtext-html-subset.html[Qt Rich Text].
|
||||
https://doc.qt.io/qt-6/richtext-html-subset.html[Qt Rich Text].
|
||||
"""
|
||||
message.error(text, rich=rich)
|
||||
|
||||
|
|
@ -379,7 +379,7 @@ def message_info(text: str, count: int = 1, rich: bool = False) -> None:
|
|||
text: The text to show.
|
||||
count: How many times to show the message.
|
||||
rich: Render the given text as
|
||||
https://doc.qt.io/qt-5/richtext-html-subset.html[Qt Rich Text].
|
||||
https://doc.qt.io/qt-6/richtext-html-subset.html[Qt Rich Text].
|
||||
"""
|
||||
for _ in range(count):
|
||||
message.info(text, rich=rich)
|
||||
|
|
@ -392,7 +392,7 @@ def message_warning(text: str, rich: bool = False) -> None:
|
|||
Args:
|
||||
text: The text to show.
|
||||
rich: Render the given text as
|
||||
https://doc.qt.io/qt-5/richtext-html-subset.html[Qt Rich Text].
|
||||
https://doc.qt.io/qt-6/richtext-html-subset.html[Qt Rich Text].
|
||||
"""
|
||||
message.warning(text, rich=rich)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import os
|
||||
from typing import Iterable, Optional, MutableMapping, Any, Callable
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QLineEdit
|
||||
from qutebrowser.qt.widgets import QApplication, QLineEdit
|
||||
|
||||
from qutebrowser.api import cmdutils
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import os
|
|||
import functools
|
||||
from typing import IO, List, Optional
|
||||
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtSignal
|
||||
from qutebrowser.qt.core import QUrl, QObject, pyqtSignal
|
||||
|
||||
from qutebrowser.api import downloads, message, config
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import functools
|
|||
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Mapping,
|
||||
MutableMapping, MutableSequence, Optional, Tuple, cast)
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
from qutebrowser.qt.core import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.commands import cmdexc, parser
|
||||
from qutebrowser.config import configdata, configexc, configutils
|
||||
|
|
@ -560,15 +560,18 @@ class Config(QObject):
|
|||
log.config.debug("{} was mutated, updating".format(name))
|
||||
self.set_obj(name, new_value, save_yaml=save_yaml)
|
||||
|
||||
def dump_userconfig(self) -> str:
|
||||
def dump_userconfig(self, *, include_hidden: bool = False) -> str:
|
||||
"""Get the part of the config which was changed by the user.
|
||||
|
||||
Args:
|
||||
include_hidden: Include default scoped configs.
|
||||
|
||||
Return:
|
||||
The changed config part as string.
|
||||
"""
|
||||
lines: List[str] = []
|
||||
for values in sorted(self, key=lambda v: v.opt.name):
|
||||
lines += values.dump()
|
||||
lines += values.dump(include_hidden=include_hidden)
|
||||
|
||||
if not lines:
|
||||
return '<Default configuration>'
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue