Merge branch 'master' into more-sophisticated-adblock
This commit is contained in:
commit
4c5e3d9ea0
|
|
@ -1,5 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 1.13.1
|
||||
current_version = 1.14.0
|
||||
commit = True
|
||||
message = Release v{new_version}
|
||||
tag = True
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ exclude_lines =
|
|||
raise utils\.Unreachable
|
||||
if __name__ == ["']__main__["']:
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
\.\.\.
|
||||
|
||||
[xml]
|
||||
|
|
|
|||
4
.flake8
4
.flake8
|
|
@ -39,6 +39,7 @@ exclude = .*,__pycache__,resources.py
|
|||
# A003: Builtin name for class attribute (needed for overridden methods)
|
||||
# W503: like break before binary operator
|
||||
# W504: line break after binary operator
|
||||
# FI15: __future__ import "generator_stop" missing
|
||||
ignore =
|
||||
B001,B008,B305,
|
||||
E128,E226,E265,E501,E402,E266,E722,E731,
|
||||
|
|
@ -48,7 +49,8 @@ ignore =
|
|||
D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D412,D413,
|
||||
A003,
|
||||
W503, W504
|
||||
min-version = 3.4.0
|
||||
FI15
|
||||
min-version = 3.6.0
|
||||
max-complexity = 12
|
||||
per-file-ignores =
|
||||
qutebrowser/api/hook.py : N801
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
|
@ -39,10 +39,10 @@ jobs:
|
|||
.tox
|
||||
~/.cache/pip
|
||||
key: "${{ matrix.testenv }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}"
|
||||
- uses: actions/setup-python@v2.1.2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.8'
|
||||
- uses: actions/setup-node@v2.1.1
|
||||
- uses: actions/setup-node@v2-beta
|
||||
with:
|
||||
node-version: '12.x'
|
||||
if: "matrix.testenv == 'eslint'"
|
||||
|
|
@ -57,7 +57,7 @@ jobs:
|
|||
bindir="$HOME/.local/bin"
|
||||
mkdir -p "$bindir"
|
||||
wget -qO- "https://github.com/koalaman/shellcheck/releases/download/$scversion/shellcheck-$scversion.linux.x86_64.tar.xz" | tar -xJv --strip-components 1 -C "$bindir" shellcheck-$scversion/shellcheck
|
||||
echo "::add-path::$bindir"
|
||||
echo "$bindir" >> "$GITHUB_PATH"
|
||||
fi
|
||||
python -m pip install -U pip
|
||||
python -m pip install -U -r misc/requirements/requirements-tox.txt
|
||||
|
|
@ -100,11 +100,6 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
### PyQt 5.7.1 (Python 3.5)
|
||||
# - testenv: py35-pyqt57
|
||||
# os: ubuntu-16.04
|
||||
# python: 3.5
|
||||
# experimental: true
|
||||
### PyQt 5.9 (Python 3.6)
|
||||
- testenv: py36-pyqt59
|
||||
os: ubuntu-18.04
|
||||
|
|
@ -132,7 +127,7 @@ jobs:
|
|||
### PyQt 5.15 (Python 3.9)
|
||||
- testenv: py39-pyqt515
|
||||
os: ubuntu-20.04
|
||||
python: 3.9-dev
|
||||
python: 3.9
|
||||
### PyQt 5.15 (Python 3.8, with coverage)
|
||||
- testenv: py38-pyqt515-cov
|
||||
os: ubuntu-20.04
|
||||
|
|
@ -157,7 +152,7 @@ jobs:
|
|||
~/.cache/pip
|
||||
key: "${{ matrix.testenv }}-${{ matrix.os }}-${{ matrix.python }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}"
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2.1.2
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "${{ matrix.python }}"
|
||||
- name: Set up problem matchers
|
||||
|
|
@ -178,7 +173,7 @@ jobs:
|
|||
if: "failure()"
|
||||
- name: Upload coverage
|
||||
if: "endsWith(matrix.testenv, '-cov')"
|
||||
uses: codecov/codecov-action@v1.0.13
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
name: "${{ matrix.testenv }}"
|
||||
|
||||
|
|
@ -212,7 +207,7 @@ jobs:
|
|||
if: "always() && github.repository_owner == 'qutebrowser'"
|
||||
steps:
|
||||
- name: Send success IRC notification
|
||||
uses: Gottox/irc-message-action@v1
|
||||
uses: Gottox/irc-message-action@v1.1
|
||||
if: "needs.linters.result == 'success' && needs.tests.result == 'success' && needs.tests-docker.result == 'success' && needs.codeql.result == 'success'"
|
||||
with:
|
||||
server: chat.freenode.net
|
||||
|
|
@ -220,7 +215,7 @@ jobs:
|
|||
nickname: qutebrowser-bot
|
||||
message: "[${{ github.workflow }}] \u00033Success:\u0003 ${{ github.ref }} https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} (@${{ github.actor }})"
|
||||
- name: Send failure IRC notification
|
||||
uses: Gottox/irc-message-action@v1
|
||||
uses: Gottox/irc-message-action@v1.1
|
||||
if: "needs.linters.result == 'failure' || needs.tests.result == 'failure' || needs.tests-docker.result == 'failure' || needs.codeql.result == 'failure'"
|
||||
with:
|
||||
server: chat.freenode.net
|
||||
|
|
@ -229,7 +224,7 @@ jobs:
|
|||
message: "[${{ github.workflow }}] \u00034FAIL:\u0003 ${{ github.ref }} https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} (@${{ github.actor }})\n
|
||||
linters: ${{ needs.linters.result }}, tests: ${{ needs.tests.result }}, tests-docker: ${{ needs.tests-docker.result }}, codeql: ${{ needs.codeql.result }}"
|
||||
- name: Send skipped IRC notification
|
||||
uses: Gottox/irc-message-action@v1
|
||||
uses: Gottox/irc-message-action@v1.1
|
||||
if: "needs.linters.result == 'skipped' || needs.tests.result == 'skipped' || needs.tests-docker.result == 'skipped' || needs.codeql.result == 'skipped'"
|
||||
with:
|
||||
server: chat.freenode.net
|
||||
|
|
@ -237,7 +232,7 @@ jobs:
|
|||
nickname: qutebrowser-bot
|
||||
message: "[${{ github.workflow }}] \u00038Skipped:\u0003 ${{ github.ref }} https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} (@${{ github.actor }})"
|
||||
- name: Send cancelled IRC notification
|
||||
uses: Gottox/irc-message-action@v1
|
||||
uses: Gottox/irc-message-action@v1.1
|
||||
if: "needs.linters.result == 'cancelled' || needs.tests.result == 'cancelled' || needs.tests-docker.result == 'cancelled' || needs.codeql.result == 'cancelled'"
|
||||
with:
|
||||
server: chat.freenode.net
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v2.1.2
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2.1.2
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.8'
|
||||
- name: Recompile requirements
|
||||
|
|
@ -60,7 +60,7 @@ jobs:
|
|||
if: "always() && github.repository == 'qutebrowser/qutebrowser'"
|
||||
steps:
|
||||
- name: Send success IRC notification
|
||||
uses: Gottox/irc-message-action@v1
|
||||
uses: Gottox/irc-message-action@v1.1
|
||||
if: "needs.update.result == 'success'"
|
||||
with:
|
||||
server: chat.freenode.net
|
||||
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
nickname: qutebrowser-bot
|
||||
message: "[${{ github.workflow }}] \u00033Success:\u0003 ${{ github.ref }} https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} (@${{ github.actor }})"
|
||||
- name: Send non-success IRC notification
|
||||
uses: Gottox/irc-message-action@v1
|
||||
uses: Gottox/irc-message-action@v1.1
|
||||
if: "needs.update.result != 'success'"
|
||||
with:
|
||||
server: chat.freenode.net
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
__pycache__
|
||||
*.py~
|
||||
*.pyc
|
||||
*.swp
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
[._]*.un~
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
*~
|
||||
/build
|
||||
/dist
|
||||
/qutebrowser.egg-info
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
[mypy]
|
||||
# We also need to support 3.5, but if we'd chose that here, we'd need to deal
|
||||
# with conditional imports (like secrets.py).
|
||||
python_version = 3.6
|
||||
|
||||
# --strict
|
||||
|
|
@ -115,6 +113,9 @@ disallow_untyped_defs = True
|
|||
[mypy-qutebrowser.browser.webengine.webengineelem]
|
||||
disallow_untyped_defs = True
|
||||
|
||||
[mypy-qutebrowser.browser.webengine.darkmode]
|
||||
disallow_untyped_defs = True
|
||||
|
||||
[mypy-qutebrowser.keyinput.*]
|
||||
disallow_untyped_defs = True
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
dist: xenial
|
||||
language: python
|
||||
python: 3.5
|
||||
python: 3.6
|
||||
os: linux
|
||||
env: TESTENV=py35-pyqt57
|
||||
env: TESTENV=py36-pyqt57
|
||||
|
||||
install:
|
||||
- python -m pip install -U pip
|
||||
|
|
|
|||
|
|
@ -109,8 +109,7 @@ Requirements
|
|||
|
||||
The following software and libraries are required to run qutebrowser:
|
||||
|
||||
* https://www.python.org/[Python] 3.5.2 or newer (3.6 - 3.8 recommended;
|
||||
support for 3.5 will be dropped with qutebrowser v2.0.0)
|
||||
* https://www.python.org/[Python] 3.6 or newer
|
||||
* https://www.qt.io/[Qt] 5.7.1 or newer (5.14 recommended; support for < 5.11
|
||||
will be dropped with qutebrowser v2.0.0) with the following modules:
|
||||
- QtCore / qtbase
|
||||
|
|
@ -219,11 +218,11 @@ Active
|
|||
|
||||
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
|
||||
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
|
||||
* https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
|
||||
* https://nyxt.atlas.engineer/[Nyxt browser] (formerly "Next browser", Lisp, Emacs-like but also offers Vim bindings, QtWebKit or GTK+/WebKit2 - note there was a http://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly)
|
||||
* https://vieb.dev/[Vieb] (JavaScript, Electron)
|
||||
* Chrome/Chromium addons:
|
||||
https://vimium.github.io/[Vimium],
|
||||
https://github.com/dcchambers/vb4c[vb4c] (fork of cVim)
|
||||
* Firefox addons (based on WebExtensions):
|
||||
https://github.com/tridactyl/tridactyl[Tridactyl],
|
||||
https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental),
|
||||
|
|
@ -231,7 +230,6 @@ Active
|
|||
https://github.com/amedama41/vvimpulation[VVimpulation]
|
||||
* Addons for Firefox and Chrome:
|
||||
https://github.com/brookhong/Surfingkeys[Surfingkeys],
|
||||
https://github.com/lusakasa/saka-key[Saka Key],
|
||||
https://krabby.netlify.com/[Krabby],
|
||||
https://lydell.github.io/LinkHints/[Link Hints] (hinting only)
|
||||
* Addons for Safari:
|
||||
|
|
@ -253,6 +251,7 @@ original site is gone but the Arch Linux wiki has some data)
|
|||
* https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
|
||||
* https://github.com/conformal/xombrero[xombrero] (C, GTK+ with WebKit1)
|
||||
* https://github.com/linkdd/cream-browser[Cream Browser] (C, GTK+ with WebKit1)
|
||||
* https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
|
||||
* Firefox addons (not based on WebExtensions or no recent activity):
|
||||
http://www.vimperator.org/[Vimperator],
|
||||
http://bug.5digits.org/pentadactyl/index[Pentadactyl],
|
||||
|
|
@ -262,7 +261,7 @@ original site is gone but the Arch Linux wiki has some data)
|
|||
* Chrome/Chromium addons:
|
||||
https://github.com/k2nr/ViChrome/[ViChrome],
|
||||
https://github.com/jinzhu/vrome[Vrome],
|
||||
https://github.com/lusakasa/saka-key[Saka Key],
|
||||
https://github.com/lusakasa/saka-key[Saka Key] (https://github.com/lusakasa/saka-key/issues/171[unmaintained]),
|
||||
https://github.com/1995eaton/chromium-vim[cVim],
|
||||
https://glee.github.io/[GleeBox]
|
||||
|
||||
|
|
|
|||
|
|
@ -15,15 +15,54 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
|||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.14.0 (unreleased)
|
||||
v2.0.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
Major changes
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
- At least Python 3.6 is now required to run qutebrowser, support for Python
|
||||
3.5 is dropped. Note that Python 3.5 is
|
||||
https://www.python.org/downloads/release/python-3510/[no longer supported
|
||||
upstream] since September 2020.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- `config.py` files now are required to have either
|
||||
`config.load_autoconfig(False)` (don't load `autoconfig.yml`) or
|
||||
`config.load_autoconfig()` (do load `autoconfig.yml`) in them.
|
||||
- The `colors.webpage.darkmode.*` settings are now also supported with older Qt
|
||||
versions (Qt 5.10 to 5.13) rather than just with Qt 5.14 and above.
|
||||
- For regexes in the config (`hints.{prev,next}_regexes`), certain patterns
|
||||
which will change meanings in future Python versions are now disallowed. This is
|
||||
the case for character sets starting with a literal `[` or containing literal
|
||||
character sequences `--`, `&&`, `~~`, or `||`. To avoid a warning, remove the
|
||||
duplicate characters or escape them with a backslash.
|
||||
|
||||
v1.14.0 (2020-10-15)
|
||||
--------------------
|
||||
|
||||
Note: The QtWebEngine version bundled with the Windows/macOS
|
||||
releases is still based on Qt 5.15.0 (like with qutebrowser v1.12.0 and
|
||||
v1.13.0) rather than Qt 5.15.1 because of a
|
||||
https://bugreports.qt.io/browse/QTBUG-86752[Qt bug] causing
|
||||
frequent renderer process crashes. When Qt 5.15.2 is released
|
||||
(planned for November 3rd, 2020), a qutebrowser v1.14.x patch
|
||||
release with an updated QtWebEngine will be released.
|
||||
|
||||
Furthermore, this release still only contains partial session support for QtWebEngine
|
||||
5.15. It's still recommended to run against Qt 5.15 due to the security patches
|
||||
contained in it -- for most users, the added workarounds seem to work out fine. A
|
||||
rewritten session support will be part of qutebrowser v2.0.0, tentatively planned for the
|
||||
end of the year or early 2021.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- The `content.media_capture` setting got split up into three more fine-grained
|
||||
settings, `content.media.audio_capture`, `.video_capture` and
|
||||
`.audio_video_capture`. Before this change, anwering "always" to a prompt
|
||||
`.audio_video_capture`. Before this change, answering "always" to a prompt
|
||||
about e.g. audio capturing would set the `content.media_capture` setting,
|
||||
which would also allow the same website to capture video on a future visit.
|
||||
Now every prompt will set the appropriate setting, though existing
|
||||
|
|
@ -44,8 +83,6 @@ Changed
|
|||
- `:back` and `:forward` now take an optional index which is completed using
|
||||
the current tab's history.
|
||||
- The time a website in a tab was visited is now saved/restored in sessions.
|
||||
- New argument `strip` for `:navigate` which removes queries and
|
||||
fragments from the current URL.
|
||||
- When attempting to download a file to a location for which there's already a
|
||||
still-running download, a confirmation prompt is now displayed.
|
||||
- `:completion-item-focus` now understands `next-page` and `prev-page` with
|
||||
|
|
@ -66,12 +103,27 @@ Changed
|
|||
`--asciidoc-python path/to/python --asciidoc path/to/asciidoc.py`
|
||||
instead of the former
|
||||
`--asciidoc path/to/python path/to/asciidoc.py`.
|
||||
- The `readability-js` userscript now adds some CSS to better deal
|
||||
with images, similarly to what Firefox' reader mode does.
|
||||
- Dark mode (`colors.webpage.darkmode.*`) is now supported with Qt 5.15.2 (which
|
||||
is not released yet).
|
||||
- The default for the darkmode `policy.images` setting is now set to `smart`
|
||||
which fixes issues with e.g. formulas on Wikipedia.
|
||||
- The `readability-js` userscript now adds some CSS to improve the reader mode
|
||||
styling in various scenarios:
|
||||
* Images are now shrinked to the page width, similarly to what Firefox' reader
|
||||
mode does.
|
||||
* Some images ore now displayed as block (rather than inline) which is what
|
||||
Firefox' reader mode does as well.
|
||||
* Blockquotes are now styled more distinctively, again based on the Firefox
|
||||
reader mode.
|
||||
* Code blocks are now easier to distinguish from text and tables have visible
|
||||
cell margins.
|
||||
- The `readability-js` userscript now supports hint userscript mode.
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New argument `strip` for `:navigate` which removes queries and
|
||||
fragments from the current URL.
|
||||
- `:undo` now has a new `-w` / `--window` argument, which can be used to
|
||||
restore closed windows (rather than tabs). This is bound to `U` by default.
|
||||
- `:jseval` can now take `javascript:...` URLs via a new `--url` flag.
|
||||
|
|
@ -88,6 +140,11 @@ Added
|
|||
open the directory containing the downloaded file. An entry to do the same
|
||||
was also added to the context menu.
|
||||
- Messages are now wrapped when they are too long to be displayed on a single line.
|
||||
- New possible `--debug-flag` values:
|
||||
* `wait-renderer-process` waits for a `SIGUSR1` in the renderer process so a
|
||||
debugger can be attached.
|
||||
* `avoid-chromium-init` allows using `--version` without needing a working
|
||||
QtWebEngine/Chromium.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
|
@ -125,18 +182,21 @@ Fixed
|
|||
could end up in a confusing state. This is now fixed.
|
||||
- When qutebrowser quits, running downloads are now cancelled properly.
|
||||
- The site-specific quirk for `web.whatsapp.com` has been updated to work after recent
|
||||
WhatsApp-changes.
|
||||
changes in WhatsApp.
|
||||
- Highlighting in the completion now works properly when UTF-16 surrogate pairs (such as
|
||||
emoji) are involved.
|
||||
- When a windowed inspector is clicked, insert mode now isn't entered anymore.
|
||||
- When `:undo` to re-open a tab but `tabs.tabs_are_windows` was set between
|
||||
- When `:undo` is used to re-open a tab, but `tabs.tabs_are_windows` was set between
|
||||
closing and undoing the close, qutebrowser crashed. This is now fixed.
|
||||
- With QtWebEngine 5.15.0, setting the darkmode image policy to `smart` leads to
|
||||
renderer process crashes. The offending setting value is now ignored with a
|
||||
warning.
|
||||
- Fixes for the `qute-pass` userscript:
|
||||
* With newer `gopass` versions, a deprecation notice was copied as
|
||||
password due to `qute-pass` using it in a deprecated way.
|
||||
* The `--password-store` argument didn't actually set
|
||||
`PASSWORD_STORE_DIR` for `pass`, resulting in `qute-pass` finding matches but the
|
||||
underlying `pass` not finding matching passwords. This is now fixed.
|
||||
underlying `pass` not finding matching passwords.
|
||||
|
||||
v1.13.1 (2020-07-17)
|
||||
--------------------
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ IMPORTANT: Bandwidth for pull request review is currently quite limited. If you
|
|||
want to contribute where it's most needed, please consider reviewing or testing
|
||||
open pull requests.
|
||||
|
||||
I `<3` footnote:[Of course, that says `<3` in HTML.] contributors!
|
||||
I `<3` footnote:[`<3` in HTML] contributors!
|
||||
|
||||
This document contains guidelines for contributing to qutebrowser, as well as
|
||||
useful hints when doing so.
|
||||
|
|
@ -111,7 +111,7 @@ unittests and several linters/checkers.
|
|||
Currently, the following tox environments are available:
|
||||
|
||||
* Tests using https://www.pytest.org[pytest]:
|
||||
- `py35`, `py36`: Run pytest for python 3.5/3.6 with the system-wide PyQt.
|
||||
- `py36`, `py37`, ...: Run pytest for python 3.6/3.7/... with the system-wide PyQt.
|
||||
- `py36-pyqt57`, ..., `py36-pyqt59`: Run pytest with the given PyQt version (`py35-*` also works).
|
||||
- `py36-pyqt59-cov`: Run with coverage support (other Python/PyQt versions work too).
|
||||
* `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8].
|
||||
|
|
|
|||
|
|
@ -322,6 +322,25 @@ certutil -d "sql:${HOME}/.pki/nssdb" -D -n "My Fancy Certificate Nickname"
|
|||
And then import the new and valid certificates using the procedure
|
||||
described above.
|
||||
|
||||
Is there a dark mode? How can I filter websites to be darker?::
|
||||
There is a total of four possible approaches to get dark websites:
|
||||
+
|
||||
- The `colors.webpage.prefers_color_scheme_dark` setting tells websites that you prefer
|
||||
a dark theme. However, this requires websites to ship an appropriate dark style sheet.
|
||||
The setting requires a restart and QtWebEngine with at least Qt 5.14.
|
||||
- The `colors.webpage.darkmode.*` settings enable the dark mode of the underlying
|
||||
Chromium. Those setting require a restart and QtWebEngine with at least Qt 5.14. It's
|
||||
unfortunately not possible (due to limitations in Chromium and/or QtWebEngine) to
|
||||
change them dynamically or to specify a list of excluded websites.
|
||||
- The `content.user_stylesheets` setting allows specifying a custom CSS such as
|
||||
https://github.com/alphapapa/solarized-everything-css/[Solarized Everything]. Despite
|
||||
the name, the repository also offers themes other than just Solarized. This approach
|
||||
often yields worse results compared to the above ones, but it's possible to toggle it
|
||||
dynamically using a binding like `:bind ,d 'config-cycle content.user_stylesheets
|
||||
~/path/to/solarized-everything-css/css/gruvbox/gruvbox-all-sites.css ""'`
|
||||
- Finally, qutebrowser's Greasemonkey support should allow for running a
|
||||
https://github.com/darkreader/darkreader/issues/926#issuecomment-575893299[stripped down version]
|
||||
of the Dark Reader extension. This is mostly untested, though.
|
||||
|
||||
== Troubleshooting
|
||||
|
||||
|
|
|
|||
|
|
@ -395,10 +395,10 @@ Pre-built colorschemes
|
|||
|
||||
- A collection of https://github.com/chriskempson/base16[base16] color-schemes can be found in https://github.com/theova/base16-qutebrowser[base16-qutebrowser] and used with https://github.com/AuditeMarlow/base16-manager[base16-manager].
|
||||
- https://gitlab.com/jjzmajic/qutewal[Pywal integration]
|
||||
- Two implementations of the https://github.com/arcticicestudio/nord[Nord] colorscheme for qutebrowser exist: https://github.com/Linuus/nord-qutebrowser[Linuus], https://github.com/KnownAsDon/QuteBrowser-Nord-Theme[KnownAsDon]
|
||||
- https://github.com/arcticicestudio/nord[Nord]: https://github.com/Linuus/nord-qutebrowser[Linuus], https://github.com/KnownAsDon/QuteBrowser-Nord-Theme[KnownAsDon]
|
||||
- https://github.com/dracula/qutebrowser-dracula-theme[Dracula]
|
||||
- https://gitlab.com/lovetocode999/selenized-qutebrowser[Selenized]
|
||||
- https://github.com/The-Compiler/dotfiles/blob/master/qutebrowser/gruvbox.py[gruvbox]
|
||||
- https://github.com/morhetz/gruvbox[gruvbox]: https://github.com/The-Compiler/dotfiles/blob/master/qutebrowser/gruvbox.py[The-Compiler], https://gitlab.com/shaneyost/dots-popos-september-2020/-/blob/master/qutebrowser/config.py[Shane Yost]
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -1572,6 +1572,7 @@ Default: +pass:[white]+
|
|||
[[colors.webpage.darkmode.algorithm]]
|
||||
=== colors.webpage.darkmode.algorithm
|
||||
Which algorithm to use for modifying how colors are rendered with darkmode.
|
||||
The `lightness-cielab` value was added with QtWebEngine 5.14 and is treated like `lightness-hsl` with older QtWebEngine versions.
|
||||
|
||||
This setting requires a restart.
|
||||
|
||||
|
|
@ -1579,13 +1580,13 @@ Type: <<types,String>>
|
|||
|
||||
Valid values:
|
||||
|
||||
* +lightness-cielab+: Modify colors by converting them to CIELAB color space and inverting the L value.
|
||||
* +lightness-cielab+: Modify colors by converting them to CIELAB color space and inverting the L value. Not available with Qt < 5.14.
|
||||
* +lightness-hsl+: Modify colors by converting them to the HSL color space and inverting the lightness (i.e. the "L" in HSL).
|
||||
* +brightness-rgb+: Modify colors by subtracting each of r, g, and b from their maximum value.
|
||||
|
||||
Default: +pass:[lightness-cielab]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
|
|
@ -1600,7 +1601,7 @@ Type: <<types,Float>>
|
|||
|
||||
Default: +pass:[0.0]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
|
|
@ -1628,7 +1629,7 @@ Type: <<types,Bool>>
|
|||
|
||||
Default: +pass:[false]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
|
|
@ -1643,7 +1644,7 @@ Type: <<types,Bool>>
|
|||
|
||||
Default: +pass:[false]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
|
|
@ -1665,7 +1666,7 @@ On QtWebKit, this setting is unavailable.
|
|||
[[colors.webpage.darkmode.policy.images]]
|
||||
=== colors.webpage.darkmode.policy.images
|
||||
Which images to apply dark mode to.
|
||||
WARNING: On Qt 5.15.0, this setting can cause frequent renderer process crashes due to a https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug in Qt].
|
||||
With QtWebEngine 5.15.0, this setting can cause frequent renderer process crashes due to a https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug in Qt]. With QtWebEngine 5.10, this is not available at all. In those cases, the 'smart' setting is ignored and treated like 'never'.
|
||||
|
||||
This setting requires a restart.
|
||||
|
||||
|
|
@ -1675,11 +1676,11 @@ Valid values:
|
|||
|
||||
* +always+: Apply dark mode filter to all images.
|
||||
* +never+: Never apply dark mode filter to any images.
|
||||
* +smart+: Apply dark mode based on image content.
|
||||
* +smart+: Apply dark mode based on image content. Not available with Qt 5.10 / 5.15.0.
|
||||
|
||||
Default: +pass:[never]+
|
||||
Default: +pass:[smart]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
|
|
@ -4081,14 +4082,14 @@ The following placeholders are defined:
|
|||
* `{perc}`: Percentage as a string like `[10%]`.
|
||||
* `{perc_raw}`: Raw percentage, e.g. `10`.
|
||||
* `{current_title}`: Title of the current web page.
|
||||
* `{title_sep}`: The string ` - ` if a title is set, empty otherwise.
|
||||
* `{title_sep}`: The string `" - "` if a title is set, empty otherwise.
|
||||
* `{index}`: Index of this tab.
|
||||
* `{aligned_index}`: Index of this tab padded with spaces to have the same
|
||||
width.
|
||||
* `{id}`: Internal tab ID of this tab.
|
||||
* `{scroll_pos}`: Page scroll position.
|
||||
* `{host}`: Host of the current web page.
|
||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||
* `{backend}`: Either `webkit` or `webengine`
|
||||
* `{private}`: Indicates when private mode is enabled.
|
||||
* `{current_url}`: URL of the current web page.
|
||||
* `{protocol}`: Protocol (http/https/...) of the current web page.
|
||||
|
|
|
|||
|
|
@ -27,22 +27,41 @@ On Debian / Ubuntu
|
|||
How to install qutebrowser depends a lot on the version of Debian/Ubuntu you're
|
||||
running.
|
||||
|
||||
[[ubuntu1604]]
|
||||
Ubuntu 16.04 LTS / Linux Mint 18
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
|
||||
QtWebEngine). However, it comes with Python 3.5, so you can
|
||||
<<tox,install qutebrowser in a virtualenv>>.
|
||||
QtWebEngine). It also comes with Python 3.5 which is not supported anymore since
|
||||
qutebrowser v2.0.0.
|
||||
|
||||
You'll need some basic libraries to use the virtualenv-installed PyQt:
|
||||
You should be able to install a newer Python (3.6+) using the
|
||||
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or
|
||||
https://github.com/pyenv/pyenv[pyenv], and then proceed to
|
||||
<<tox,install qutebrowser in a virtualenv>>. However, this is currently untested. If you
|
||||
got this setup to work successfully, please submit a pull request to adjust these
|
||||
instructions!
|
||||
|
||||
Note you'll need some basic libraries to use the virtualenv-installed PyQt:
|
||||
|
||||
----
|
||||
# apt install libglib2.0-0 libgl1 libfontconfig1 libx11-xcb1 libxi6 libxrender1 libdbus-1-3
|
||||
# apt install --no-install-recommends git ca-certificates python3 python3-venv asciidoc libglib2.0-0 libgl1 libfontconfig1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libdbus-1-3 libyaml-dev gcc python3-dev
|
||||
----
|
||||
|
||||
// FIXME not needed anymore?
|
||||
// libxi6 libxrender1 libegl1-mesa
|
||||
|
||||
Debian Stretch
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
WARNING: Debian Stretch packages Qt 5.7 which is very old (based on a Chromium
|
||||
from March 2016 with security fixes from November 2016) and insecure. It is also
|
||||
https://www.debian.org/releases/stretch/amd64/release-notes/ch-information.en.html#browser-security[not covered]
|
||||
by Debian's security patches. Support for it will be dropped in qutebrowser
|
||||
v2.0.0, preliminarily planned for December 2020. It is recommended to
|
||||
<<tox,install qutebrowser in a virtualenv>> with a newer PyQt/Qt binary
|
||||
instead.
|
||||
|
||||
Debian Stretch comes with QtWebEngine in the repositories. This makes it possible
|
||||
to install qutebrowser via the Debian package.
|
||||
|
||||
|
|
@ -70,6 +89,14 @@ qutebrowser package.
|
|||
Debian Buster / Ubuntu 18.04 LTS / Linux Mint 19 (or newer)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
WARNING: Debian Buster packages Qt 5.11 which is very old (based on a Chromium
|
||||
from March 2018 with security fixes from November 2018) and insecure. It is also
|
||||
https://www.debian.org/releases/buster/amd64/release-notes/ch-information.en.html#browser-security[not covered]
|
||||
by Debian's security patches. Support for it will be dropped in qutebrowser
|
||||
v2.0.0, preliminarily planned for December 2020. It is recommended to
|
||||
<<tox,install qutebrowser in a virtualenv>> with a newer PyQt/Qt binary
|
||||
instead.
|
||||
|
||||
With those distributions, qutebrowser is in the official repositories, and you
|
||||
can install it with apt:
|
||||
|
||||
|
|
@ -80,18 +107,18 @@ can install it with apt:
|
|||
Additional hints
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
- Alternatively, you can <<tox,install qutebrowser in a virtualenv>> to get a newer
|
||||
QtWebEngine version.
|
||||
- If running from git, run the following to generate the documentation for the
|
||||
`:help` command:
|
||||
+
|
||||
----
|
||||
# apt install --no-install-recommends asciidoc source-highlight
|
||||
# apt install --no-install-recommends asciidoc
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
- If you prefer using QtWebKit, there's an up-to-date version available in
|
||||
https://packages.debian.org/buster/libqt5webkit5[Debian Testing].
|
||||
- If you prefer using QtWebKit, there's QtWebKit 5.212 available in
|
||||
https://packages.debian.org/buster/libqt5webkit5[Debian Testing]. Note
|
||||
however that it is based on an upstream WebKit from September 2016 with known
|
||||
security issues and no sandboxing or process isolation.
|
||||
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||
+
|
||||
----
|
||||
|
|
@ -141,7 +168,8 @@ $ cd ..
|
|||
$ rm -r qutebrowser-git
|
||||
----
|
||||
|
||||
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
|
||||
or you could use an AUR helper like https://github.com/Jguer/yay/[yay], e.g.
|
||||
`yay -S qutebrowser-git`.
|
||||
|
||||
If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||
|
||||
|
|
@ -181,12 +209,6 @@ with:
|
|||
# xbps-install qutebrowser
|
||||
----
|
||||
|
||||
It's currently recommended to install `python3-PyQt5-webengine` and
|
||||
`python3-PyQt5-opengl`, then start with `--backend webengine` to use the new
|
||||
backend.
|
||||
|
||||
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
|
||||
|
||||
On NixOS
|
||||
--------
|
||||
|
||||
|
|
@ -197,18 +219,11 @@ it with:
|
|||
$ nix-env -i qutebrowser
|
||||
----
|
||||
|
||||
It's recommended to install `qt5.qtwebengine` and start with
|
||||
`--backend webengine` to use the new backend.
|
||||
|
||||
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
|
||||
|
||||
On openSUSE
|
||||
-----------
|
||||
|
||||
There are prebuilt RPMs available at https://software.opensuse.org/download.html?project=network&package=qutebrowser[OBS].
|
||||
|
||||
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
|
||||
|
||||
On Slackware
|
||||
------------
|
||||
|
||||
|
|
@ -248,7 +263,7 @@ qutebrowser is available
|
|||
https://flathub.org/apps/details/org.qutebrowser.qutebrowser[on Flathub]
|
||||
as `org.qutebrowser.qutebrowser`.
|
||||
|
||||
WARNING: As of July 2020, the Flatpak package is severely outdated (qutebrowser
|
||||
WARNING: As of October 2020, the Flatpak package is severely outdated (qutebrowser
|
||||
v1.7.0 from July 2019) and, among other issues, misses fixes for a
|
||||
(low-severity) https://github.com/qutebrowser/qutebrowser/security/advisories/GHSA-4rcq-jv2f-898j[security issue].
|
||||
It's recommended to <<tox,install qutebrowser in a virtualenv>> instead, which
|
||||
|
|
@ -350,10 +365,14 @@ qutebrowser from source.
|
|||
==== Homebrew
|
||||
|
||||
----
|
||||
$ brew install qt5
|
||||
$ brew install qt
|
||||
(build PyQt and PyQtWebEngine from source)
|
||||
$ pip3 install qutebrowser
|
||||
----
|
||||
|
||||
NOTE: Homebrew does not package PyQtWebEngine (Python wrappers for
|
||||
QtWebEngine), so you will need to build that from sources manually.
|
||||
|
||||
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
|
||||
|
||||
Homebrew's builds of Qt and PyQt don't come with QtWebKit (and `--with-qtwebkit`
|
||||
|
|
@ -364,12 +383,11 @@ https://github.com/annulen/webkit/wiki/Building-QtWebKit-on-OS-X[manually].
|
|||
Packagers
|
||||
---------
|
||||
|
||||
There are example .desktop and icon files provided. They would go in the
|
||||
standard location for your distro (`/usr/share/applications` and
|
||||
`/usr/share/pixmaps` for example).
|
||||
|
||||
The normal `setup.py install` doesn't install these files, so you'll have to do
|
||||
it as part of the packaging process.
|
||||
qutebrowser ships with a
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/misc/Makefile[Makefile]
|
||||
intended for packagers. This installs system-wide files in a proper locations,
|
||||
so it should be preferred to the usual `setup.py install` or `pip install`
|
||||
invocation.
|
||||
|
||||
// The tox anchor is so that old links remain compatible.
|
||||
// When switching to Sphinx, that should be changed.
|
||||
|
|
@ -405,6 +423,10 @@ $ cd qutebrowser
|
|||
Installing dependencies (including Qt)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using a Qt installed via virtualenv needs a couple of system-wide libraries.
|
||||
See the <<ubuntu1604,Ubuntu 16.04 section>> for details about which libraries
|
||||
are required.
|
||||
|
||||
Then run the install script:
|
||||
|
||||
----
|
||||
|
|
@ -418,9 +440,8 @@ This installs all needed Python dependencies in a `.venv` subfolder
|
|||
This comes with an up-to-date Qt/PyQt including a pre-compiled QtWebEngine
|
||||
binary, but has a few caveats:
|
||||
|
||||
- Make sure your `python3` is Python 3.5 or newer, otherwise you'll get a "No
|
||||
matching distribution found" error. Note that qutebrowser itself also requires
|
||||
this.
|
||||
- Make sure your `python3` is Python 3.6 or newer, otherwise you'll get a "No
|
||||
matching distribution found" error and/or qutebrowser will not run.
|
||||
- It only works on 64-bit x86 systems, with other architectures you'll get the
|
||||
same error.
|
||||
- It comes with a QtWebEngine compiled without proprietary codec support (such
|
||||
|
|
@ -433,6 +454,12 @@ You can specify a Qt/PyQt version with the `--pyqt-version` flag, see
|
|||
`mkenv.py --help` for a list of available versions. By default, the latest
|
||||
version which plays well with qutebrowser is used.
|
||||
|
||||
NOTE: If qutebrowser fails to start with a _"This application failed to start
|
||||
because no Qt platform plugin could be initialized."_ message, most likely a
|
||||
system-wide library is missing. Run qutebrowser again after
|
||||
`export QT_DEBUG_PLUGINS=1` and keep attention to a
|
||||
_QLibraryPrivate::loadPlugin failed on ..._ line for details.
|
||||
|
||||
Installing dependencies (system-wide Qt)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ https://www.linuxmint.com/rel_tessa_mate_whatsnew.php[Linux Mint]) and install
|
|||
the debug packages:
|
||||
|
||||
----
|
||||
# apt install python3-dbg python3-pyqt5-dbg python3-pyqt5.qtwebengine-dbg libqt5webengine5-dbgsym
|
||||
# apt install python3-dbg python3-pyqt5-dbg python3-pyqt5.qtwebengine-dbg libqt5webengine5-dbgsym libqt5webenginecore5-dbgsym
|
||||
----
|
||||
|
||||
or with the QtWebKit backend:
|
||||
|
|
|
|||
|
|
@ -1,41 +1,79 @@
|
|||
# AppArmor profile for qutebrowser
|
||||
# Tested on Debian jessie
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
profile qutebrowser /usr/{local/,}bin/qutebrowser {
|
||||
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/python>
|
||||
#include <abstractions/audio>
|
||||
#include <abstractions/dri-common>
|
||||
#include <abstractions/mesa>
|
||||
#include <abstractions/X>
|
||||
#include <abstractions/wayland>
|
||||
#include <abstractions/qt5>
|
||||
#include <abstractions/fonts>
|
||||
|
||||
#include <abstractions/dbus-session-strict>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/openssl>
|
||||
#include <abstractions/ssl_certs>
|
||||
#include <abstractions/audio>
|
||||
#include <abstractions/fonts>
|
||||
#include <abstractions/kde>
|
||||
|
||||
#include <abstractions/freedesktop.org>
|
||||
#include <abstractions/user-download>
|
||||
#include <abstractions/X>
|
||||
#include <abstractions/user-tmp>
|
||||
|
||||
capability dac_override,
|
||||
|
||||
/usr/{local/,}bin/ r,
|
||||
/usr/{local/,}bin/qutebrowser rix,
|
||||
/usr/bin/python3.? r,
|
||||
# not nice but required for chromium sandbox
|
||||
capability sys_admin,
|
||||
capability sys_chroot,
|
||||
capability sys_ptrace,
|
||||
|
||||
/usr/lib/python3/ mr,
|
||||
/usr/lib/python3/** mr,
|
||||
/usr/lib/python3.?/ r,
|
||||
/usr/lib/python3.?/** mr,
|
||||
/usr/local/lib/python3.?/** r,
|
||||
/dev/ r,
|
||||
/dev/video* r,
|
||||
/etc/mime.types r,
|
||||
/usr/bin/ r,
|
||||
/usr/bin/ldconfig ix,
|
||||
/usr/bin/uname ix,
|
||||
/usr/bin/qutebrowser rix,
|
||||
/usr/lib/qt/libexec/QtWebEngineProcess mrix,
|
||||
/usr/share/pdf.js/** r,
|
||||
/usr/share/qt/translations/qtwebengine_locales/* r,
|
||||
/usr/share/qt/qtwebengine_dictionaries r,
|
||||
/usr/share/qt/qtwebengine_dictionaries/* r,
|
||||
/usr/share/qt/resources/* r,
|
||||
|
||||
/proc/*/mounts r,
|
||||
owner /tmp/** rwkl,
|
||||
owner /run/user/*/ rw,
|
||||
owner /run/user/*/** krw,
|
||||
owner @{HOME}/ r,
|
||||
owner /dev/shm/.org.chromium* rw,
|
||||
owner @{HOME}/.cache/{qtshadercache,qutebrowser}/** rwlk,
|
||||
owner @{HOME}/.cache/qtshadercache** rwl,
|
||||
owner @{HOME}/.config/qutebrowser/** rwlk,
|
||||
owner @{HOME}/.local/share/.org.chromium.Chromium* rw,
|
||||
owner @{HOME}/.local/share/mime/generic-icons r,
|
||||
owner @{HOME}/.local/share/qutebrowser/ r,
|
||||
owner @{HOME}/.local/share/qutebrowser/** rwkl,
|
||||
owner @{HOME}/.pki/nssdb/* rwk,
|
||||
owner @{HOME}/#[0-9]* rwm,
|
||||
owner /run/user/*/qutebrowser/ rw,
|
||||
owner /run/user/*/qutebrowser/* rw,
|
||||
owner /run/user/*/qutebrowser*slave-socket rwl,
|
||||
owner /run/user/*/#* rw,
|
||||
|
||||
@{HOME}/.config/qutebrowser/** krw,
|
||||
@{HOME}/.local/share/qutebrowser/** krw,
|
||||
@{HOME}/.cache/qutebrowser/** krw,
|
||||
@{HOME}/.gstreamer-0.10/* r,
|
||||
# qt/kde
|
||||
@{PROC} r,
|
||||
@{PROC}/sys/fs/inotify/max_user_watches r,
|
||||
@{PROC}/sys/kernel/random/boot_id r,
|
||||
@{PROC}/sys/kernel/core_pattern r,
|
||||
@{PROC}/sys/kernel/yama/ptrace_scope r,
|
||||
/sys/{class,bus}/ r,
|
||||
/sys/bus/pci/devices/ r,
|
||||
/sys/devices/**/{class,config,device,resource,revision,removable,uevent} r,
|
||||
/sys/devices/**/{vendor,subsystem_device,subsystem_vendor} r,
|
||||
|
||||
owner @{PROC}/@{pid}/{fd,stat,task,mounts}/ r,
|
||||
owner @{PROC}/@{pid}/stat r,
|
||||
owner @{PROC}/@{pid}/task/@{pid}/status r,
|
||||
owner @{PROC}/@{pid}/{setgroups,gid_map,oom_score_adj,uid_map} rw,
|
||||
owner @{PROC}/@{pid}/{oom_score_adj,uid_map} rw,
|
||||
|
||||
# allow execution of userscripts
|
||||
/usr/share/qutebrowser/userscripts/* Ux,
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
</content_rating>
|
||||
<releases>
|
||||
<!-- Add new releases here -->
|
||||
<release version="1.14.0" date="2020-10-15"/>
|
||||
<release version="1.13.1" date="2020-07-17"/>
|
||||
<release version="1.13.0" date="2020-06-26"/>
|
||||
<release version="1.12.0" date="2020-06-01"/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
check-manifest==0.42
|
||||
pep517==0.8.2
|
||||
check-manifest==0.44
|
||||
pep517==0.9.1
|
||||
toml==0.10.1
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
bump2version==1.0.0
|
||||
bump2version==1.0.1
|
||||
certifi==2020.6.20
|
||||
cffi==1.14.2
|
||||
cffi==1.14.3
|
||||
chardet==3.0.4
|
||||
colorama==0.4.3
|
||||
cryptography==3.1
|
||||
colorama==0.4.4
|
||||
cryptography==3.2
|
||||
cssutils==1.0.2
|
||||
github3.py==1.3.0
|
||||
hunter==3.2.2
|
||||
hunter==3.3.1
|
||||
idna==2.10
|
||||
jwcrypto==0.8
|
||||
manhole==1.6.0
|
||||
packaging==20.4
|
||||
pycparser==2.20
|
||||
Pympler==0.8
|
||||
Pympler==0.9
|
||||
pyparsing==2.4.7
|
||||
PyQt-builder==1.5.0
|
||||
python-dateutil==2.8.1
|
||||
|
|
@ -23,4 +23,4 @@ sip==5.4.0
|
|||
six==1.15.0
|
||||
toml==0.10.1
|
||||
uritemplate==3.0.1
|
||||
# urllib3==1.25.10
|
||||
# urllib3==1.25.11
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==20.2.0
|
||||
flake8==3.8.3
|
||||
flake8==3.8.4
|
||||
flake8-bugbear==20.1.4
|
||||
flake8-builtins==1.5.3
|
||||
flake8-comprehensions==3.2.3
|
||||
flake8-comprehensions==3.3.0
|
||||
flake8-copyright==0.2.2
|
||||
flake8-debugger==3.2.1
|
||||
flake8-deprecated==1.3
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
diff-cover==4.0.0
|
||||
diff-cover==4.0.1
|
||||
inflect==4.1.0
|
||||
Jinja2==2.11.2
|
||||
jinja2-pluralize==0.3.0
|
||||
lxml==4.5.2
|
||||
lxml==4.6.1
|
||||
MarkupSafe==1.1.1
|
||||
mypy==0.782
|
||||
mypy==0.790
|
||||
mypy-extensions==0.4.3
|
||||
pluggy==0.13.1
|
||||
Pygments==2.7.1
|
||||
-e git+https://github.com/stlehmann/PyQt5-stubs.git@master#egg=PyQt5_stubs
|
||||
Pygments==2.7.2
|
||||
-e git+https://github.com/stlehmann/PyQt5-stubs.git@811462b34ee151b898289ae8f1de8af30c690c55#egg=PyQt5_stubs
|
||||
typed-ast==1.4.1
|
||||
typing-extensions==3.7.4.3
|
||||
|
|
|
|||
|
|
@ -2,6 +2,3 @@ mypy
|
|||
lxml # For HTML reports
|
||||
diff-cover
|
||||
-e git+https://github.com/stlehmann/PyQt5-stubs.git@master#egg=PyQt5-stubs
|
||||
|
||||
# remove @commit-id for scm installs
|
||||
#@ replace: @.*# @master#
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
altgraph==0.17
|
||||
pyinstaller==4.0
|
||||
pyinstaller-hooks-contrib==2020.8
|
||||
pyinstaller-hooks-contrib==2020.9
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
astroid==2.3.3 # rq.filter: < 2.4
|
||||
certifi==2020.6.20
|
||||
cffi==1.14.2
|
||||
cffi==1.14.3
|
||||
chardet==3.0.4
|
||||
cryptography==3.1
|
||||
cryptography==3.2
|
||||
github3.py==1.3.0
|
||||
idna==2.10
|
||||
isort==4.3.21
|
||||
|
|
@ -19,5 +19,5 @@ requests==2.24.0
|
|||
six==1.15.0
|
||||
typed-ast==1.4.1 ; python_version<"3.8"
|
||||
uritemplate==3.0.1
|
||||
# urllib3==1.25.10
|
||||
# urllib3==1.25.11
|
||||
wrapt==1.11.2
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
PyQt5==5.15.1
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.15.1
|
||||
PyQtWebEngine==5.15.0
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
PyQt5
|
||||
PyQtWebEngine
|
||||
PyQtWebEngine!=5.15.1
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.16
|
||||
Pygments==2.7.1
|
||||
Pygments==2.7.2
|
||||
pyroma==2.6
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ imagesize==1.2.0
|
|||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
packaging==20.4
|
||||
Pygments==2.7.1
|
||||
Pygments==2.7.2
|
||||
pyparsing==2.4.7
|
||||
pytz==2020.1
|
||||
requests==2.24.0
|
||||
|
|
@ -23,4 +23,4 @@ sphinxcontrib-htmlhelp==1.0.3
|
|||
sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==1.0.3
|
||||
sphinxcontrib-serializinghtml==1.1.4
|
||||
urllib3==1.25.10
|
||||
urllib3==1.25.11
|
||||
|
|
|
|||
|
|
@ -2,23 +2,25 @@
|
|||
|
||||
apipkg==1.5
|
||||
attrs==20.2.0
|
||||
beautifulsoup4==4.9.1
|
||||
beautifulsoup4==4.9.3
|
||||
certifi==2020.6.20
|
||||
chardet==3.0.4
|
||||
cheroot==8.4.5
|
||||
click==7.1.2
|
||||
# colorama==0.4.3
|
||||
# colorama==0.4.4
|
||||
coverage==5.3
|
||||
EasyProcess==0.3
|
||||
execnet==1.7.1
|
||||
filelock==3.0.12
|
||||
Flask==1.1.2
|
||||
glob2==0.7
|
||||
hunter==3.2.2
|
||||
hypothesis==5.35.3 ; python_version>="3.6"
|
||||
hunter==3.3.1
|
||||
hypothesis==5.38.0
|
||||
icdiff==1.9.1
|
||||
idna==2.10
|
||||
iniconfig==1.0.1
|
||||
iniconfig==1.1.1
|
||||
itsdangerous==1.1.0
|
||||
jaraco.functools==3.0.1 ; python_version>="3.6"
|
||||
jaraco.functools==3.0.1
|
||||
# Jinja2==2.11.2
|
||||
Mako==1.1.3
|
||||
manhole==1.6.0
|
||||
|
|
@ -28,20 +30,23 @@ packaging==20.4
|
|||
parse==1.18.0
|
||||
parse-type==0.5.2
|
||||
pluggy==0.13.1
|
||||
pprintpp==0.4.0
|
||||
py==1.9.0
|
||||
py-cpuinfo==7.0.0
|
||||
Pygments==2.7.1
|
||||
Pygments==2.7.2
|
||||
pyparsing==2.4.7
|
||||
pytest==6.0.2
|
||||
pytest==6.1.1
|
||||
pytest-bdd==4.0.1
|
||||
pytest-benchmark==3.2.3
|
||||
pytest-clarity==0.3.0a0
|
||||
pytest-cov==2.10.1
|
||||
pytest-forked==1.3.0
|
||||
pytest-icdiff==0.5
|
||||
pytest-instafail==0.4.2
|
||||
pytest-mock==3.3.1
|
||||
pytest-qt==3.3.0
|
||||
pytest-repeat==0.8.0
|
||||
pytest-rerunfailures==9.1
|
||||
pytest-rerunfailures==9.1.1
|
||||
pytest-xdist==2.1.0
|
||||
pytest-xvfb==2.0.0
|
||||
PyVirtualDisplay==1.3.2
|
||||
|
|
@ -50,11 +55,9 @@ requests-file==1.5.1
|
|||
six==1.15.0
|
||||
sortedcontainers==2.2.2
|
||||
soupsieve==2.0.1
|
||||
tldextract==2.2.3
|
||||
termcolor==1.1.0
|
||||
tldextract==3.0.2
|
||||
toml==0.10.1
|
||||
urllib3==1.25.10
|
||||
vulture==2.1 ; python_version>="3.6"
|
||||
urllib3==1.25.11
|
||||
vulture==2.1
|
||||
Werkzeug==1.0.1
|
||||
jaraco.functools==2.0; python_version<"3.6"
|
||||
vulture==1.6; python_version<"3.6"
|
||||
hypothesis<5.34.0; python_version<"3.6"
|
||||
|
|
|
|||
|
|
@ -27,17 +27,11 @@ pytest-xvfb
|
|||
PyVirtualDisplay
|
||||
# To run on multiple cores with -n
|
||||
pytest-xdist
|
||||
# For nicer output
|
||||
pytest-icdiff
|
||||
pytest-clarity
|
||||
|
||||
# Needed to test misc/userscripts/qute-lastpass
|
||||
tldextract
|
||||
|
||||
#@ markers: jaraco.functools python_version>="3.6"
|
||||
#@ add: jaraco.functools==2.0; python_version<"3.6"
|
||||
|
||||
#@ markers: vulture python_version>="3.6"
|
||||
#@ add: vulture==1.6; python_version<"3.6"
|
||||
|
||||
#@ markers: hypothesis python_version>="3.6"
|
||||
#@ add: hypothesis<5.34.0; python_version<"3.6"
|
||||
|
||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ py==1.9.0
|
|||
pyparsing==2.4.7
|
||||
six==1.15.0
|
||||
toml==0.10.1
|
||||
tox==3.20.0
|
||||
tox==3.20.1
|
||||
tox-pip-version==0.0.7
|
||||
tox-venv==0.4.0
|
||||
virtualenv==20.0.31
|
||||
virtualenv==20.1.0
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
pathspec==0.8.0
|
||||
PyYAML==5.3.1
|
||||
yamllint==1.24.2
|
||||
yamllint==1.25.0
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ The following userscripts can be found on their own repositories.
|
|||
snippets on web pages to the clipboard via hints.
|
||||
- [Qute-Translate](https://github.com/AckslD/Qute-Translate): Translate URLs or
|
||||
selections via Google Translate.
|
||||
- [qute-snippets](https://github.com/Aledosim/qute-snippets): Bind text snippets to a keyword
|
||||
and retrieve they when you want.
|
||||
|
||||
[Zotero]: https://www.zotero.org/
|
||||
[Pocket]: https://getpocket.com/
|
||||
|
|
|
|||
|
|
@ -54,6 +54,35 @@ const HEADER = `
|
|||
figure img {
|
||||
display: block;
|
||||
}
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border: 1px solid currentColor;
|
||||
border-collapse: collapse;
|
||||
padding: 6px;
|
||||
vertical-align: top;
|
||||
}
|
||||
table {
|
||||
margin: 5px;
|
||||
}
|
||||
pre {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
line-height: 1.45;
|
||||
background-color: #dddddd;
|
||||
}
|
||||
code {
|
||||
padding: .2em .4em;
|
||||
margin: 0;
|
||||
background-color: #dddddd;
|
||||
}
|
||||
blockquote {
|
||||
border-inline-start: 2px solid #333333 !important;
|
||||
padding: 0;
|
||||
padding-inline-start: 16px;
|
||||
margin-inline-start: 24px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
<!-- This icon is licensed under the Mozilla Public License 2.0 (available at: https://www.mozilla.org/en-US/MPL/2.0/).
|
||||
The original icon can be found here: https://dxr.mozilla.org/mozilla-central/source/browser/themes/shared/reader/readerMode.svg -->
|
||||
|
|
@ -68,13 +97,24 @@ const HEADER = `
|
|||
</head>`;
|
||||
const scriptsDir = path.join(process.env.QUTE_DATA_DIR, 'userscripts');
|
||||
const tmpFile = path.join(scriptsDir, '/readability.html');
|
||||
const domOpts = {url: process.env.QUTE_URL, contentType: "text/html; charset=utf-8"};
|
||||
|
||||
if (!fs.existsSync(scriptsDir)){
|
||||
if (!fs.existsSync(scriptsDir)) {
|
||||
fs.mkdirSync(scriptsDir);
|
||||
}
|
||||
|
||||
JSDOM.fromFile(process.env.QUTE_HTML, domOpts).then(dom => {
|
||||
let getDOM, domOpts, target;
|
||||
// When hinting, use the selected hint instead of the current page
|
||||
if (process.env.QUTE_MODE === 'hints') {
|
||||
getDOM = JSDOM.fromURL;
|
||||
target = process.env.QUTE_URL;
|
||||
}
|
||||
else {
|
||||
getDOM = JSDOM.fromFile;
|
||||
domOpts = {url: process.env.QUTE_URL, contentType: "text/html; charset=utf-8"};
|
||||
target = process.env.QUTE_HTML;
|
||||
}
|
||||
|
||||
getDOM(target, domOpts).then(dom => {
|
||||
let reader = new Readability(dom.window.document);
|
||||
let article = reader.parse();
|
||||
let content = util.format(HEADER, article.title) + article.content;
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ qt_log_ignore =
|
|||
^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
|
||||
xfail_strict = true
|
||||
filterwarnings =
|
||||
error
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2020 Florian Bruhin (The Compiler)"
|
|||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version__ = "1.13.1"
|
||||
__version__ = "1.14.0"
|
||||
__version_info__ = tuple(int(part) for part in __version__.split('.'))
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ Possible values:
|
|||
value.
|
||||
- A python enum type: All members of the enum are possible values.
|
||||
- A ``typing.Union`` of multiple types above: Any of these types are valid
|
||||
values, e.g., ``typing.Union[str, int]``.
|
||||
values, e.g., ``Union[str, int]``.
|
||||
"""
|
||||
|
||||
|
||||
import inspect
|
||||
import typing
|
||||
from typing import Any, Callable, Iterable
|
||||
|
||||
from qutebrowser.utils import qtutils
|
||||
from qutebrowser.commands import command, cmdexc
|
||||
|
|
@ -91,8 +91,7 @@ def check_overflow(arg: int, ctype: str) -> None:
|
|||
"representation.".format(ctype))
|
||||
|
||||
|
||||
def check_exclusive(flags: typing.Iterable[bool],
|
||||
names: typing.Iterable[str]) -> None:
|
||||
def check_exclusive(flags: Iterable[bool], names: Iterable[str]) -> None:
|
||||
"""Check if only one flag is set with exclusive flags.
|
||||
|
||||
Raise a CommandError if not.
|
||||
|
|
@ -113,7 +112,7 @@ class register: # noqa: N801,N806 pylint: disable=invalid-name
|
|||
def __init__(self, *,
|
||||
instance: str = None,
|
||||
name: str = None,
|
||||
**kwargs: typing.Any) -> None:
|
||||
**kwargs: Any) -> None:
|
||||
"""Save decorator arguments.
|
||||
|
||||
Gets called on parse-time with the decorator arguments.
|
||||
|
|
@ -128,7 +127,7 @@ class register: # noqa: N801,N806 pylint: disable=invalid-name
|
|||
# The arguments to pass to Command.
|
||||
self._kwargs = kwargs
|
||||
|
||||
def __call__(self, func: typing.Callable) -> typing.Callable:
|
||||
def __call__(self, func: Callable) -> Callable:
|
||||
"""Register the command before running the function.
|
||||
|
||||
Gets called when a function should be decorated.
|
||||
|
|
@ -175,7 +174,7 @@ class argument: # noqa: N801,N806 pylint: disable=invalid-name
|
|||
def foo(bar: str):
|
||||
...
|
||||
|
||||
For ``typing.Union`` types, the given ``choices`` are only checked if other
|
||||
For ``Union`` types, the given ``choices`` are only checked if other
|
||||
types (like ``int``) don't match.
|
||||
|
||||
The following arguments are supported for ``@cmdutils.argument``:
|
||||
|
|
@ -197,11 +196,11 @@ class argument: # noqa: N801,N806 pylint: disable=invalid-name
|
|||
trailing underscores stripped and underscores replaced by dashes.
|
||||
"""
|
||||
|
||||
def __init__(self, argname: str, **kwargs: typing.Any) -> None:
|
||||
def __init__(self, argname: str, **kwargs: Any) -> None:
|
||||
self._argname = argname # The name of the argument to handle.
|
||||
self._kwargs = kwargs # Valid ArgInfo members.
|
||||
|
||||
def __call__(self, func: typing.Callable) -> typing.Callable:
|
||||
def __call__(self, func: Callable) -> Callable:
|
||||
funcname = func.__name__
|
||||
|
||||
if self._argname not in inspect.signature(func).parameters:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""Access to the qutebrowser configuration."""
|
||||
|
||||
import typing
|
||||
from typing import cast, Any
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
|
|
@ -35,9 +35,9 @@ from qutebrowser.config import config
|
|||
#: This also supports setting configuration values::
|
||||
#:
|
||||
#: config.val.content.javascript.enabled = False
|
||||
val = typing.cast('config.ConfigContainer', None)
|
||||
val = cast('config.ConfigContainer', None)
|
||||
|
||||
|
||||
def get(name: str, url: QUrl = None) -> typing.Any:
|
||||
def get(name: str, url: QUrl = None) -> Any:
|
||||
"""Get a value from the config based on a string name."""
|
||||
return config.instance.get(name, url)
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@
|
|||
"""Hooks for extensions."""
|
||||
|
||||
import importlib
|
||||
import typing
|
||||
from typing import Callable
|
||||
|
||||
|
||||
from qutebrowser.extensions import loader
|
||||
|
||||
|
||||
def _add_module_info(func: typing.Callable) -> loader.ModuleInfo:
|
||||
def _add_module_info(func: Callable) -> loader.ModuleInfo:
|
||||
"""Add module info to the given function."""
|
||||
module = importlib.import_module(func.__module__)
|
||||
return loader.add_module_info(module)
|
||||
|
|
@ -48,7 +48,7 @@ class init:
|
|||
message.info("Extension initialized.")
|
||||
"""
|
||||
|
||||
def __call__(self, func: typing.Callable) -> typing.Callable:
|
||||
def __call__(self, func: Callable) -> Callable:
|
||||
info = _add_module_info(func)
|
||||
if info.init_hook is not None:
|
||||
raise ValueError("init hook is already registered!")
|
||||
|
|
@ -86,7 +86,7 @@ class config_changed:
|
|||
def __init__(self, option_filter: str = None) -> None:
|
||||
self._filter = option_filter
|
||||
|
||||
def __call__(self, func: typing.Callable) -> typing.Callable:
|
||||
def __call__(self, func: Callable) -> Callable:
|
||||
info = _add_module_info(func)
|
||||
info.config_changed_hooks.append((self._filter, func))
|
||||
return func
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ def run(args):
|
|||
if args.temp_basedir:
|
||||
args.basedir = tempfile.mkdtemp(prefix='qutebrowser-basedir-')
|
||||
|
||||
log.init.debug("Main process PID: {}".format(os.getpid()))
|
||||
|
||||
log.init.debug("Initializing directories...")
|
||||
standarddir.init(args)
|
||||
utils.preload_resources()
|
||||
|
|
@ -553,15 +555,15 @@ class Application(QApplication):
|
|||
|
||||
def event(self, e):
|
||||
"""Handle macOS FileOpen events."""
|
||||
if e.type() == QEvent.FileOpen:
|
||||
url = e.url()
|
||||
if url.isValid():
|
||||
open_url(url, no_raise=True)
|
||||
else:
|
||||
message.error("Invalid URL: {}".format(url.errorString()))
|
||||
else:
|
||||
if e.type() != QEvent.FileOpen:
|
||||
return super().event(e)
|
||||
|
||||
url = e.url()
|
||||
if url.isValid():
|
||||
open_url(url, no_raise=True)
|
||||
else:
|
||||
message.error("Invalid URL: {}".format(url.errorString()))
|
||||
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@
|
|||
import enum
|
||||
import itertools
|
||||
import functools
|
||||
import typing
|
||||
from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional,
|
||||
Sequence, Set, Type, Union)
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt,
|
||||
|
|
@ -32,9 +33,10 @@ from PyQt5.QtWidgets import QWidget, QApplication, QDialog
|
|||
from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5.QtWebKit import QWebHistory
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineHistory
|
||||
from PyQt5.QtWebKitWidgets import QWebPage
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineHistory, QWebEnginePage
|
||||
|
||||
import pygments
|
||||
import pygments.lexers
|
||||
|
|
@ -48,7 +50,7 @@ from qutebrowser.misc import miscwidgets, objects, sessions
|
|||
from qutebrowser.browser import eventfilter, inspector
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.browser.inspector import AbstractWebInspector
|
||||
|
||||
|
|
@ -71,7 +73,7 @@ def create(win_id: int,
|
|||
mode_manager = modeman.instance(win_id)
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
from qutebrowser.browser.webengine import webenginetab
|
||||
tab_class = webenginetab.WebEngineTab # type: typing.Type[AbstractTab]
|
||||
tab_class: Type[AbstractTab] = webenginetab.WebEngineTab
|
||||
elif objects.backend == usertypes.Backend.QtWebKit:
|
||||
from qutebrowser.browser.webkit import webkittab
|
||||
tab_class = webkittab.WebKitTab
|
||||
|
|
@ -100,13 +102,23 @@ class UnsupportedOperationError(WebTabError):
|
|||
"""Raised when an operation is not supported with the given backend."""
|
||||
|
||||
|
||||
TerminationStatus = enum.Enum('TerminationStatus', [
|
||||
'normal',
|
||||
'abnormal', # non-zero exit status
|
||||
'crashed', # e.g. segfault
|
||||
'killed',
|
||||
'unknown',
|
||||
])
|
||||
class TerminationStatus(enum.Enum):
|
||||
|
||||
"""How a QtWebEngine renderer process terminated.
|
||||
|
||||
Also see QWebEnginePage::RenderProcessTerminationStatus
|
||||
"""
|
||||
|
||||
#: Unknown render process status value gotten from Qt.
|
||||
unknown = -1
|
||||
#: The render process terminated normally.
|
||||
normal = 0
|
||||
#: The render process terminated with with a non-zero exit status.
|
||||
abnormal = 1
|
||||
#: The render process crashed, for example because of a segmentation fault.
|
||||
crashed = 2
|
||||
#: The render process was killed, for example by SIGKILL or task manager kill.
|
||||
killed = 3
|
||||
|
||||
|
||||
@attr.s
|
||||
|
|
@ -131,19 +143,17 @@ class TabData:
|
|||
splitter: InspectorSplitter used to show inspector inside the tab.
|
||||
"""
|
||||
|
||||
keep_icon = attr.ib(False) # type: bool
|
||||
viewing_source = attr.ib(False) # type: bool
|
||||
inspector = attr.ib(None) # type: typing.Optional[AbstractWebInspector]
|
||||
open_target = attr.ib(
|
||||
usertypes.ClickTarget.normal) # type: usertypes.ClickTarget
|
||||
override_target = attr.ib(
|
||||
None) # type: typing.Optional[usertypes.ClickTarget]
|
||||
pinned = attr.ib(False) # type: bool
|
||||
fullscreen = attr.ib(False) # type: bool
|
||||
netrc_used = attr.ib(False) # type: bool
|
||||
input_mode = attr.ib(usertypes.KeyMode.normal) # type: usertypes.KeyMode
|
||||
last_navigation = attr.ib(None) # type: usertypes.NavigationRequest
|
||||
splitter = attr.ib(None) # type: miscwidgets.InspectorSplitter
|
||||
keep_icon: bool = attr.ib(False)
|
||||
viewing_source: bool = attr.ib(False)
|
||||
inspector: Optional['AbstractWebInspector'] = attr.ib(None)
|
||||
open_target: usertypes.ClickTarget = attr.ib(usertypes.ClickTarget.normal)
|
||||
override_target: Optional[usertypes.ClickTarget] = attr.ib(None)
|
||||
pinned: bool = attr.ib(False)
|
||||
fullscreen: bool = attr.ib(False)
|
||||
netrc_used: bool = attr.ib(False)
|
||||
input_mode: usertypes.KeyMode = attr.ib(usertypes.KeyMode.normal)
|
||||
last_navigation: usertypes.NavigationRequest = attr.ib(None)
|
||||
splitter: miscwidgets.InspectorSplitter = attr.ib(None)
|
||||
|
||||
def should_show_icon(self) -> bool:
|
||||
return (config.val.tabs.favicons.show == 'always' or
|
||||
|
|
@ -154,13 +164,11 @@ class AbstractAction:
|
|||
|
||||
"""Attribute ``action`` of AbstractTab for Qt WebActions."""
|
||||
|
||||
# The class actions are defined on (QWeb{Engine,}Page)
|
||||
action_class = None # type: type
|
||||
# The type of the actions (QWeb{Engine,}Page.WebAction)
|
||||
action_base = None # type: type
|
||||
action_class: Type[Union['QWebPage', 'QWebEnginePage']]
|
||||
action_base: Type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']]
|
||||
|
||||
def __init__(self, tab: 'AbstractTab') -> None:
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
self._tab = tab
|
||||
|
||||
def exit_fullscreen(self) -> None:
|
||||
|
|
@ -211,7 +219,7 @@ class AbstractPrinting:
|
|||
"""Attribute ``printing`` of AbstractTab for printing the page."""
|
||||
|
||||
def __init__(self, tab: 'AbstractTab') -> None:
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
self._tab = tab
|
||||
|
||||
def check_pdf_support(self) -> None:
|
||||
|
|
@ -243,7 +251,7 @@ class AbstractPrinting:
|
|||
raise NotImplementedError
|
||||
|
||||
def to_printer(self, printer: QPrinter,
|
||||
callback: typing.Callable[[bool], None] = None) -> None:
|
||||
callback: Callable[[bool], None] = None) -> None:
|
||||
"""Print the tab.
|
||||
|
||||
Args:
|
||||
|
|
@ -295,13 +303,13 @@ class AbstractSearch(QObject):
|
|||
#: Signal emitted when an existing search was cleared.
|
||||
cleared = pyqtSignal()
|
||||
|
||||
_Callback = typing.Callable[[bool], None]
|
||||
_Callback = Callable[[bool], None]
|
||||
|
||||
def __init__(self, tab: 'AbstractTab', parent: QWidget = None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self.text = None # type: typing.Optional[str]
|
||||
self._widget = cast(QWidget, None)
|
||||
self.text: Optional[str] = None
|
||||
self.search_displayed = False
|
||||
|
||||
def _is_case_sensitive(self, ignore_case: usertypes.IgnoreCase) -> bool:
|
||||
|
|
@ -364,7 +372,7 @@ class AbstractZoom(QObject):
|
|||
def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
# Whether zoom was changed from the default.
|
||||
self._default_zoom_changed = False
|
||||
self._init_neighborlist()
|
||||
|
|
@ -384,9 +392,8 @@ class AbstractZoom(QObject):
|
|||
|
||||
It is a NeighborList with the zoom levels."""
|
||||
levels = config.val.zoom.levels
|
||||
self._neighborlist = usertypes.NeighborList(
|
||||
levels, mode=usertypes.NeighborList.Modes.edge
|
||||
) # type: usertypes.NeighborList[float]
|
||||
self._neighborlist: usertypes.NeighborList[float] = usertypes.NeighborList(
|
||||
levels, mode=usertypes.NeighborList.Modes.edge)
|
||||
self._neighborlist.fuzzyval = config.val.zoom.default
|
||||
|
||||
def apply_offset(self, offset: int) -> float:
|
||||
|
|
@ -440,9 +447,9 @@ class SelectionState(enum.Enum):
|
|||
NOTE: Names need to line up with SelectionState in caret.js!
|
||||
"""
|
||||
|
||||
none = 1
|
||||
normal = 2
|
||||
line = 3
|
||||
none = enum.auto()
|
||||
normal = enum.auto()
|
||||
line = enum.auto()
|
||||
|
||||
|
||||
class AbstractCaret(QObject):
|
||||
|
|
@ -455,14 +462,15 @@ class AbstractCaret(QObject):
|
|||
follow_selected_done = pyqtSignal()
|
||||
|
||||
def __init__(self,
|
||||
tab: 'AbstractTab',
|
||||
mode_manager: modeman.ModeManager,
|
||||
parent: QWidget = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
self._mode_manager = mode_manager
|
||||
mode_manager.entered.connect(self._on_mode_entered)
|
||||
mode_manager.left.connect(self._on_mode_left)
|
||||
# self._tab is set by subclasses so mypy knows its concrete type.
|
||||
self._tab = tab
|
||||
|
||||
def _on_mode_entered(self, mode: usertypes.KeyMode) -> None:
|
||||
raise NotImplementedError
|
||||
|
|
@ -521,7 +529,7 @@ class AbstractCaret(QObject):
|
|||
def drop_selection(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def selection(self, callback: typing.Callable[[str], None]) -> None:
|
||||
def selection(self, callback: Callable[[str], None]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def reverse_selection(self) -> None:
|
||||
|
|
@ -551,7 +559,7 @@ class AbstractScroller(QObject):
|
|||
def __init__(self, tab: 'AbstractTab', parent: QWidget = None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
if 'log-scroll-pos' in objects.debug_flags:
|
||||
self.perc_changed.connect(self._log_scroll_pos_change)
|
||||
|
||||
|
|
@ -619,11 +627,6 @@ class AbstractHistoryPrivate:
|
|||
|
||||
"""Private API related to the history."""
|
||||
|
||||
def __init__(self, tab: 'AbstractTab'):
|
||||
self._tab = tab
|
||||
self._history = typing.cast(
|
||||
typing.Union['QWebHistory', 'QWebEngineHistory'], None)
|
||||
|
||||
def serialize(self) -> bytes:
|
||||
"""Serialize into an opaque format understood by self.deserialize."""
|
||||
raise NotImplementedError
|
||||
|
|
@ -632,7 +635,7 @@ class AbstractHistoryPrivate:
|
|||
"""Deserialize from a format produced by self.serialize."""
|
||||
raise NotImplementedError
|
||||
|
||||
def load_items(self, items: typing.Sequence) -> None:
|
||||
def load_items(self, items: Sequence) -> None:
|
||||
"""Deserialize from a list of WebHistoryItems."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
@ -643,14 +646,13 @@ class AbstractHistory:
|
|||
|
||||
def __init__(self, tab: 'AbstractTab') -> None:
|
||||
self._tab = tab
|
||||
self._history = typing.cast(
|
||||
typing.Union['QWebHistory', 'QWebEngineHistory'], None)
|
||||
self.private_api = AbstractHistoryPrivate(tab)
|
||||
self._history = cast(Union['QWebHistory', 'QWebEngineHistory'], None)
|
||||
self.private_api = AbstractHistoryPrivate()
|
||||
|
||||
def __len__(self) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self) -> typing.Iterable:
|
||||
def __iter__(self) -> Iterable:
|
||||
raise NotImplementedError
|
||||
|
||||
def _check_count(self, count: int) -> None:
|
||||
|
|
@ -687,16 +689,16 @@ class AbstractHistory:
|
|||
def can_go_forward(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def _item_at(self, i: int) -> typing.Any:
|
||||
def _item_at(self, i: int) -> Any:
|
||||
raise NotImplementedError
|
||||
|
||||
def _go_to_item(self, item: typing.Any) -> None:
|
||||
def _go_to_item(self, item: Any) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def back_items(self) -> typing.List[typing.Any]:
|
||||
def back_items(self) -> List[Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
def forward_items(self) -> typing.List[typing.Any]:
|
||||
def forward_items(self) -> List[Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
|
@ -704,15 +706,13 @@ class AbstractElements:
|
|||
|
||||
"""Finding and handling of elements on the page."""
|
||||
|
||||
_MultiCallback = typing.Callable[
|
||||
[typing.Sequence['webelem.AbstractWebElement']], None]
|
||||
_SingleCallback = typing.Callable[
|
||||
[typing.Optional['webelem.AbstractWebElement']], None]
|
||||
_ErrorCallback = typing.Callable[[Exception], None]
|
||||
_MultiCallback = Callable[[Sequence['webelem.AbstractWebElement']], None]
|
||||
_SingleCallback = Callable[[Optional['webelem.AbstractWebElement']], None]
|
||||
_ErrorCallback = Callable[[Exception], None]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
# self._tab is set by subclasses so mypy knows its concrete type.
|
||||
def __init__(self, tab: 'AbstractTab') -> None:
|
||||
self._widget = cast(QWidget, None)
|
||||
self._tab = tab
|
||||
|
||||
def find_css(self, selector: str,
|
||||
callback: _MultiCallback,
|
||||
|
|
@ -772,7 +772,7 @@ class AbstractAudio(QObject):
|
|||
|
||||
def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
self._tab = tab
|
||||
|
||||
def set_muted(self, muted: bool, override: bool = False) -> None:
|
||||
|
|
@ -803,7 +803,7 @@ class AbstractTabPrivate:
|
|||
|
||||
def __init__(self, mode_manager: modeman.ModeManager,
|
||||
tab: 'AbstractTab') -> None:
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
self._tab = tab
|
||||
self._mode_manager = mode_manager
|
||||
|
||||
|
|
@ -821,7 +821,7 @@ class AbstractTabPrivate:
|
|||
return
|
||||
|
||||
def _auto_insert_mode_cb(
|
||||
elem: typing.Optional['webelem.AbstractWebElement']
|
||||
elem: Optional['webelem.AbstractWebElement']
|
||||
) -> None:
|
||||
"""Called from JS after finding the focused element."""
|
||||
if elem is None:
|
||||
|
|
@ -836,7 +836,7 @@ class AbstractTabPrivate:
|
|||
def clear_ssl_errors(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def networkaccessmanager(self) -> typing.Optional[QNetworkAccessManager]:
|
||||
def networkaccessmanager(self) -> Optional[QNetworkAccessManager]:
|
||||
"""Get the QNetworkAccessManager for this tab.
|
||||
|
||||
This is only implemented for QtWebKit.
|
||||
|
|
@ -928,7 +928,7 @@ class AbstractTab(QWidget):
|
|||
# Note that we remember hosts here, without scheme/port:
|
||||
# QtWebEngine/Chromium also only remembers hostnames, and certificates are
|
||||
# for a given hostname anyways.
|
||||
_insecure_hosts = set() # type: typing.Set[str]
|
||||
_insecure_hosts: Set[str] = set()
|
||||
|
||||
def __init__(self, *, win_id: int,
|
||||
mode_manager: modeman.ModeManager,
|
||||
|
|
@ -948,12 +948,12 @@ class AbstractTab(QWidget):
|
|||
|
||||
self.data = TabData()
|
||||
self._layout = miscwidgets.WrapperLayout(self)
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
self._progress = 0
|
||||
self._load_status = usertypes.LoadStatus.none
|
||||
self._tab_event_filter = eventfilter.TabEventFilter(
|
||||
self, parent=self)
|
||||
self.backend = None # type: typing.Optional[usertypes.Backend]
|
||||
self.backend: Optional[usertypes.Backend] = None
|
||||
|
||||
# If true, this tab has been requested to be removed (or is removed).
|
||||
self.pending_removal = False
|
||||
|
|
@ -1156,7 +1156,7 @@ class AbstractTab(QWidget):
|
|||
self.send_event(release_evt)
|
||||
|
||||
def dump_async(self,
|
||||
callback: typing.Callable[[str], None], *,
|
||||
callback: Callable[[str], None], *,
|
||||
plain: bool = False) -> None:
|
||||
"""Dump the current page's html asynchronously.
|
||||
|
||||
|
|
@ -1168,8 +1168,8 @@ class AbstractTab(QWidget):
|
|||
def run_js_async(
|
||||
self,
|
||||
code: str,
|
||||
callback: typing.Callable[[typing.Any], None] = None, *,
|
||||
world: typing.Union[usertypes.JsWorld, int] = None
|
||||
callback: Callable[[Any], None] = None, *,
|
||||
world: Union[usertypes.JsWorld, int] = None
|
||||
) -> None:
|
||||
"""Run javascript async.
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import os.path
|
||||
import shlex
|
||||
import functools
|
||||
import typing
|
||||
from typing import cast, Callable, Dict, Union
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QTabBar
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QEvent, QUrlQuery
|
||||
|
|
@ -600,15 +600,14 @@ class CommandDispatcher:
|
|||
widget = self._current_widget()
|
||||
url = self._current_url()
|
||||
|
||||
handlers = {
|
||||
handlers: Dict[str, Callable] = {
|
||||
'prev': functools.partial(navigate.prevnext, prev=True),
|
||||
'next': functools.partial(navigate.prevnext, prev=False),
|
||||
'up': navigate.path_up,
|
||||
'decrement': functools.partial(navigate.incdec,
|
||||
inc_or_dec='decrement'),
|
||||
'increment': functools.partial(navigate.incdec,
|
||||
inc_or_dec='increment'),
|
||||
} # type: typing.Dict[str, typing.Callable]
|
||||
'strip': navigate.strip,
|
||||
'decrement': functools.partial(navigate.incdec, inc_or_dec='decrement'),
|
||||
'increment': functools.partial(navigate.incdec, inc_or_dec='increment'),
|
||||
}
|
||||
|
||||
try:
|
||||
if where in ['prev', 'next']:
|
||||
|
|
@ -949,7 +948,7 @@ class CommandDispatcher:
|
|||
@cmdutils.argument('index', choices=['last', 'stack-next', 'stack-prev'],
|
||||
completion=miscmodels.tab_focus)
|
||||
@cmdutils.argument('count', value=cmdutils.Value.count)
|
||||
def tab_focus(self, index: typing.Union[str, int] = None,
|
||||
def tab_focus(self, index: Union[str, int] = None,
|
||||
count: int = None, no_last: bool = False) -> None:
|
||||
"""Select the tab given as argument/[count].
|
||||
|
||||
|
|
@ -993,7 +992,7 @@ class CommandDispatcher:
|
|||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', choices=['+', '-'])
|
||||
@cmdutils.argument('count', value=cmdutils.Value.count)
|
||||
def tab_move(self, index: typing.Union[str, int] = None,
|
||||
def tab_move(self, index: Union[str, int] = None,
|
||||
count: int = None) -> None:
|
||||
"""Move the current tab according to the argument and [count].
|
||||
|
||||
|
|
@ -1431,7 +1430,7 @@ class CommandDispatcher:
|
|||
query = QUrlQuery()
|
||||
query.addQueryItem('level', level)
|
||||
if plain:
|
||||
query.addQueryItem('plain', typing.cast(str, None))
|
||||
query.addQueryItem('plain', cast(str, None))
|
||||
|
||||
if logfilter:
|
||||
try:
|
||||
|
|
@ -1652,7 +1651,7 @@ class CommandDispatcher:
|
|||
url: bool = False,
|
||||
quiet: bool = False,
|
||||
*,
|
||||
world: typing.Union[usertypes.JsWorld, int] = None) -> None:
|
||||
world: Union[usertypes.JsWorld, int] = None) -> None:
|
||||
"""Evaluate a JavaScript string.
|
||||
|
||||
Args:
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import functools
|
|||
import pathlib
|
||||
import tempfile
|
||||
import enum
|
||||
import typing
|
||||
from typing import Any, Dict, IO, List, MutableSequence, Optional, Union
|
||||
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||
QTimer, QAbstractListModel, QUrl)
|
||||
|
|
@ -49,7 +49,7 @@ class ModelRole(enum.IntEnum):
|
|||
|
||||
|
||||
# Remember the last used directory
|
||||
last_used_directory = None # type: typing.Optional[str]
|
||||
last_used_directory: Optional[str] = None
|
||||
|
||||
# All REFRESH_INTERVAL milliseconds, speeds will be recalculated and downloads
|
||||
# redrawn.
|
||||
|
|
@ -228,7 +228,7 @@ def suggested_fn_from_title(url_path, title=None):
|
|||
ext_whitelist = [".html", ".htm", ".php", ""]
|
||||
_, ext = os.path.splitext(url_path)
|
||||
|
||||
suggested_fn = None # type: typing.Optional[str]
|
||||
suggested_fn: Optional[str] = None
|
||||
if ext.lower() in ext_whitelist and title:
|
||||
suggested_fn = utils.sanitize_filename(title, shorten=True)
|
||||
if not suggested_fn.lower().endswith((".html", ".htm")):
|
||||
|
|
@ -355,8 +355,7 @@ class DownloadItemStats(QObject):
|
|||
self.speed = 0
|
||||
self._last_done = 0
|
||||
samples = int(self.SPEED_AVG_WINDOW * (1000 / _REFRESH_INTERVAL))
|
||||
self._speed_avg = collections.deque(
|
||||
maxlen=samples) # type: typing.MutableSequence[float]
|
||||
self._speed_avg: MutableSequence[float] = collections.deque(maxlen=samples)
|
||||
|
||||
def update_speed(self):
|
||||
"""Recalculate the current download speed.
|
||||
|
|
@ -459,12 +458,14 @@ class AbstractDownloadItem(QObject):
|
|||
self.basename = '???'
|
||||
self.successful = False
|
||||
|
||||
self.fileobj = UnsupportedAttribute(
|
||||
) # type: typing.Union[UnsupportedAttribute, typing.IO[bytes], None]
|
||||
self.raw_headers = UnsupportedAttribute(
|
||||
) # type: typing.Union[UnsupportedAttribute, typing.Dict[bytes,bytes]]
|
||||
self.fileobj: Union[
|
||||
UnsupportedAttribute, IO[bytes], None
|
||||
] = UnsupportedAttribute()
|
||||
self.raw_headers: Union[
|
||||
UnsupportedAttribute, Dict[bytes, bytes]
|
||||
] = UnsupportedAttribute()
|
||||
|
||||
self._filename = None # type: typing.Optional[str]
|
||||
self._filename: Optional[str] = None
|
||||
self._dead = False
|
||||
|
||||
def __repr__(self):
|
||||
|
|
@ -877,7 +878,7 @@ class AbstractDownloadManager(QObject):
|
|||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.downloads = [] # type: typing.List[AbstractDownloadItem]
|
||||
self.downloads: List[AbstractDownloadItem] = []
|
||||
self._update_timer = usertypes.Timer(self, 'download-update')
|
||||
self._update_timer.timeout.connect(self._update_gui)
|
||||
self._update_timer.setInterval(_REFRESH_INTERVAL)
|
||||
|
|
@ -1251,7 +1252,7 @@ class DownloadModel(QAbstractListModel):
|
|||
|
||||
item = self[index.row()]
|
||||
if role == Qt.DisplayRole:
|
||||
data = str(item) # type: typing.Any
|
||||
data: Any = str(item)
|
||||
elif role == Qt.ForegroundRole:
|
||||
data = item.get_status_color('fg')
|
||||
elif role == Qt.BackgroundRole:
|
||||
|
|
@ -1297,7 +1298,7 @@ class TempDownloadManager:
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.files = [] # type: typing.MutableSequence[typing.IO[bytes]]
|
||||
self.files: MutableSequence[IO[bytes]] = []
|
||||
self._tmpdir = None
|
||||
|
||||
def cleanup(self):
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"""The ListView to display downloads in."""
|
||||
|
||||
import functools
|
||||
import typing
|
||||
from typing import Callable, MutableSequence, Tuple, Union
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
|
||||
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
||||
|
|
@ -54,10 +54,10 @@ def update_geometry(obj):
|
|||
QTimer.singleShot(0, _update_geometry)
|
||||
|
||||
|
||||
_ActionListType = typing.MutableSequence[
|
||||
typing.Union[
|
||||
typing.Tuple[None, None], # separator
|
||||
typing.Tuple[str, typing.Callable[[], None]],
|
||||
_ActionListType = MutableSequence[
|
||||
Union[
|
||||
Tuple[None, None], # separator
|
||||
Tuple[str, Callable[[], None]],
|
||||
]
|
||||
]
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ class DownloadView(QListView):
|
|||
item: The DownloadItem to get the actions for, or None.
|
||||
"""
|
||||
model = self.model()
|
||||
actions = [] # type: _ActionListType
|
||||
actions: _ActionListType = []
|
||||
if item is None:
|
||||
pass
|
||||
elif item.done:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import fnmatch
|
|||
import functools
|
||||
import glob
|
||||
import textwrap
|
||||
import typing
|
||||
from typing import cast, List, Sequence
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
|
@ -39,7 +39,7 @@ from qutebrowser.browser import downloads
|
|||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
gm_manager = typing.cast('GreasemonkeyManager', None)
|
||||
gm_manager = cast('GreasemonkeyManager', None)
|
||||
|
||||
|
||||
def _scripts_dir():
|
||||
|
|
@ -54,10 +54,10 @@ class GreasemonkeyScript:
|
|||
def __init__(self, properties, code, # noqa: C901 pragma: no mccabe
|
||||
filename=None):
|
||||
self._code = code
|
||||
self.includes = [] # type: typing.Sequence[str]
|
||||
self.matches = [] # type: typing.Sequence[str]
|
||||
self.excludes = [] # type: typing.Sequence[str]
|
||||
self.requires = [] # type: typing.Sequence[str]
|
||||
self.includes: Sequence[str] = []
|
||||
self.matches: Sequence[str] = []
|
||||
self.excludes: Sequence[str] = []
|
||||
self.requires: Sequence[str] = []
|
||||
self.description = None
|
||||
self.namespace = None
|
||||
self.run_at = None
|
||||
|
|
@ -259,11 +259,10 @@ class GreasemonkeyManager(QObject):
|
|||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._run_start = [] # type: typing.List[GreasemonkeyScript]
|
||||
self._run_end = [] # type: typing.List[GreasemonkeyScript]
|
||||
self._run_idle = [] # type: typing.List[GreasemonkeyScript]
|
||||
self._in_progress_dls = [
|
||||
] # type: typing.List[downloads.AbstractDownloadItem]
|
||||
self._run_start: List[GreasemonkeyScript] = []
|
||||
self._run_end: List[GreasemonkeyScript] = []
|
||||
self._run_idle: List[GreasemonkeyScript] = []
|
||||
self._in_progress_dls: List[downloads.AbstractDownloadItem] = []
|
||||
|
||||
self.load_scripts()
|
||||
|
||||
|
|
|
|||
|
|
@ -20,16 +20,17 @@
|
|||
"""A HintManager to draw hints over links."""
|
||||
|
||||
import collections
|
||||
import typing
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import html
|
||||
import enum
|
||||
from string import ascii_lowercase
|
||||
from typing import (TYPE_CHECKING, Callable, Dict, Iterable, Iterator, List, Mapping,
|
||||
MutableSequence, Optional, Sequence, Set)
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QUrl
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt, QUrl
|
||||
from PyQt5.QtWidgets import QLabel
|
||||
|
||||
from qutebrowser.config import config, configexc
|
||||
|
|
@ -38,14 +39,30 @@ from qutebrowser.browser import webelem, history
|
|||
from qutebrowser.commands import userscripts, runners
|
||||
from qutebrowser.api import cmdutils
|
||||
from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.browser import browsertab
|
||||
|
||||
|
||||
Target = enum.Enum('Target', ['normal', 'current', 'tab', 'tab_fg', 'tab_bg',
|
||||
'window', 'yank', 'yank_primary', 'run', 'fill',
|
||||
'hover', 'download', 'userscript', 'spawn',
|
||||
'delete', 'right_click'])
|
||||
class Target(enum.Enum):
|
||||
|
||||
"""What action to take on a hint."""
|
||||
|
||||
normal = enum.auto()
|
||||
current = enum.auto()
|
||||
tab = enum.auto()
|
||||
tab_fg = enum.auto()
|
||||
tab_bg = enum.auto()
|
||||
window = enum.auto()
|
||||
yank = enum.auto()
|
||||
yank_primary = enum.auto()
|
||||
run = enum.auto()
|
||||
fill = enum.auto()
|
||||
hover = enum.auto()
|
||||
download = enum.auto()
|
||||
userscript = enum.auto()
|
||||
spawn = enum.auto()
|
||||
delete = enum.auto()
|
||||
right_click = enum.auto()
|
||||
|
||||
|
||||
class HintingError(Exception):
|
||||
|
|
@ -164,22 +181,22 @@ class HintContext:
|
|||
group: The group of web elements to hint.
|
||||
"""
|
||||
|
||||
all_labels = attr.ib(attr.Factory(list)) # type: typing.List[HintLabel]
|
||||
labels = attr.ib(attr.Factory(dict)) # type: typing.Dict[str, HintLabel]
|
||||
target = attr.ib(None) # type: Target
|
||||
baseurl = attr.ib(None) # type: QUrl
|
||||
to_follow = attr.ib(None) # type: str
|
||||
rapid = attr.ib(False) # type: bool
|
||||
first_run = attr.ib(True) # type: bool
|
||||
add_history = attr.ib(False) # type: bool
|
||||
filterstr = attr.ib(None) # type: str
|
||||
args = attr.ib(attr.Factory(list)) # type: typing.List[str]
|
||||
tab = attr.ib(None) # type: browsertab.AbstractTab
|
||||
group = attr.ib(None) # type: str
|
||||
hint_mode = attr.ib(None) # type: str
|
||||
first = attr.ib(False) # type: bool
|
||||
all_labels: List[HintLabel] = attr.ib(attr.Factory(list))
|
||||
labels: Dict[str, HintLabel] = attr.ib(attr.Factory(dict))
|
||||
target: Target = attr.ib(None)
|
||||
baseurl: QUrl = attr.ib(None)
|
||||
to_follow: str = attr.ib(None)
|
||||
rapid: bool = attr.ib(False)
|
||||
first_run: bool = attr.ib(True)
|
||||
add_history: bool = attr.ib(False)
|
||||
filterstr: str = attr.ib(None)
|
||||
args: List[str] = attr.ib(attr.Factory(list))
|
||||
tab: 'browsertab.AbstractTab' = attr.ib(None)
|
||||
group: str = attr.ib(None)
|
||||
hint_mode: str = attr.ib(None)
|
||||
first: bool = attr.ib(False)
|
||||
|
||||
def get_args(self, urlstr: str) -> typing.Sequence[str]:
|
||||
def get_args(self, urlstr: str) -> Sequence[str]:
|
||||
"""Get the arguments, with {hint-url} replaced by the given URL."""
|
||||
args = []
|
||||
for arg in self.args:
|
||||
|
|
@ -336,8 +353,8 @@ class HintActions:
|
|||
commandrunner.run_safely('spawn ' + ' '.join(args))
|
||||
|
||||
|
||||
_ElemsType = typing.Sequence[webelem.AbstractWebElement]
|
||||
_HintStringsType = typing.MutableSequence[str]
|
||||
_ElemsType = Sequence[webelem.AbstractWebElement]
|
||||
_HintStringsType = MutableSequence[str]
|
||||
|
||||
|
||||
class HintManager(QObject):
|
||||
|
|
@ -353,7 +370,7 @@ class HintManager(QObject):
|
|||
_tab_id: The tab ID this HintManager is associated with.
|
||||
|
||||
Signals:
|
||||
See HintActions
|
||||
set_text: Request for the statusbar to change its text.
|
||||
"""
|
||||
|
||||
HINT_TEXTS = {
|
||||
|
|
@ -375,11 +392,13 @@ class HintManager(QObject):
|
|||
Target.delete: "Delete an element",
|
||||
}
|
||||
|
||||
set_text = pyqtSignal(str)
|
||||
|
||||
def __init__(self, win_id: int, parent: QObject = None) -> None:
|
||||
"""Constructor."""
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._context = None # type: typing.Optional[HintContext]
|
||||
self._context: Optional[HintContext] = None
|
||||
self._word_hinter = WordHinter()
|
||||
|
||||
self._actions = HintActions(win_id)
|
||||
|
|
@ -402,10 +421,8 @@ class HintManager(QObject):
|
|||
for label in self._context.all_labels:
|
||||
label.cleanup()
|
||||
|
||||
text = self._get_text()
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
message_bridge.maybe_reset_text(text)
|
||||
self.set_text.emit('')
|
||||
|
||||
self._context = None
|
||||
|
||||
def _hint_strings(self, elems: _ElemsType) -> _HintStringsType:
|
||||
|
|
@ -511,12 +528,10 @@ class HintManager(QObject):
|
|||
Return:
|
||||
A list of shuffled hint strings.
|
||||
"""
|
||||
buckets = [
|
||||
[] for i in range(length)
|
||||
] # type: typing.Sequence[_HintStringsType]
|
||||
buckets: Sequence[_HintStringsType] = [[] for i in range(length)]
|
||||
for i, hint in enumerate(hints):
|
||||
buckets[i % len(buckets)].append(hint)
|
||||
result = [] # type: _HintStringsType
|
||||
result: _HintStringsType = []
|
||||
for bucket in buckets:
|
||||
result += bucket
|
||||
return result
|
||||
|
|
@ -541,7 +556,7 @@ class HintManager(QObject):
|
|||
A hint string.
|
||||
"""
|
||||
base = len(chars)
|
||||
hintstr = [] # type: typing.MutableSequence[str]
|
||||
hintstr: MutableSequence[str] = []
|
||||
remainder = 0
|
||||
while True:
|
||||
remainder = number % base
|
||||
|
|
@ -636,9 +651,7 @@ class HintManager(QObject):
|
|||
modeman.enter(self._win_id, usertypes.KeyMode.hint,
|
||||
'HintManager.start')
|
||||
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
message_bridge.set_text(self._get_text())
|
||||
self.set_text.emit(self._get_text())
|
||||
|
||||
if self._context.first:
|
||||
self._fire(strings[0])
|
||||
|
|
@ -771,7 +784,7 @@ class HintManager(QObject):
|
|||
error_cb=lambda err: message.error(str(err)),
|
||||
only_visible=True)
|
||||
|
||||
def _get_hint_mode(self, mode: typing.Optional[str]) -> str:
|
||||
def _get_hint_mode(self, mode: Optional[str]) -> str:
|
||||
"""Get the hinting mode to use based on a mode argument."""
|
||||
if mode is None:
|
||||
return config.val.hints.mode
|
||||
|
|
@ -783,7 +796,7 @@ class HintManager(QObject):
|
|||
raise cmdutils.CommandError("Invalid mode: {}".format(e))
|
||||
return mode
|
||||
|
||||
def current_mode(self) -> typing.Optional[str]:
|
||||
def current_mode(self) -> Optional[str]:
|
||||
"""Return the currently active hinting mode (or None otherwise)."""
|
||||
if self._context is None:
|
||||
return None
|
||||
|
|
@ -794,7 +807,7 @@ class HintManager(QObject):
|
|||
self,
|
||||
keystr: str = "",
|
||||
filterstr: str = "",
|
||||
visible: typing.Mapping[str, HintLabel] = None
|
||||
visible: Mapping[str, HintLabel] = None
|
||||
) -> None:
|
||||
"""Handle the auto_follow option."""
|
||||
assert self._context is not None
|
||||
|
|
@ -856,7 +869,7 @@ class HintManager(QObject):
|
|||
pass
|
||||
self._handle_auto_follow(keystr=keystr)
|
||||
|
||||
def filter_hints(self, filterstr: typing.Optional[str]) -> None:
|
||||
def filter_hints(self, filterstr: Optional[str]) -> None:
|
||||
"""Filter displayed hints according to a text.
|
||||
|
||||
Args:
|
||||
|
|
@ -1027,7 +1040,7 @@ class WordHinter:
|
|||
|
||||
def __init__(self) -> None:
|
||||
# will be initialized on first use.
|
||||
self.words = set() # type: typing.Set[str]
|
||||
self.words: Set[str] = set()
|
||||
self.dictionary = None
|
||||
|
||||
def ensure_initialized(self) -> None:
|
||||
|
|
@ -1059,10 +1072,10 @@ class WordHinter:
|
|||
|
||||
def extract_tag_words(
|
||||
self, elem: webelem.AbstractWebElement
|
||||
) -> typing.Iterator[str]:
|
||||
) -> Iterator[str]:
|
||||
"""Extract tag words form the given element."""
|
||||
_extractor_type = typing.Callable[[webelem.AbstractWebElement], str]
|
||||
attr_extractors = {
|
||||
_extractor_type = Callable[[webelem.AbstractWebElement], str]
|
||||
attr_extractors: Mapping[str, _extractor_type] = {
|
||||
"alt": lambda elem: elem["alt"],
|
||||
"name": lambda elem: elem["name"],
|
||||
"title": lambda elem: elem["title"],
|
||||
|
|
@ -1070,7 +1083,7 @@ class WordHinter:
|
|||
"src": lambda elem: elem["src"].split('/')[-1],
|
||||
"href": lambda elem: elem["href"].split('/')[-1],
|
||||
"text": str,
|
||||
} # type: typing.Mapping[str, _extractor_type]
|
||||
}
|
||||
|
||||
extractable_attrs = collections.defaultdict(list, {
|
||||
"img": ["alt", "title", "src"],
|
||||
|
|
@ -1086,8 +1099,8 @@ class WordHinter:
|
|||
|
||||
def tag_words_to_hints(
|
||||
self,
|
||||
words: typing.Iterable[str]
|
||||
) -> typing.Iterator[str]:
|
||||
words: Iterable[str]
|
||||
) -> Iterator[str]:
|
||||
"""Take words and transform them to proper hints if possible."""
|
||||
for candidate in words:
|
||||
if not candidate:
|
||||
|
|
@ -1098,20 +1111,20 @@ class WordHinter:
|
|||
if 4 < match.end() - match.start() < 8:
|
||||
yield candidate[match.start():match.end()].lower()
|
||||
|
||||
def any_prefix(self, hint: str, existing: typing.Iterable[str]) -> bool:
|
||||
def any_prefix(self, hint: str, existing: Iterable[str]) -> bool:
|
||||
return any(hint.startswith(e) or e.startswith(hint) for e in existing)
|
||||
|
||||
def filter_prefixes(
|
||||
self,
|
||||
hints: typing.Iterable[str],
|
||||
existing: typing.Iterable[str]
|
||||
) -> typing.Iterator[str]:
|
||||
hints: Iterable[str],
|
||||
existing: Iterable[str]
|
||||
) -> Iterator[str]:
|
||||
"""Filter hints which don't start with the given prefix."""
|
||||
return (h for h in hints if not self.any_prefix(h, existing))
|
||||
|
||||
def new_hint_for(self, elem: webelem.AbstractWebElement,
|
||||
existing: typing.Iterable[str],
|
||||
fallback: typing.Iterable[str]) -> typing.Optional[str]:
|
||||
existing: Iterable[str],
|
||||
fallback: Iterable[str]) -> Optional[str]:
|
||||
"""Return a hint for elem, not conflicting with the existing."""
|
||||
new = self.tag_words_to_hints(self.extract_tag_words(elem))
|
||||
new_no_prefixes = self.filter_prefixes(new, existing)
|
||||
|
|
@ -1135,7 +1148,7 @@ class WordHinter:
|
|||
"""
|
||||
self.ensure_initialized()
|
||||
hints = []
|
||||
used_hints = set() # type: typing.Set[str]
|
||||
used_hints: Set[str] = set()
|
||||
words = iter(self.words)
|
||||
for elem in elems:
|
||||
hint = self.new_hint_for(elem, used_hints, words)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import os
|
||||
import time
|
||||
import contextlib
|
||||
import typing
|
||||
from typing import cast, Mapping, MutableSequence
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal
|
||||
from PyQt5.QtWidgets import QProgressDialog, QApplication
|
||||
|
|
@ -35,7 +35,7 @@ from qutebrowser.misc import objects, sql
|
|||
|
||||
# increment to indicate that HistoryCompletion must be regenerated
|
||||
_USER_VERSION = 2
|
||||
web_history = typing.cast('WebHistory', None)
|
||||
web_history = cast('WebHistory', None)
|
||||
|
||||
|
||||
class HistoryProgress:
|
||||
|
|
@ -208,11 +208,11 @@ class WebHistory(sql.SqlTable):
|
|||
return any(pattern.matches(url) for pattern in patterns)
|
||||
|
||||
def _rebuild_completion(self):
|
||||
data = {
|
||||
data: Mapping[str, MutableSequence[str]] = {
|
||||
'url': [],
|
||||
'title': [],
|
||||
'last_atime': []
|
||||
} # type: typing.Mapping[str, typing.MutableSequence[str]]
|
||||
}
|
||||
# select the latest entry for each url
|
||||
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
|
||||
'WHERE NOT redirect and url NOT LIKE "qute://back%" '
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
import base64
|
||||
import binascii
|
||||
import typing
|
||||
import enum
|
||||
from typing import cast, Optional
|
||||
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent
|
||||
|
|
@ -65,11 +65,11 @@ class Position(enum.Enum):
|
|||
|
||||
"""Where the inspector is shown."""
|
||||
|
||||
right = 1
|
||||
left = 2
|
||||
top = 3
|
||||
bottom = 4
|
||||
window = 5
|
||||
right = enum.auto()
|
||||
left = enum.auto()
|
||||
top = enum.auto()
|
||||
bottom = enum.auto()
|
||||
window = enum.auto()
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
|
@ -119,10 +119,10 @@ class AbstractWebInspector(QWidget):
|
|||
win_id: int,
|
||||
parent: QWidget = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
self._layout = miscwidgets.WrapperLayout(self)
|
||||
self._splitter = splitter
|
||||
self._position = None # type: typing.Optional[Position]
|
||||
self._position: Optional[Position] = None
|
||||
self._win_id = win_id
|
||||
|
||||
self._event_filter = _EventFilter(parent=self)
|
||||
|
|
@ -163,7 +163,7 @@ class AbstractWebInspector(QWidget):
|
|||
modeman.enter(self._win_id, usertypes.KeyMode.insert,
|
||||
reason='Inspector clicked', only_if_normal=True)
|
||||
|
||||
def set_position(self, position: typing.Optional[Position]) -> None:
|
||||
def set_position(self, position: Optional[Position]) -> None:
|
||||
"""Set the position of the inspector.
|
||||
|
||||
If the position is None, the last known position is used.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import re
|
||||
import posixpath
|
||||
import typing
|
||||
from typing import Optional, Set
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
|
|
@ -97,9 +97,9 @@ def incdec(url, count, inc_or_dec):
|
|||
window: Open the link in a new window.
|
||||
"""
|
||||
urlutils.ensure_valid(url)
|
||||
segments = (
|
||||
segments: Optional[Set[str]] = (
|
||||
set(config.val.url.incdec_segments)
|
||||
) # type: typing.Optional[typing.Set[str]]
|
||||
)
|
||||
|
||||
if segments is None:
|
||||
segments = {'path', 'query'}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import sys
|
||||
import functools
|
||||
import typing
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QUrl
|
||||
from PyQt5.QtNetwork import (QNetworkProxy, QNetworkRequest, QHostInfo,
|
||||
|
|
@ -66,7 +66,7 @@ def _js_slot(*args):
|
|||
return self._error_con.callAsConstructor([e])
|
||||
# pylint: enable=protected-access
|
||||
|
||||
deco = pyqtSlot(*args, result=QJSValue) # type: ignore[arg-type]
|
||||
deco = pyqtSlot(*args, result=QJSValue)
|
||||
return deco(new_method)
|
||||
return _decorator
|
||||
|
||||
|
|
@ -251,8 +251,7 @@ class PACFetcher(QObject):
|
|||
url.setScheme(url.scheme()[len(pac_prefix):])
|
||||
|
||||
self._pac_url = url
|
||||
self._manager = QNetworkAccessManager(
|
||||
) # type: typing.Optional[QNetworkAccessManager]
|
||||
self._manager: Optional[QNetworkAccessManager] = QNetworkAccessManager()
|
||||
self._manager.setProxy(QNetworkProxy(QNetworkProxy.NoProxy))
|
||||
self._pac = None
|
||||
self._error_message = None
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import io
|
|||
import os.path
|
||||
import shutil
|
||||
import functools
|
||||
import typing
|
||||
from typing import Dict, IO, Optional
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, QUrl
|
||||
|
|
@ -92,8 +92,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
reply: The QNetworkReply to download.
|
||||
"""
|
||||
super().__init__(manager=manager, parent=manager)
|
||||
self.fileobj = None # type: typing.Optional[typing.IO[bytes]]
|
||||
self.raw_headers = {} # type: typing.Dict[bytes, bytes]
|
||||
self.fileobj: Optional[IO[bytes]] = None
|
||||
self.raw_headers: Dict[bytes, bytes] = {}
|
||||
|
||||
self._autoclose = True
|
||||
self._retry_info = None
|
||||
|
|
|
|||
|
|
@ -31,15 +31,8 @@ import time
|
|||
import textwrap
|
||||
import urllib
|
||||
import collections
|
||||
import base64
|
||||
import typing
|
||||
from typing import TypeVar, Callable, Union, Tuple
|
||||
|
||||
try:
|
||||
import secrets
|
||||
except ImportError:
|
||||
# New in Python 3.6
|
||||
secrets = None # type: ignore[assignment]
|
||||
import secrets
|
||||
from typing import TypeVar, Callable, Dict, List, Optional, Union, Sequence, Tuple
|
||||
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl, qVersion
|
||||
|
||||
|
|
@ -112,7 +105,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
|||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
self._function = None # type: typing.Optional[typing.Callable]
|
||||
self._function: Optional[Callable] = None
|
||||
|
||||
def __call__(self, function: _Handler) -> _Handler:
|
||||
self._function = function
|
||||
|
|
@ -125,7 +118,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
|||
return self._function(*args, **kwargs)
|
||||
|
||||
|
||||
def data_for_url(url: QUrl) -> typing.Tuple[str, bytes]:
|
||||
def data_for_url(url: QUrl) -> Tuple[str, bytes]:
|
||||
"""Get the data to show for the given URL.
|
||||
|
||||
Args:
|
||||
|
|
@ -199,8 +192,7 @@ def qute_bookmarks(_url: QUrl) -> _HandlerRet:
|
|||
@add_handler('tabs')
|
||||
def qute_tabs(_url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://tabs. Display information about all open tabs."""
|
||||
tabs = collections.defaultdict(
|
||||
list) # type: typing.Dict[str, typing.List[typing.Tuple[str, str]]]
|
||||
tabs: Dict[str, List[Tuple[str, str]]] = collections.defaultdict(list)
|
||||
for win_id, window in objreg.window_registry.items():
|
||||
if sip.isdeleted(window):
|
||||
continue
|
||||
|
|
@ -221,7 +213,7 @@ def qute_tabs(_url: QUrl) -> _HandlerRet:
|
|||
def history_data(
|
||||
start_time: float,
|
||||
offset: int = None
|
||||
) -> typing.Sequence[typing.Dict[str, typing.Union[str, int]]]:
|
||||
) -> Sequence[Dict[str, Union[str, int]]]:
|
||||
"""Return history data.
|
||||
|
||||
Arguments:
|
||||
|
|
@ -355,7 +347,7 @@ def qute_gpl(_url: QUrl) -> _HandlerRet:
|
|||
return 'text/html', utils.read_file('html/license.html')
|
||||
|
||||
|
||||
def _asciidoc_fallback_path(html_path: str) -> typing.Optional[str]:
|
||||
def _asciidoc_fallback_path(html_path: str) -> Optional[str]:
|
||||
"""Fall back to plaintext asciidoc if the HTML is unavailable."""
|
||||
path = html_path.replace('.html', '.asciidoc')
|
||||
try:
|
||||
|
|
@ -449,12 +441,7 @@ def qute_settings(url: QUrl) -> _HandlerRet:
|
|||
# Requests to qute://settings/set should only be allowed from
|
||||
# qute://settings. As an additional security precaution, we generate a CSRF
|
||||
# token to use here.
|
||||
if secrets:
|
||||
csrf_token = secrets.token_urlsafe()
|
||||
else:
|
||||
# On Python < 3.6, from secrets.py
|
||||
token = base64.urlsafe_b64encode(os.urandom(32))
|
||||
csrf_token = token.rstrip(b'=').decode('ascii')
|
||||
csrf_token = secrets.token_urlsafe()
|
||||
|
||||
src = jinja.render('settings.html', title='settings',
|
||||
configdata=configdata,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import os
|
||||
import html
|
||||
import netrc
|
||||
import typing
|
||||
from typing import Callable, Mapping
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
|
|
@ -134,13 +134,13 @@ def javascript_alert(url, js_msg, abort_on, *, escape_msg=True):
|
|||
|
||||
# Needs to line up with the values allowed for the
|
||||
# content.javascript.log setting.
|
||||
_JS_LOGMAP = {
|
||||
_JS_LOGMAP: Mapping[str, Callable[[str], None]] = {
|
||||
'none': lambda arg: None,
|
||||
'debug': log.js.debug,
|
||||
'info': log.js.info,
|
||||
'warning': log.js.warning,
|
||||
'error': log.js.error,
|
||||
} # type: typing.Mapping[str, typing.Callable[[str], None]]
|
||||
}
|
||||
|
||||
|
||||
def javascript_log_message(level, source, line, msg):
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class SignalFilter(QObject):
|
|||
"""Factory for partial _filter_signals functions.
|
||||
|
||||
Args:
|
||||
signal: The pyqtSignal to filter.
|
||||
signal: The pyqtBoundSignal to filter.
|
||||
tab: The WebView to create filters for.
|
||||
|
||||
Return:
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import os.path
|
|||
import html
|
||||
import functools
|
||||
import collections
|
||||
import typing
|
||||
from typing import MutableMapping
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, QObject
|
||||
|
||||
|
|
@ -78,8 +78,7 @@ class UrlMarkManager(QObject):
|
|||
"""Initialize and read quickmarks."""
|
||||
super().__init__(parent)
|
||||
|
||||
self.marks = collections.OrderedDict(
|
||||
) # type: typing.MutableMapping[str, str]
|
||||
self.marks: MutableMapping[str, str] = collections.OrderedDict()
|
||||
|
||||
self._init_lineparser()
|
||||
for line in self._lineparser:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""Generic web element related code."""
|
||||
|
||||
import typing
|
||||
from typing import cast, TYPE_CHECKING, Iterator, Optional, Set, Union
|
||||
import collections.abc
|
||||
|
||||
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer, QRect, QPoint
|
||||
|
|
@ -29,11 +29,11 @@ from qutebrowser.config import config
|
|||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.browser import browsertab
|
||||
|
||||
|
||||
JsValueType = typing.Union[int, float, str, None]
|
||||
JsValueType = Union[int, float, str, None]
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
|
@ -80,7 +80,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||
def __delitem__(self, key: str) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self) -> typing.Iterator[str]:
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
raise NotImplementedError
|
||||
|
||||
def __len__(self) -> int:
|
||||
|
|
@ -88,8 +88,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||
|
||||
def __repr__(self) -> str:
|
||||
try:
|
||||
html = utils.compact_text(
|
||||
self.outer_xml(), 500) # type: typing.Optional[str]
|
||||
html: Optional[str] = utils.compact_text(self.outer_xml(), 500)
|
||||
except Error:
|
||||
html = None
|
||||
return utils.get_repr(self, html=html)
|
||||
|
|
@ -102,7 +101,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||
"""Get the geometry for this element."""
|
||||
raise NotImplementedError
|
||||
|
||||
def classes(self) -> typing.Set[str]:
|
||||
def classes(self) -> Set[str]:
|
||||
"""Get a set of classes assigned to this element."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
@ -282,7 +281,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||
"""Remove target from link."""
|
||||
raise NotImplementedError
|
||||
|
||||
def resolve_url(self, baseurl: QUrl) -> typing.Optional[QUrl]:
|
||||
def resolve_url(self, baseurl: QUrl) -> Optional[QUrl]:
|
||||
"""Resolve the URL in the element's src/href attribute.
|
||||
|
||||
Args:
|
||||
|
|
@ -357,16 +356,12 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||
else:
|
||||
target_modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
|
||||
|
||||
modifiers = typing.cast(Qt.KeyboardModifiers,
|
||||
target_modifiers[click_target])
|
||||
modifiers = cast(Qt.KeyboardModifiers, 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.MouseMove, pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier),
|
||||
QMouseEvent(QEvent.MouseButtonPress, pos, button, button, modifiers),
|
||||
QMouseEvent(QEvent.MouseButtonRelease, pos, button, Qt.NoButton, modifiers),
|
||||
]
|
||||
|
||||
for evt in events:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,307 @@
|
|||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2020 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Get darkmode arguments to pass to Qt.
|
||||
|
||||
Overview of blink setting names based on the Qt version:
|
||||
|
||||
Qt 5.10
|
||||
-------
|
||||
|
||||
First implementation, called "high contrast mode".
|
||||
|
||||
- highContrastMode (kOff/kSimpleInvertForTesting/kInvertBrightness/kInvertLightness)
|
||||
- highContrastGrayscale (bool)
|
||||
- highContrastContrast (float)
|
||||
- highContractImagePolicy (kFilterAll/kFilterNone)
|
||||
|
||||
Qt 5.11, 5.12, 5.13
|
||||
-------------------
|
||||
|
||||
New "smart" image policy.
|
||||
|
||||
- Mode/Grayscale/Contrast as above
|
||||
- highContractImagePolicy (kFilterAll/kFilterNone/kFilterSmart [new!])
|
||||
|
||||
Qt 5.14
|
||||
-------
|
||||
|
||||
Renamed to "darkMode".
|
||||
|
||||
- darkMode (kOff/kSimpleInvertForTesting/kInvertBrightness/kInvertLightness/
|
||||
kInvertLightnessLAB [new!])
|
||||
- darkModeGrayscale (bool)
|
||||
- darkModeContrast (float)
|
||||
- darkModeImagePolicy (kFilterAll/kFilterNone/kFilterSmart)
|
||||
- darkModePagePolicy (kFilterAll/kFilterByBackground) [new!]
|
||||
- darkModeTextBrightnessThreshold (int) [new!]
|
||||
- darkModeBackgroundBrightnessThreshold (int) [new!]
|
||||
- darkModeImageGrayscale (float) [new!]
|
||||
|
||||
Qt 5.15.0 and 5.15.1
|
||||
--------------------
|
||||
|
||||
"darkMode" split into "darkModeEnabled" and "darkModeInversionAlgorithm".
|
||||
|
||||
- darkModeEnabled (bool) [new!]
|
||||
- darkModeInversionAlgorithm (kSimpleInvertForTesting/kInvertBrightness/
|
||||
kInvertLightness/kInvertLightnessLAB)
|
||||
- Rest (except darkMode) as above.
|
||||
- NOTE: smart image policy is broken with Qt 5.15.0!
|
||||
|
||||
Qt 5.15.2
|
||||
---------
|
||||
|
||||
Prefix changed to "forceDarkMode".
|
||||
|
||||
- As with Qt 5.15.0 / .1, but with "forceDarkMode" as prefix.
|
||||
"""
|
||||
|
||||
import enum
|
||||
from typing import Any, Iterable, Iterator, Mapping, Optional, Set, Tuple, Union
|
||||
|
||||
try:
|
||||
from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION
|
||||
except ImportError: # pragma: no cover
|
||||
# Added in PyQt 5.13
|
||||
PYQT_WEBENGINE_VERSION = None # type: ignore[assignment]
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, qtutils, utils, log
|
||||
|
||||
|
||||
class Variant(enum.Enum):
|
||||
|
||||
"""A dark mode variant."""
|
||||
|
||||
unavailable = enum.auto()
|
||||
qt_510 = enum.auto()
|
||||
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()
|
||||
|
||||
|
||||
# Mapping from a colors.webpage.darkmode.algorithm setting value to
|
||||
# Chromium's DarkModeInversionAlgorithm enum values.
|
||||
_ALGORITHMS = {
|
||||
# 0: kOff (not exposed)
|
||||
# 1: kSimpleInvertForTesting (not exposed)
|
||||
'brightness-rgb': 2, # kInvertBrightness
|
||||
'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']
|
||||
|
||||
# Mapping from a colors.webpage.darkmode.policy.images setting value to
|
||||
# Chromium's DarkModeImagePolicy enum values.
|
||||
_IMAGE_POLICIES = {
|
||||
'always': 0, # kFilterAll
|
||||
'never': 1, # kFilterNone
|
||||
'smart': 2, # kFilterSmart
|
||||
}
|
||||
# Image policy smart is not available with Qt 5.10
|
||||
_IMAGE_POLICIES_QT_510 = _IMAGE_POLICIES.copy()
|
||||
_IMAGE_POLICIES_QT_510['smart'] = _IMAGE_POLICIES['never']
|
||||
|
||||
# Mapping from a colors.webpage.darkmode.policy.page setting value to
|
||||
# Chromium's DarkModePagePolicy enum values.
|
||||
_PAGE_POLICIES = {
|
||||
'always': 0, # kFilterAll
|
||||
'smart': 1, # kFilterByBackground
|
||||
}
|
||||
|
||||
_BOOLS = {
|
||||
True: 'true',
|
||||
False: 'false',
|
||||
}
|
||||
|
||||
_DarkModeSettingsType = Iterable[
|
||||
Tuple[
|
||||
str, # qutebrowser option name
|
||||
str, # darkmode setting name
|
||||
# Mapping from the config value to a string (or something convertable
|
||||
# to a string) which gets passed to Chromium.
|
||||
Optional[Mapping[Any, Union[str, int]]],
|
||||
],
|
||||
]
|
||||
|
||||
_DarkModeDefinitionType = Tuple[_DarkModeSettingsType, Set[str]]
|
||||
|
||||
_QT_514_SETTINGS = [
|
||||
('policy.images', 'darkModeImagePolicy', _IMAGE_POLICIES),
|
||||
('contrast', 'darkModeContrast', None),
|
||||
('grayscale.all', 'darkModeGrayscale', _BOOLS),
|
||||
|
||||
('policy.page', 'darkModePagePolicy', _PAGE_POLICIES),
|
||||
('threshold.text', 'darkModeTextBrightnessThreshold', None),
|
||||
('threshold.background', 'darkModeBackgroundBrightnessThreshold', None),
|
||||
('grayscale.images', 'darkModeImageGrayscale', None),
|
||||
]
|
||||
|
||||
# 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.
|
||||
|
||||
_DARK_MODE_DEFINITIONS: Mapping[Variant, _DarkModeDefinitionType] = {
|
||||
Variant.unavailable: ([], set()),
|
||||
|
||||
Variant.qt_515_2: ([
|
||||
# 'darkMode' renamed to 'forceDarkMode'
|
||||
('enabled', 'forceDarkModeEnabled', _BOOLS),
|
||||
('algorithm', 'forceDarkModeInversionAlgorithm', _ALGORITHMS),
|
||||
|
||||
('policy.images', 'forceDarkModeImagePolicy', _IMAGE_POLICIES),
|
||||
('contrast', 'forceDarkModeContrast', None),
|
||||
('grayscale.all', 'forceDarkModeGrayscale', _BOOLS),
|
||||
|
||||
('policy.page', 'forceDarkModePagePolicy', _PAGE_POLICIES),
|
||||
('threshold.text', 'forceDarkModeTextBrightnessThreshold', None),
|
||||
(
|
||||
'threshold.background',
|
||||
'forceDarkModeBackgroundBrightnessThreshold',
|
||||
None
|
||||
),
|
||||
('grayscale.images', 'forceDarkModeImageGrayscale', None),
|
||||
], {'enabled', 'policy.images'}),
|
||||
|
||||
Variant.qt_515_1: ([
|
||||
# 'policy.images' mandatory again
|
||||
('enabled', 'darkModeEnabled', _BOOLS),
|
||||
('algorithm', 'darkModeInversionAlgorithm', _ALGORITHMS),
|
||||
|
||||
('policy.images', 'darkModeImagePolicy', _IMAGE_POLICIES),
|
||||
('contrast', 'darkModeContrast', None),
|
||||
('grayscale.all', 'darkModeGrayscale', _BOOLS),
|
||||
|
||||
('policy.page', 'darkModePagePolicy', _PAGE_POLICIES),
|
||||
('threshold.text', 'darkModeTextBrightnessThreshold', None),
|
||||
('threshold.background', 'darkModeBackgroundBrightnessThreshold', None),
|
||||
('grayscale.images', 'darkModeImageGrayscale', None),
|
||||
], {'enabled', 'policy.images'}),
|
||||
|
||||
Variant.qt_515_0: ([
|
||||
# 'policy.images' not mandatory because it's broken
|
||||
('enabled', 'darkModeEnabled', _BOOLS),
|
||||
('algorithm', 'darkModeInversionAlgorithm', _ALGORITHMS),
|
||||
|
||||
('policy.images', 'darkModeImagePolicy', _IMAGE_POLICIES),
|
||||
('contrast', 'darkModeContrast', None),
|
||||
('grayscale.all', 'darkModeGrayscale', _BOOLS),
|
||||
|
||||
('policy.page', 'darkModePagePolicy', _PAGE_POLICIES),
|
||||
('threshold.text', 'darkModeTextBrightnessThreshold', None),
|
||||
('threshold.background', 'darkModeBackgroundBrightnessThreshold', None),
|
||||
('grayscale.images', 'darkModeImageGrayscale', None),
|
||||
], {'enabled'}),
|
||||
|
||||
Variant.qt_514: ([
|
||||
('algorithm', 'darkMode', _ALGORITHMS), # new: kInvertLightnessLAB
|
||||
|
||||
('policy.images', 'darkModeImagePolicy', _IMAGE_POLICIES),
|
||||
('contrast', 'darkModeContrast', None),
|
||||
('grayscale.all', 'darkModeGrayscale', _BOOLS),
|
||||
|
||||
('policy.page', 'darkModePagePolicy', _PAGE_POLICIES),
|
||||
('threshold.text', 'darkModeTextBrightnessThreshold', None),
|
||||
('threshold.background', 'darkModeBackgroundBrightnessThreshold', None),
|
||||
('grayscale.images', 'darkModeImageGrayscale', None),
|
||||
], {'algorithm', 'policy.images'}),
|
||||
|
||||
Variant.qt_511_to_513: ([
|
||||
('algorithm', 'highContrastMode', _ALGORITHMS_BEFORE_QT_514),
|
||||
|
||||
('policy.images', 'highContrastImagePolicy', _IMAGE_POLICIES), # new: smart
|
||||
('contrast', 'highContrastContrast', None),
|
||||
('grayscale.all', 'highContrastGrayscale', _BOOLS),
|
||||
], {'algorithm', 'policy.images'}),
|
||||
|
||||
Variant.qt_510: ([
|
||||
('algorithm', 'highContrastMode', _ALGORITHMS_BEFORE_QT_514),
|
||||
|
||||
('policy.images', 'highContrastImagePolicy', _IMAGE_POLICIES_QT_510),
|
||||
('contrast', 'highContrastContrast', None),
|
||||
('grayscale.all', 'highContrastGrayscale', _BOOLS),
|
||||
], {'algorithm'}),
|
||||
}
|
||||
|
||||
|
||||
def _variant() -> Variant:
|
||||
"""Get the dark mode variant based on the underlying Qt version."""
|
||||
if PYQT_WEBENGINE_VERSION is not None:
|
||||
# Available with Qt >= 5.13
|
||||
if PYQT_WEBENGINE_VERSION >= 0x050f02:
|
||||
return Variant.qt_515_2
|
||||
elif PYQT_WEBENGINE_VERSION == 0x050f01:
|
||||
return Variant.qt_515_1
|
||||
elif PYQT_WEBENGINE_VERSION == 0x050f00:
|
||||
return Variant.qt_515_0
|
||||
elif PYQT_WEBENGINE_VERSION >= 0x050e00:
|
||||
return Variant.qt_514
|
||||
elif PYQT_WEBENGINE_VERSION >= 0x050d00:
|
||||
return Variant.qt_511_to_513
|
||||
raise utils.Unreachable(hex(PYQT_WEBENGINE_VERSION))
|
||||
|
||||
# If we don't have PYQT_WEBENGINE_VERSION, we'll need to assume based on the Qt
|
||||
# version.
|
||||
assert not qtutils.version_check( # type: ignore[unreachable]
|
||||
'5.13', compiled=False)
|
||||
|
||||
if qtutils.version_check('5.11', compiled=False):
|
||||
return Variant.qt_511_to_513
|
||||
elif qtutils.version_check('5.10', compiled=False):
|
||||
return Variant.qt_510
|
||||
|
||||
return Variant.unavailable
|
||||
|
||||
|
||||
def settings() -> Iterator[Tuple[str, str]]:
|
||||
"""Get necessary blink settings to configure dark mode for QtWebEngine."""
|
||||
if not config.val.colors.webpage.darkmode.enabled:
|
||||
return
|
||||
|
||||
variant = _variant()
|
||||
setting_defs, mandatory_settings = _DARK_MODE_DEFINITIONS[variant]
|
||||
|
||||
for setting, key, mapping in setting_defs:
|
||||
# To avoid blowing up the commandline length, we only pass modified
|
||||
# settings to Chromium, as our defaults line up with Chromium's.
|
||||
# However, we always pass enabled/algorithm to make sure dark mode gets
|
||||
# actually turned on.
|
||||
value = config.instance.get(
|
||||
'colors.webpage.darkmode.' + setting,
|
||||
fallback=setting in mandatory_settings)
|
||||
if isinstance(value, usertypes.Unset):
|
||||
continue
|
||||
|
||||
if (setting == '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
|
||||
|
||||
if mapping is not None:
|
||||
value = mapping[value]
|
||||
|
||||
yield key, str(value)
|
||||
|
|
@ -19,7 +19,8 @@
|
|||
|
||||
"""QtWebEngine specific part of the web element API."""
|
||||
|
||||
import typing
|
||||
from typing import (
|
||||
TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Set, Tuple, Union)
|
||||
|
||||
from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop
|
||||
from PyQt5.QtGui import QMouseEvent
|
||||
|
|
@ -29,7 +30,7 @@ from PyQt5.QtWebEngineWidgets import QWebEngineSettings
|
|||
from qutebrowser.utils import log, javascript, urlutils, usertypes, utils
|
||||
from qutebrowser.browser import webelem
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.browser.webengine import webenginetab
|
||||
|
||||
|
||||
|
|
@ -37,11 +38,11 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
|
||||
"""A web element for QtWebEngine, using JS under the hood."""
|
||||
|
||||
def __init__(self, js_dict: typing.Dict[str, typing.Any],
|
||||
def __init__(self, js_dict: Dict[str, Any],
|
||||
tab: 'webenginetab.WebEngineTab') -> None:
|
||||
super().__init__(tab)
|
||||
# Do some sanity checks on the data we get from JS
|
||||
js_dict_types = {
|
||||
js_dict_types: Dict[str, Union[type, Tuple[type, ...]]] = {
|
||||
'id': int,
|
||||
'text': str,
|
||||
'value': (str, int, float),
|
||||
|
|
@ -52,7 +53,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
'attributes': dict,
|
||||
'is_content_editable': bool,
|
||||
'caret_position': (int, type(None)),
|
||||
} # type: typing.Dict[str, typing.Union[type, typing.Tuple[type,...]]]
|
||||
}
|
||||
assert set(js_dict.keys()).issubset(js_dict_types.keys())
|
||||
for name, typ in js_dict_types.items():
|
||||
if name in js_dict and not isinstance(js_dict[name], typ):
|
||||
|
|
@ -97,14 +98,14 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
utils.unused(key)
|
||||
log.stub()
|
||||
|
||||
def __iter__(self) -> typing.Iterator[str]:
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
return iter(self._js_dict['attributes'])
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._js_dict['attributes'])
|
||||
|
||||
def _js_call(self, name: str, *args: webelem.JsValueType,
|
||||
callback: typing.Callable[[typing.Any], None] = None) -> None:
|
||||
callback: Callable[[Any], None] = None) -> None:
|
||||
"""Wrapper to run stuff from webelem.js."""
|
||||
if self._tab.is_deleted():
|
||||
raise webelem.OrphanedError("Tab containing element vanished")
|
||||
|
|
@ -118,7 +119,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
log.stub()
|
||||
return QRect()
|
||||
|
||||
def classes(self) -> typing.Set[str]:
|
||||
def classes(self) -> Set[str]:
|
||||
"""Get a list of classes assigned to this element."""
|
||||
return set(self._js_dict['class_name'].split())
|
||||
|
||||
|
|
@ -150,7 +151,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
composed: bool = False) -> None:
|
||||
self._js_call('dispatch_event', event, bubbles, cancelable, composed)
|
||||
|
||||
def caret_position(self) -> typing.Optional[int]:
|
||||
def caret_position(self) -> Optional[int]:
|
||||
"""Get the text caret position for the current element.
|
||||
|
||||
If the element is not a text element, None is returned.
|
||||
|
|
@ -256,7 +257,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
QEventLoop.ExcludeSocketNotifiers |
|
||||
QEventLoop.ExcludeUserInputEvents)
|
||||
|
||||
def reset_setting(_arg: typing.Any) -> None:
|
||||
def reset_setting(_arg: Any) -> None:
|
||||
"""Set the JavascriptCanOpenWindows setting to its old value."""
|
||||
assert view is not None
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
"""Customized QWebInspector for QtWebEngine."""
|
||||
|
||||
import os
|
||||
import typing
|
||||
import pathlib
|
||||
|
||||
from PyQt5.QtCore import QUrl, QLibraryInfo
|
||||
|
|
@ -118,7 +117,7 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
|||
pak = data_path / 'resources' / 'qtwebengine_devtools_resources.pak'
|
||||
if not pak.exists():
|
||||
raise inspector.Error("QtWebEngine devtools resources not found, "
|
||||
"please install the qt5-webengine-devtools "
|
||||
"please install the qt5-qtwebengine-devtools "
|
||||
"Fedora package.")
|
||||
|
||||
def inspect(self, page: QWebEnginePage) -> None: # type: ignore[override]
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Module attributes:
|
|||
|
||||
import os
|
||||
import operator
|
||||
import typing
|
||||
from typing import cast, Any, List, Optional, Tuple, Union
|
||||
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
||||
|
|
@ -39,11 +39,11 @@ from qutebrowser.utils import (utils, standarddir, qtutils, message, log,
|
|||
urlmatch, usertypes)
|
||||
|
||||
# The default QWebEngineProfile
|
||||
default_profile = typing.cast(QWebEngineProfile, None)
|
||||
default_profile = cast(QWebEngineProfile, None)
|
||||
# The QWebEngineProfile used for private (off-the-record) windows
|
||||
private_profile = None # type: typing.Optional[QWebEngineProfile]
|
||||
private_profile: Optional[QWebEngineProfile] = None
|
||||
# The global WebEngineSettings object
|
||||
global_settings = typing.cast('WebEngineSettings', None)
|
||||
global_settings = cast('WebEngineSettings', None)
|
||||
|
||||
parsed_user_agent = None
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ class WebEngineSettings(websettings.AbstractSettings):
|
|||
}
|
||||
|
||||
def set_unknown_url_scheme_policy(
|
||||
self, policy: typing.Union[str, usertypes.Unset]) -> bool:
|
||||
self, policy: Union[str, usertypes.Unset]) -> bool:
|
||||
"""Set the UnknownUrlSchemePolicy to use.
|
||||
|
||||
Return:
|
||||
|
|
@ -448,10 +448,10 @@ def _init_site_specific_quirks():
|
|||
|
||||
def _init_devtools_settings():
|
||||
"""Make sure the devtools always get images/JS permissions."""
|
||||
settings = [
|
||||
settings: List[Tuple[str, Any]] = [
|
||||
('content.javascript.enabled', True),
|
||||
('content.images', True)
|
||||
] # type: typing.List[typing.Tuple[str, typing.Any]]
|
||||
]
|
||||
if qtutils.version_check('5.11'):
|
||||
settings.append(('content.cookies.accept', 'all'))
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ import math
|
|||
import functools
|
||||
import re
|
||||
import html as html_utils
|
||||
import typing
|
||||
from typing import cast, Optional, Union
|
||||
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl,
|
||||
QTimer, QObject)
|
||||
from PyQt5.QtNetwork import QAuthenticator
|
||||
from PyQt5.QtWidgets import QApplication, QWidget
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineHistory
|
||||
|
||||
from qutebrowser.config import configdata, config
|
||||
from qutebrowser.browser import (browsertab, eventfilter, shared, webelem,
|
||||
|
|
@ -41,7 +41,6 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
|||
from qutebrowser.misc import miscwidgets, objects, quitter
|
||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||
message, objreg, jinja, debug)
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
|
|
@ -356,12 +355,7 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||
|
||||
"""QtWebEngine implementations related to moving the cursor/selection."""
|
||||
|
||||
def __init__(self,
|
||||
tab: 'WebEngineTab',
|
||||
mode_manager: modeman.ModeManager,
|
||||
parent: QWidget = None) -> None:
|
||||
super().__init__(mode_manager, parent)
|
||||
self._tab = tab
|
||||
_tab: 'WebEngineTab'
|
||||
|
||||
def _flags(self):
|
||||
"""Get flags to pass to JS."""
|
||||
|
|
@ -674,6 +668,10 @@ class WebEngineHistoryPrivate(browsertab.AbstractHistoryPrivate):
|
|||
|
||||
"""History-related methods which are not part of the extension API."""
|
||||
|
||||
def __init__(self, tab: 'WebEngineTab') -> None:
|
||||
self._tab = tab
|
||||
self._history = cast(QWebEngineHistory, None)
|
||||
|
||||
def serialize(self):
|
||||
if not qtutils.version_check('5.9', compiled=False):
|
||||
# WORKAROUND for
|
||||
|
|
@ -782,9 +780,7 @@ class WebEngineElements(browsertab.AbstractElements):
|
|||
|
||||
"""QtWebEngine implemementations related to elements on the page."""
|
||||
|
||||
def __init__(self, tab: 'WebEngineTab') -> None:
|
||||
super().__init__()
|
||||
self._tab = tab
|
||||
_tab: 'WebEngineTab'
|
||||
|
||||
def _js_cb_multiple(self, callback, error_cb, js_elems):
|
||||
"""Handle found elements coming from JS and call the real callback.
|
||||
|
|
@ -931,7 +927,7 @@ class _WebEnginePermissions(QObject):
|
|||
def __init__(self, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
|
||||
try:
|
||||
self._options.update({
|
||||
|
|
@ -1087,7 +1083,7 @@ class _WebEngineScripts(QObject):
|
|||
def __init__(self, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._widget = cast(QWidget, None)
|
||||
self._greasemonkey = greasemonkey.gm_manager
|
||||
|
||||
def connect_signals(self):
|
||||
|
|
@ -1380,7 +1376,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
self.backend = usertypes.Backend.QtWebEngine
|
||||
self._child_event_filter = None
|
||||
self._saved_zoom = None
|
||||
self._reload_url = None # type: typing.Optional[QUrl]
|
||||
self._reload_url: Optional[QUrl] = None
|
||||
self._scripts.init()
|
||||
|
||||
def _set_widget(self, widget):
|
||||
|
|
@ -1447,9 +1443,9 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
self._widget.page().toHtml(callback)
|
||||
|
||||
def run_js_async(self, code, callback=None, *, world=None):
|
||||
world_id_type = typing.Union[QWebEngineScript.ScriptWorldId, int]
|
||||
world_id_type = Union[QWebEngineScript.ScriptWorldId, int]
|
||||
if world is None:
|
||||
world_id = QWebEngineScript.ApplicationWorld # type: world_id_type
|
||||
world_id: world_id_type = QWebEngineScript.ApplicationWorld
|
||||
elif isinstance(world, int):
|
||||
world_id = world
|
||||
if not 0 <= world_id <= qtutils.MAX_WORLD_ID:
|
||||
|
|
@ -1545,9 +1541,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
authenticator.setPassword(answer.password)
|
||||
else:
|
||||
try:
|
||||
sip.assign( # type: ignore[attr-defined]
|
||||
authenticator,
|
||||
QAuthenticator())
|
||||
sip.assign(authenticator, QAuthenticator())
|
||||
except AttributeError:
|
||||
self._show_error_page(url, "Proxy authentication required")
|
||||
|
||||
|
|
@ -1568,8 +1562,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
if not netrc_success and answer is None:
|
||||
log.network.debug("Aborting auth")
|
||||
try:
|
||||
sip.assign( # type: ignore[attr-defined]
|
||||
authenticator, QAuthenticator())
|
||||
sip.assign(authenticator, QAuthenticator())
|
||||
except AttributeError:
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
|
||||
|
|
@ -1585,6 +1578,11 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
super()._on_load_started()
|
||||
self.data.netrc_used = False
|
||||
|
||||
@pyqtSlot('qint64')
|
||||
def _on_renderer_process_pid_changed(self, pid):
|
||||
log.webview.debug("Renderer process PID for tab {}: {}"
|
||||
.format(self.tab_id, pid))
|
||||
|
||||
@pyqtSlot(QWebEnginePage.RenderProcessTerminationStatus, int)
|
||||
def _on_render_process_terminated(self, status, exitcode):
|
||||
"""Show an error when the renderer process terminated."""
|
||||
|
|
@ -1857,11 +1855,15 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
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
|
||||
|
||||
self.before_load_started.connect(self._on_before_load_started)
|
||||
self.shutting_down.connect(
|
||||
self.abort_questions) # type: ignore[arg-type]
|
||||
self.load_started.connect(
|
||||
self.abort_questions) # type: ignore[arg-type]
|
||||
self.shutting_down.connect(self.abort_questions)
|
||||
self.load_started.connect(self.abort_questions)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.audio._connect_signals()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""The main browser widget for QtWebEngine."""
|
||||
|
||||
import typing
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
|
||||
from PyQt5.QtGui import QPalette
|
||||
|
|
@ -70,7 +70,7 @@ class WebEngineView(QWebEngineView):
|
|||
|
||||
The above bug got introduced in Qt 5.11.0 and fixed in 5.12.0.
|
||||
"""
|
||||
proxy = self.focusProxy() # type: typing.Optional[QWidget]
|
||||
proxy: Optional[QWidget] = self.focusProxy()
|
||||
|
||||
if 'lost-focusproxy' in objects.debug_flags:
|
||||
proxy = None
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""HTTP network cache."""
|
||||
|
||||
import typing
|
||||
from typing import cast
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkDiskCache
|
||||
|
|
@ -28,7 +28,7 @@ from qutebrowser.config import config
|
|||
from qutebrowser.utils import utils, qtutils, standarddir
|
||||
|
||||
|
||||
diskcache = typing.cast('DiskCache', None)
|
||||
diskcache = cast('DiskCache', None)
|
||||
|
||||
|
||||
class DiskCache(QNetworkDiskCache):
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""Handling of HTTP cookies."""
|
||||
|
||||
import typing
|
||||
from typing import Sequence
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkCookie, QNetworkCookieJar
|
||||
from PyQt5.QtCore import pyqtSignal, QDateTime
|
||||
|
|
@ -93,7 +93,7 @@ class CookieJar(RAMCookieJar):
|
|||
|
||||
def parse_cookies(self):
|
||||
"""Parse cookies from lineparser and store them."""
|
||||
cookies = [] # type: typing.Sequence[QNetworkCookie]
|
||||
cookies: Sequence[QNetworkCookie] = []
|
||||
for line in self._lineparser:
|
||||
line_cookies = QNetworkCookie.parseCookies(line)
|
||||
cookies += line_cookies # type: ignore[operator]
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import email.encoders
|
|||
import email.mime.multipart
|
||||
import email.message
|
||||
import quopri
|
||||
import typing
|
||||
from typing import MutableMapping, Set, Tuple
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
|
@ -90,10 +90,7 @@ def _get_css_imports_cssutils(data, inline=False):
|
|||
"""
|
||||
try:
|
||||
import cssutils
|
||||
except (ImportError, re.error):
|
||||
# Catching re.error because cssutils in earlier releases (<= 1.0) is
|
||||
# broken on Python 3.5
|
||||
# See https://bitbucket.org/cthedot/cssutils/issues/52
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
# We don't care about invalid CSS data, this will only litter the log
|
||||
|
|
@ -189,7 +186,7 @@ class MHTMLWriter:
|
|||
self.root_content = root_content
|
||||
self.content_location = content_location
|
||||
self.content_type = content_type
|
||||
self._files = {} # type: typing.MutableMapping[QUrl, _File]
|
||||
self._files: MutableMapping[QUrl, _File] = {}
|
||||
|
||||
def add_file(self, location, content, content_type=None,
|
||||
transfer_encoding=E_QUOPRI):
|
||||
|
|
@ -244,8 +241,7 @@ class MHTMLWriter:
|
|||
return msg
|
||||
|
||||
|
||||
_PendingDownloadType = typing.Set[
|
||||
typing.Tuple[QUrl, downloads.AbstractDownloadItem]]
|
||||
_PendingDownloadType = Set[Tuple[QUrl, downloads.AbstractDownloadItem]]
|
||||
|
||||
|
||||
class _Downloader:
|
||||
|
|
@ -268,7 +264,7 @@ class _Downloader:
|
|||
self.target = target
|
||||
self.writer = None
|
||||
self.loaded_urls = {tab.url()}
|
||||
self.pending_downloads = set() # type: _PendingDownloadType
|
||||
self.pending_downloads: _PendingDownloadType = set()
|
||||
self._finished_file = False
|
||||
self._used = False
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import collections
|
||||
import html
|
||||
import typing
|
||||
from typing import TYPE_CHECKING, Dict, MutableMapping, Optional, Sequence
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl,
|
||||
|
|
@ -40,12 +40,12 @@ from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
|
|||
filescheme)
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.mainwindow import prompt
|
||||
|
||||
|
||||
HOSTBLOCK_ERROR_STRING = '%HOSTBLOCK%'
|
||||
_proxy_auth_cache = {} # type: typing.Dict[ProxyId, prompt.AuthInfo]
|
||||
_proxy_auth_cache: Dict['ProxyId', 'prompt.AuthInfo'] = {}
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
|
|
@ -123,8 +123,7 @@ def init():
|
|||
QSslSocket.setDefaultCiphers(good_ciphers)
|
||||
|
||||
|
||||
_SavedErrorsType = typing.MutableMapping[urlutils.HostTupleType,
|
||||
typing.Sequence[QSslError]]
|
||||
_SavedErrorsType = MutableMapping[urlutils.HostTupleType, Sequence[QSslError]]
|
||||
|
||||
|
||||
class NetworkManager(QNetworkAccessManager):
|
||||
|
|
@ -173,10 +172,8 @@ class NetworkManager(QNetworkAccessManager):
|
|||
self._set_cache()
|
||||
self.sslErrors.connect( # type: ignore[attr-defined]
|
||||
self.on_ssl_errors)
|
||||
self._rejected_ssl_errors = collections.defaultdict(
|
||||
list) # type: _SavedErrorsType
|
||||
self._accepted_ssl_errors = collections.defaultdict(
|
||||
list) # type: _SavedErrorsType
|
||||
self._rejected_ssl_errors: _SavedErrorsType = collections.defaultdict(list)
|
||||
self._accepted_ssl_errors: _SavedErrorsType = collections.defaultdict(list)
|
||||
self.authenticationRequired.connect( # type: ignore[attr-defined]
|
||||
self.on_authentication_required)
|
||||
self.proxyAuthenticationRequired.connect( # type: ignore[attr-defined]
|
||||
|
|
@ -241,8 +238,8 @@ class NetworkManager(QNetworkAccessManager):
|
|||
log.network.debug("Certificate errors: {!r}".format(
|
||||
' / '.join(str(err) for err in errors)))
|
||||
try:
|
||||
host_tpl = urlutils.host_tuple(
|
||||
reply.url()) # type: typing.Optional[urlutils.HostTupleType]
|
||||
host_tpl: Optional[urlutils.HostTupleType] = urlutils.host_tuple(
|
||||
reply.url())
|
||||
except ValueError:
|
||||
host_tpl = None
|
||||
is_accepted = False
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""Utilities related to QWebHistory."""
|
||||
|
||||
import typing
|
||||
from typing import Any, List, Mapping
|
||||
|
||||
from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ def serialize(items):
|
|||
"""
|
||||
data = QByteArray()
|
||||
stream = QDataStream(data, QIODevice.ReadWrite)
|
||||
user_data = [] # type: typing.List[typing.Mapping[str, typing.Any]]
|
||||
user_data: List[Mapping[str, Any]] = []
|
||||
|
||||
current_idx = None
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""QtWebKit specific part of the web element API."""
|
||||
|
||||
import typing
|
||||
from typing import cast, TYPE_CHECKING, Iterator, List, Optional, Set
|
||||
|
||||
from PyQt5.QtCore import QRect, Qt
|
||||
from PyQt5.QtWebKit import QWebElement, QWebSettings
|
||||
|
|
@ -29,7 +29,7 @@ from qutebrowser.config import config
|
|||
from qutebrowser.utils import log, utils, javascript, usertypes
|
||||
from qutebrowser.browser import webelem
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.browser.webkit import webkittab
|
||||
|
||||
|
||||
|
|
@ -42,6 +42,8 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
|
||||
"""A wrapper around a QWebElement."""
|
||||
|
||||
_tab: 'webkittab.WebKitTab'
|
||||
|
||||
def __init__(self, elem: QWebElement, tab: 'webkittab.WebKitTab') -> None:
|
||||
super().__init__(tab)
|
||||
if isinstance(elem, self.__class__):
|
||||
|
|
@ -80,7 +82,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
self._check_vanished()
|
||||
return self._elem.hasAttribute(key)
|
||||
|
||||
def __iter__(self) -> typing.Iterator[str]:
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
self._check_vanished()
|
||||
yield from self._elem.attributeNames()
|
||||
|
||||
|
|
@ -101,7 +103,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
self._check_vanished()
|
||||
return self._elem.geometry()
|
||||
|
||||
def classes(self) -> typing.Set[str]:
|
||||
def classes(self) -> Set[str]:
|
||||
self._check_vanished()
|
||||
return set(self._elem.classes())
|
||||
|
||||
|
|
@ -174,21 +176,16 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
this.dispatchEvent(event);
|
||||
""".format(javascript.to_js(text)))
|
||||
|
||||
def _parent(self) -> typing.Optional['WebKitElement']:
|
||||
def _parent(self) -> Optional['WebKitElement']:
|
||||
"""Get the parent element of this element."""
|
||||
self._check_vanished()
|
||||
elem = typing.cast(typing.Optional[QWebElement],
|
||||
self._elem.parent())
|
||||
elem = cast(Optional[QWebElement], self._elem.parent())
|
||||
if elem is None or elem.isNull():
|
||||
return None
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
# pylint: disable=used-before-assignment
|
||||
assert isinstance(self._tab, webkittab.WebKitTab)
|
||||
|
||||
return WebKitElement(elem, tab=self._tab)
|
||||
|
||||
def _rect_on_view_js(self) -> typing.Optional[QRect]:
|
||||
def _rect_on_view_js(self) -> Optional[QRect]:
|
||||
"""Javascript implementation for rect_on_view."""
|
||||
# FIXME:qtwebengine maybe we can reuse this?
|
||||
rects = self._elem.evaluateJavaScript("this.getClientRects()")
|
||||
|
|
@ -217,29 +214,32 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
height *= zoom
|
||||
rect = QRect(int(rect["left"]), int(rect["top"]),
|
||||
int(width), int(height))
|
||||
frame = self._elem.webFrame()
|
||||
|
||||
frame = cast(Optional[QWebFrame], self._elem.webFrame())
|
||||
while frame is not None:
|
||||
# Translate to parent frames' position (scroll position
|
||||
# is taken care of inside getClientRects)
|
||||
rect.translate(frame.geometry().topLeft())
|
||||
frame = frame.parentFrame()
|
||||
|
||||
return rect
|
||||
|
||||
return None
|
||||
|
||||
def _rect_on_view_python(self,
|
||||
elem_geometry: typing.Optional[QRect]) -> QRect:
|
||||
def _rect_on_view_python(self, elem_geometry: Optional[QRect]) -> QRect:
|
||||
"""Python implementation for rect_on_view."""
|
||||
if elem_geometry is None:
|
||||
geometry = self._elem.geometry()
|
||||
else:
|
||||
geometry = elem_geometry
|
||||
frame = self._elem.webFrame()
|
||||
rect = QRect(geometry)
|
||||
|
||||
frame = cast(Optional[QWebFrame], self._elem.webFrame())
|
||||
while frame is not None:
|
||||
rect.translate(frame.geometry().topLeft())
|
||||
rect.translate(frame.scrollPosition() * -1)
|
||||
frame = frame.parentFrame()
|
||||
frame = cast(Optional[QWebFrame], frame.parentFrame())
|
||||
|
||||
return rect
|
||||
|
||||
def rect_on_view(self, *, elem_geometry: QRect = None,
|
||||
|
|
@ -332,7 +332,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
return all([visible_on_screen, visible_in_frame])
|
||||
|
||||
def remove_blank_target(self) -> None:
|
||||
elem = self # type: typing.Optional[WebKitElement]
|
||||
elem: Optional[WebKitElement] = self
|
||||
for _ in range(5):
|
||||
if elem is None:
|
||||
break
|
||||
|
|
@ -377,7 +377,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
super()._click_fake_event(click_target)
|
||||
|
||||
|
||||
def get_child_frames(startframe: QWebFrame) -> typing.List[QWebFrame]:
|
||||
def get_child_frames(startframe: QWebFrame) -> List[QWebFrame]:
|
||||
"""Get all children recursively of a given QWebFrame.
|
||||
|
||||
Loosely based on http://blog.nextgenetics.net/?e=64
|
||||
|
|
@ -391,7 +391,7 @@ def get_child_frames(startframe: QWebFrame) -> typing.List[QWebFrame]:
|
|||
results = []
|
||||
frames = [startframe]
|
||||
while frames:
|
||||
new_frames = [] # type: typing.List[QWebFrame]
|
||||
new_frames: List[QWebFrame] = []
|
||||
for frame in frames:
|
||||
results.append(frame)
|
||||
new_frames += frame.childFrames()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Module attributes:
|
|||
constants.
|
||||
"""
|
||||
|
||||
import typing
|
||||
from typing import cast
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
|
@ -39,7 +39,7 @@ from qutebrowser.browser import shared
|
|||
|
||||
|
||||
# The global WebKitSettings object
|
||||
global_settings = typing.cast('WebKitSettings', None)
|
||||
global_settings = cast('WebKitSettings', None)
|
||||
|
||||
parsed_user_agent = None
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,13 @@
|
|||
import re
|
||||
import functools
|
||||
import xml.etree.ElementTree
|
||||
from typing import cast, Iterable
|
||||
|
||||
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
|
||||
from PyQt5.QtWebKit import QWebSettings, QWebHistory, QWebElement
|
||||
from PyQt5.QtPrintSupport import QPrinter
|
||||
|
||||
from qutebrowser.browser import browsertab, shared
|
||||
|
|
@ -200,8 +201,7 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
tab: 'WebKitTab',
|
||||
mode_manager: modeman.ModeManager,
|
||||
parent: QWidget = None) -> None:
|
||||
super().__init__(mode_manager, parent)
|
||||
self._tab = tab
|
||||
super().__init__(tab, mode_manager, parent)
|
||||
self._selection_state = browsertab.SelectionState.none
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
|
|
@ -622,6 +622,10 @@ class WebKitHistoryPrivate(browsertab.AbstractHistoryPrivate):
|
|||
|
||||
"""History-related methods which are not part of the extension API."""
|
||||
|
||||
def __init__(self, tab: 'WebKitTab') -> None:
|
||||
self._tab = tab
|
||||
self._history = cast(QWebHistory, None)
|
||||
|
||||
def serialize(self):
|
||||
return qtutils.serialize(self._history)
|
||||
|
||||
|
|
@ -636,6 +640,7 @@ class WebKitHistoryPrivate(browsertab.AbstractHistoryPrivate):
|
|||
qtutils.deserialize_stream(stream, self._history)
|
||||
for i, data in enumerate(user_data):
|
||||
self._history.itemAt(i).setUserData(data)
|
||||
|
||||
cur_data = self._history.currentItem().userData()
|
||||
if cur_data is not None:
|
||||
if 'zoom' in cur_data:
|
||||
|
|
@ -687,9 +692,7 @@ class WebKitElements(browsertab.AbstractElements):
|
|||
|
||||
"""QtWebKit implemementations related to elements on the page."""
|
||||
|
||||
def __init__(self, tab: 'WebKitTab') -> None:
|
||||
super().__init__()
|
||||
self._tab = tab
|
||||
_tab: 'WebKitTab'
|
||||
|
||||
def find_css(self, selector, callback, error_cb, *, only_visible=False):
|
||||
utils.unused(error_cb)
|
||||
|
|
@ -700,7 +703,8 @@ class WebKitElements(browsertab.AbstractElements):
|
|||
elems = []
|
||||
frames = webkitelem.get_child_frames(mainframe)
|
||||
for f in frames:
|
||||
for elem in f.findAllElements(selector):
|
||||
frame_elems = cast(Iterable[QWebElement], f.findAllElements(selector))
|
||||
for elem in frame_elems:
|
||||
elems.append(webkitelem.WebKitElement(elem, tab=self._tab))
|
||||
|
||||
if only_visible:
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import html
|
||||
import functools
|
||||
import typing
|
||||
from typing import cast
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
|
|
@ -353,11 +353,11 @@ class BrowserPage(QWebPage):
|
|||
self.setFeaturePermission, frame, feature,
|
||||
QWebPage.PermissionDeniedByUser)
|
||||
|
||||
url = frame.url().adjusted(typing.cast(QUrl.FormattingOptions,
|
||||
QUrl.RemoveUserInfo |
|
||||
QUrl.RemovePath |
|
||||
QUrl.RemoveQuery |
|
||||
QUrl.RemoveFragment))
|
||||
url = frame.url().adjusted(cast(QUrl.FormattingOptions,
|
||||
QUrl.RemoveUserInfo |
|
||||
QUrl.RemovePath |
|
||||
QUrl.RemoveQuery |
|
||||
QUrl.RemoveFragment))
|
||||
question = shared.feature_permission(
|
||||
url=url,
|
||||
option=options[feature], msg=messages[feature],
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import inspect
|
|||
import collections
|
||||
import traceback
|
||||
import typing
|
||||
from typing import Any, MutableMapping, MutableSequence, Tuple, Union
|
||||
|
||||
import attr
|
||||
|
||||
|
|
@ -116,13 +117,11 @@ class Command:
|
|||
self.parser.add_argument('-h', '--help', action=argparser.HelpAction,
|
||||
default=argparser.SUPPRESS, nargs=0,
|
||||
help=argparser.SUPPRESS)
|
||||
self.opt_args = collections.OrderedDict(
|
||||
) # type: typing.MutableMapping[str, typing.Tuple[str, str]]
|
||||
self.opt_args: MutableMapping[str, Tuple[str, str]] = collections.OrderedDict()
|
||||
self.namespace = None
|
||||
self._count = None
|
||||
self.pos_args = [
|
||||
] # type: typing.MutableSequence[typing.Tuple[str, str]]
|
||||
self.flags_with_args = [] # type: typing.MutableSequence[str]
|
||||
self.pos_args: MutableSequence[Tuple[str, str]] = []
|
||||
self.flags_with_args: MutableSequence[str] = []
|
||||
self._has_vararg = False
|
||||
|
||||
# This is checked by future @cmdutils.argument calls so they fail
|
||||
|
|
@ -406,22 +405,19 @@ class Command:
|
|||
raise TypeError("{}: Legacy tuple type annotation!".format(
|
||||
self.name))
|
||||
|
||||
if hasattr(typing, 'UnionMeta'):
|
||||
# Python 3.5.2
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
is_union = isinstance(
|
||||
typ, typing.UnionMeta) # type: ignore[attr-defined]
|
||||
else:
|
||||
is_union = getattr(typ, '__origin__', None) is typing.Union
|
||||
try:
|
||||
origin = typing.get_origin(typ) # type: ignore[attr-defined]
|
||||
except AttributeError:
|
||||
# typing.get_origin was added in Python 3.8
|
||||
origin = getattr(typ, '__origin__', None)
|
||||
|
||||
if is_union:
|
||||
# this is... slightly evil, I know
|
||||
if origin is Union:
|
||||
try:
|
||||
types = list(typ.__args__)
|
||||
types = list(typing.get_args(typ)) # type: ignore[attr-defined]
|
||||
except AttributeError:
|
||||
# Python 3.5.2
|
||||
types = list(typ.__union_params__)
|
||||
# pylint: enable=no-member,useless-suppression
|
||||
# typing.get_args was added in Python 3.8
|
||||
types = list(typ.__args__)
|
||||
|
||||
if param.default is not inspect.Parameter.empty:
|
||||
types.append(type(param.default))
|
||||
choices = self.get_arg_info(param).choices
|
||||
|
|
@ -497,8 +493,8 @@ class Command:
|
|||
Return:
|
||||
An (args, kwargs) tuple.
|
||||
"""
|
||||
args = [] # type: typing.Any
|
||||
kwargs = {} # type: typing.MutableMapping[str, typing.Any]
|
||||
args: Any = []
|
||||
kwargs: MutableMapping[str, Any] = {}
|
||||
signature = inspect.signature(self.handler)
|
||||
|
||||
for i, param in enumerate(signature.parameters.values()):
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
import traceback
|
||||
import re
|
||||
import typing
|
||||
import contextlib
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Iterator, Mapping, MutableMapping
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
|
||||
|
|
@ -34,9 +34,9 @@ from qutebrowser.utils import message, objreg, qtutils, usertypes, utils
|
|||
from qutebrowser.misc import split, objects
|
||||
from qutebrowser.keyinput import macros, modeman
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.mainwindow import tabbedbrowser
|
||||
_ReplacementFunction = typing.Callable[['tabbedbrowser.TabbedBrowser'], str]
|
||||
_ReplacementFunction = Callable[['tabbedbrowser.TabbedBrowser'], str]
|
||||
|
||||
|
||||
last_command = {}
|
||||
|
|
@ -64,9 +64,9 @@ def _url(tabbed_browser):
|
|||
raise cmdutils.CommandError(msg)
|
||||
|
||||
|
||||
def _init_variable_replacements() -> typing.Mapping[str, _ReplacementFunction]:
|
||||
def _init_variable_replacements() -> Mapping[str, _ReplacementFunction]:
|
||||
"""Return a dict from variable replacements to fns processing them."""
|
||||
replacements = {
|
||||
replacements: Dict[str, _ReplacementFunction] = {
|
||||
'url': lambda tb: _url(tb).toString(
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword),
|
||||
'url:pretty': lambda tb: _url(tb).toString(
|
||||
|
|
@ -88,7 +88,7 @@ def _init_variable_replacements() -> typing.Mapping[str, _ReplacementFunction]:
|
|||
'title': lambda tb: tb.widget.page_title(tb.widget.currentIndex()),
|
||||
'clipboard': lambda _: utils.get_clipboard(),
|
||||
'primary': lambda _: utils.get_clipboard(selection=True),
|
||||
} # type: typing.Dict[str, _ReplacementFunction]
|
||||
}
|
||||
|
||||
for key in list(replacements):
|
||||
modified_key = '{' + key + '}'
|
||||
|
|
@ -108,7 +108,7 @@ def replace_variables(win_id, arglist):
|
|||
"""Utility function to replace variables like {url} in a list of args."""
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
values = {} # type: typing.MutableMapping[str, str]
|
||||
values: MutableMapping[str, str] = {}
|
||||
args = []
|
||||
|
||||
def repl_cb(matchobj):
|
||||
|
|
@ -332,7 +332,7 @@ class CommandRunner(AbstractCommandRunner):
|
|||
self._win_id = win_id
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _handle_error(self, safely: bool) -> typing.Iterator[None]:
|
||||
def _handle_error(self, safely: bool) -> Iterator[None]:
|
||||
"""Show exceptions as errors if safely=True is given."""
|
||||
try:
|
||||
yield
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import typing
|
||||
from typing import cast, Any, MutableMapping, Tuple
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QSocketNotifier
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ class _QtFIFOReader(QObject):
|
|||
fd = os.open(filepath, os.O_RDWR | os.O_NONBLOCK)
|
||||
# pylint: enable=no-member,useless-suppression
|
||||
self._fifo = os.fdopen(fd, 'r')
|
||||
self._notifier = QSocketNotifier(typing.cast(sip.voidptr, fd),
|
||||
self._notifier = QSocketNotifier(cast(sip.voidptr, fd),
|
||||
QSocketNotifier.Read, self)
|
||||
self._notifier.activated.connect( # type: ignore[attr-defined]
|
||||
self.read_line)
|
||||
|
|
@ -117,10 +117,10 @@ class _BaseUserscriptRunner(QObject):
|
|||
self._cleaned_up = False
|
||||
self._filepath = None
|
||||
self._proc = None
|
||||
self._env = {} # type: typing.MutableMapping[str, str]
|
||||
self._env: MutableMapping[str, str] = {}
|
||||
self._text_stored = False
|
||||
self._html_stored = False
|
||||
self._args = () # type: typing.Tuple[typing.Any, ...]
|
||||
self._args: Tuple[Any, ...] = ()
|
||||
self._kwargs = {}
|
||||
|
||||
def store_text(self, text):
|
||||
|
|
@ -267,7 +267,7 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
|
|||
return
|
||||
|
||||
self._reader = _QtFIFOReader(self._filepath)
|
||||
self._reader.got_line.connect(self.got_cmd) # type: ignore[arg-type]
|
||||
self._reader.got_line.connect(self.got_cmd)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_proc_finished(self):
|
||||
|
|
@ -426,7 +426,7 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False,
|
|||
commandrunner = runners.CommandRunner(win_id, parent=tb)
|
||||
|
||||
if utils.is_posix:
|
||||
runner = _POSIXUserscriptRunner(tb) # type: _BaseUserscriptRunner
|
||||
runner: _BaseUserscriptRunner = _POSIXUserscriptRunner(tb)
|
||||
elif utils.is_windows: # pragma: no cover
|
||||
runner = _WindowsUserscriptRunner(tb)
|
||||
else: # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ Defines a CompletionView which uses CompletionFiterModel and CompletionModel
|
|||
subclasses to provide completions.
|
||||
"""
|
||||
|
||||
import typing
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from PyQt5.QtWidgets import QTreeView, QSizePolicy, QStyleFactory, QWidget
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
|
||||
|
|
@ -32,7 +32,7 @@ from qutebrowser.config import config, stylesheet
|
|||
from qutebrowser.completion import completiondelegate
|
||||
from qutebrowser.utils import utils, usertypes, debug, log
|
||||
from qutebrowser.api import cmdutils
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.mainwindow.statusbar import command
|
||||
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ class CompletionView(QTreeView):
|
|||
win_id: int,
|
||||
parent: QWidget = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.pattern = None # type: typing.Optional[str]
|
||||
self.pattern: Optional[str] = None
|
||||
self._win_id = win_id
|
||||
self._cmd = cmd
|
||||
self._active = False
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""A model that proxies access to one or more completion categories."""
|
||||
|
||||
import typing
|
||||
from typing import MutableSequence
|
||||
|
||||
from PyQt5.QtCore import Qt, QModelIndex, QAbstractItemModel
|
||||
|
||||
|
|
@ -43,8 +43,7 @@ class CompletionModel(QAbstractItemModel):
|
|||
def __init__(self, *, column_widths=(30, 70, 0), parent=None):
|
||||
super().__init__(parent)
|
||||
self.column_widths = column_widths
|
||||
self._categories = [
|
||||
] # type: typing.MutableSequence[QAbstractItemModel]
|
||||
self._categories: MutableSequence[QAbstractItemModel] = []
|
||||
|
||||
def _cat_from_idx(self, index):
|
||||
"""Return the category pointed to by the given index.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""A completion category that queries the SQL history store."""
|
||||
|
||||
import typing
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtSql import QSqlQueryModel
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
|
@ -40,12 +40,12 @@ class HistoryCategory(QSqlQueryModel):
|
|||
"""Create a new History completion category."""
|
||||
super().__init__(parent=parent)
|
||||
self.name = "History"
|
||||
self._query = None # type: typing.Optional[sql.Query]
|
||||
self._query: Optional[sql.Query] = None
|
||||
|
||||
# advertise that this model filters by URL and title
|
||||
self.columns_to_filter = [0, 1]
|
||||
self.delete_func = delete_func
|
||||
self._empty_prefix = None # type: typing.Optional[str]
|
||||
self._empty_prefix: Optional[str] = None
|
||||
|
||||
def _atime_expr(self):
|
||||
"""If max_items is set, return an expression to limit the query."""
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"""Completion category that uses a list of tuples as a data source."""
|
||||
|
||||
import re
|
||||
import typing
|
||||
from typing import Iterable, Tuple
|
||||
|
||||
from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp
|
||||
from PyQt5.QtGui import QStandardItem, QStandardItemModel
|
||||
|
|
@ -36,7 +36,7 @@ class ListCategory(QSortFilterProxyModel):
|
|||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
items: typing.Iterable[typing.Tuple[str, ...]],
|
||||
items: Iterable[Tuple[str, ...]],
|
||||
sort: bool = True,
|
||||
delete_func: util.DeleteFuncType = None,
|
||||
parent: QWidget = None):
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"""Functions that return miscellaneous completion models."""
|
||||
|
||||
import datetime
|
||||
import typing
|
||||
from typing import List, Sequence, Tuple
|
||||
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.utils import objreg, log, utils
|
||||
|
|
@ -53,7 +53,7 @@ def helptopic(*, info):
|
|||
|
||||
def quickmark(*, info=None):
|
||||
"""A CompletionModel filled with all quickmarks."""
|
||||
def delete(data: typing.Sequence[str]) -> None:
|
||||
def delete(data: Sequence[str]) -> None:
|
||||
"""Delete a quickmark from the completion menu."""
|
||||
name = data[0]
|
||||
quickmark_manager = objreg.get('quickmark-manager')
|
||||
|
|
@ -71,7 +71,7 @@ def quickmark(*, info=None):
|
|||
|
||||
def bookmark(*, info=None):
|
||||
"""A CompletionModel filled with all bookmarks."""
|
||||
def delete(data: typing.Sequence[str]) -> None:
|
||||
def delete(data: Sequence[str]) -> None:
|
||||
"""Delete a bookmark from the completion menu."""
|
||||
urlstr = data[0]
|
||||
log.completion.debug('Deleting bookmark {}'.format(urlstr))
|
||||
|
|
@ -121,7 +121,7 @@ def _buffer(*, win_id_filter=lambda _win_id: True, add_win_id=True):
|
|||
|
||||
tabs_are_windows = config.val.tabs.tabs_are_windows
|
||||
# list storing all single-tabbed windows when tabs_are_windows
|
||||
windows = [] # type: typing.List[typing.Tuple[str, str, str]]
|
||||
windows: List[Tuple[str, str, str]] = []
|
||||
|
||||
for win_id in objreg.window_registry:
|
||||
if not win_id_filter(win_id):
|
||||
|
|
@ -131,7 +131,7 @@ def _buffer(*, win_id_filter=lambda _win_id: True, add_win_id=True):
|
|||
window=win_id)
|
||||
if tabbed_browser.is_shutting_down:
|
||||
continue
|
||||
tabs = [] # type: typing.List[typing.Tuple[str, str, str]]
|
||||
tabs: List[Tuple[str, str, str]] = []
|
||||
for idx in range(tabbed_browser.widget.count()):
|
||||
tab = tabbed_browser.widget.widget(idx)
|
||||
tab_str = ("{}/{}".format(win_id, idx + 1) if add_win_id
|
||||
|
|
|
|||
|
|
@ -19,10 +19,9 @@
|
|||
|
||||
"""Function to return the url completion model for the `open` command."""
|
||||
|
||||
import typing
|
||||
from typing import Dict, Sequence
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from PyQt5.QtCore import QAbstractItemModel
|
||||
from PyQt5.QtCore import QAbstractItemModel
|
||||
|
||||
from qutebrowser.completion.models import (completionmodel, listcategory,
|
||||
histcategory)
|
||||
|
|
@ -41,14 +40,14 @@ def _delete_history(data):
|
|||
history.web_history.delete_url(urlstr)
|
||||
|
||||
|
||||
def _delete_bookmark(data: typing.Sequence[str]) -> None:
|
||||
def _delete_bookmark(data: Sequence[str]) -> None:
|
||||
urlstr = data[_URLCOL]
|
||||
log.completion.debug('Deleting bookmark {}'.format(urlstr))
|
||||
bookmark_manager = objreg.get('bookmark-manager')
|
||||
bookmark_manager.delete(urlstr)
|
||||
|
||||
|
||||
def _delete_quickmark(data: typing.Sequence[str]) -> None:
|
||||
def _delete_quickmark(data: Sequence[str]) -> None:
|
||||
name = data[_TEXTCOL]
|
||||
quickmark_manager = objreg.get('quickmark-manager')
|
||||
log.completion.debug('Deleting quickmark {}'.format(name))
|
||||
|
|
@ -77,7 +76,7 @@ def url(*, info):
|
|||
if k != 'DEFAULT']
|
||||
# pylint: enable=bad-config-option
|
||||
categories = config.val.completion.open_categories
|
||||
models = {} # type: typing.Dict[str, QAbstractItemModel]
|
||||
models: Dict[str, QAbstractItemModel] = {}
|
||||
|
||||
if searchengines and 'searchengines' in categories:
|
||||
models['searchengines'] = listcategory.ListCategory(
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
"""Utility functions for completion models."""
|
||||
|
||||
import typing
|
||||
from typing import Callable, Sequence
|
||||
|
||||
from qutebrowser.utils import usertypes
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
DeleteFuncType = typing.Callable[[typing.Sequence[str]], None]
|
||||
DeleteFuncType = Callable[[Sequence[str]], None]
|
||||
|
||||
|
||||
def get_cmd_completions(info, include_hidden, include_aliases, prefix=''):
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ import os.path
|
|||
import posixpath
|
||||
import zipfile
|
||||
import logging
|
||||
import typing
|
||||
import pathlib
|
||||
from typing import cast, IO, List, Set
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ def _guess_zip_filename(zf: zipfile.ZipFile) -> str:
|
|||
raise FileNotFoundError("No hosts file found in zip")
|
||||
|
||||
|
||||
def get_fileobj(byte_io: typing.IO[bytes]) -> typing.IO[bytes]:
|
||||
def get_fileobj(byte_io: IO[bytes]) -> IO[bytes]:
|
||||
"""Get a usable file object to read the hosts file from."""
|
||||
byte_io.seek(0) # rewind downloaded file
|
||||
if zipfile.is_zipfile(byte_io):
|
||||
|
|
@ -101,8 +101,8 @@ class HostBlocker:
|
|||
) -> None:
|
||||
self.enabled = _should_be_used()
|
||||
self._has_basedir = has_basedir
|
||||
self._blocked_hosts = set() # type: typing.Set[str]
|
||||
self._config_blocked_hosts = set() # type: typing.Set[str]
|
||||
self._blocked_hosts: Set[str] = set()
|
||||
self._config_blocked_hosts: Set[str] = set()
|
||||
|
||||
self._local_hosts_file = str(data_dir / "blocked-hosts")
|
||||
self.update_files()
|
||||
|
|
@ -137,7 +137,7 @@ class HostBlocker:
|
|||
)
|
||||
info.block()
|
||||
|
||||
def _read_hosts_line(self, raw_line: bytes) -> typing.Set[str]:
|
||||
def _read_hosts_line(self, raw_line: bytes) -> Set[str]:
|
||||
"""Read hosts from the given line.
|
||||
|
||||
Args:
|
||||
|
|
@ -173,7 +173,7 @@ class HostBlocker:
|
|||
|
||||
return filtered_hosts
|
||||
|
||||
def _read_hosts_file(self, filename: str, target: typing.Set[str]) -> bool:
|
||||
def _read_hosts_file(self, filename: str, target: Set[str]) -> bool:
|
||||
"""Read hosts from the given filename.
|
||||
|
||||
Args:
|
||||
|
|
@ -225,7 +225,7 @@ class HostBlocker:
|
|||
dl.initiate()
|
||||
return dl
|
||||
|
||||
def _merge_file(self, byte_io: typing.IO[bytes]) -> None:
|
||||
def _merge_file(self, byte_io: IO[bytes]) -> None:
|
||||
"""Read and merge host files.
|
||||
|
||||
Args:
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import os
|
|||
import signal
|
||||
import functools
|
||||
import logging
|
||||
import typing
|
||||
from typing import Optional
|
||||
|
||||
try:
|
||||
import hunter
|
||||
|
|
@ -41,7 +41,7 @@ from qutebrowser.completion.models import miscmodels
|
|||
|
||||
@cmdutils.register(name='reload')
|
||||
@cmdutils.argument('tab', value=cmdutils.Value.count_tab)
|
||||
def reloadpage(tab: typing.Optional[apitypes.Tab],
|
||||
def reloadpage(tab: Optional[apitypes.Tab],
|
||||
force: bool = False) -> None:
|
||||
"""Reload the current/[count]th tab.
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ def reloadpage(tab: typing.Optional[apitypes.Tab],
|
|||
|
||||
@cmdutils.register()
|
||||
@cmdutils.argument('tab', value=cmdutils.Value.count_tab)
|
||||
def stop(tab: typing.Optional[apitypes.Tab]) -> None:
|
||||
def stop(tab: Optional[apitypes.Tab]) -> None:
|
||||
"""Stop loading in the current/[count]th tab.
|
||||
|
||||
Args:
|
||||
|
|
@ -97,7 +97,7 @@ def _print_pdf(tab: apitypes.Tab, filename: str) -> None:
|
|||
@cmdutils.register(name='print')
|
||||
@cmdutils.argument('tab', value=cmdutils.Value.count_tab)
|
||||
@cmdutils.argument('pdf', flag='f', metavar='file')
|
||||
def printpage(tab: typing.Optional[apitypes.Tab],
|
||||
def printpage(tab: Optional[apitypes.Tab],
|
||||
preview: bool = False, *,
|
||||
pdf: str = None) -> None:
|
||||
"""Print the current/[count]th tab.
|
||||
|
|
@ -163,7 +163,7 @@ def insert_text(tab: apitypes.Tab, text: str) -> None:
|
|||
Args:
|
||||
text: The text to insert.
|
||||
"""
|
||||
def _insert_text_cb(elem: typing.Optional[apitypes.WebElement]) -> None:
|
||||
def _insert_text_cb(elem: Optional[apitypes.WebElement]) -> None:
|
||||
if elem is None:
|
||||
message.error("No element focused!")
|
||||
return
|
||||
|
|
@ -195,7 +195,7 @@ def click_element(tab: apitypes.Tab, filter_: str, value: str, *,
|
|||
target: How to open the clicked element (normal/tab/tab-bg/window).
|
||||
force_event: Force generating a fake click event.
|
||||
"""
|
||||
def single_cb(elem: typing.Optional[apitypes.WebElement]) -> None:
|
||||
def single_cb(elem: Optional[apitypes.WebElement]) -> None:
|
||||
"""Click a single element."""
|
||||
if elem is None:
|
||||
message.error("No element found with id {}!".format(value))
|
||||
|
|
@ -236,7 +236,7 @@ def debug_webaction(tab: apitypes.Tab, action: str, count: int = 1) -> None:
|
|||
|
||||
@cmdutils.register()
|
||||
@cmdutils.argument('tab', value=cmdutils.Value.count_tab)
|
||||
def tab_mute(tab: typing.Optional[apitypes.Tab]) -> None:
|
||||
def tab_mute(tab: Optional[apitypes.Tab]) -> None:
|
||||
"""Mute/Unmute the current/[count]th tab.
|
||||
|
||||
Args:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""Bridge to provide readline-like shortcuts for QLineEdits."""
|
||||
|
||||
import typing
|
||||
from typing import Iterable, Optional, MutableMapping
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QLineEdit
|
||||
|
||||
|
|
@ -35,9 +35,9 @@ class _ReadlineBridge:
|
|||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._deleted = {} # type: typing.MutableMapping[QLineEdit, str]
|
||||
self._deleted: MutableMapping[QLineEdit, str] = {}
|
||||
|
||||
def _widget(self) -> typing.Optional[QLineEdit]:
|
||||
def _widget(self) -> Optional[QLineEdit]:
|
||||
"""Get the currently active QLineEdit."""
|
||||
w = QApplication.instance().focusWidget()
|
||||
if isinstance(w, QLineEdit):
|
||||
|
|
@ -86,7 +86,7 @@ class _ReadlineBridge:
|
|||
def kill_line(self) -> None:
|
||||
self._dispatch('end', mark=True, delete=True)
|
||||
|
||||
def _rubout(self, delim: typing.Iterable[str]) -> None:
|
||||
def _rubout(self, delim: Iterable[str]) -> None:
|
||||
"""Delete backwards using the characters in delim as boundaries."""
|
||||
widget = self._widget()
|
||||
if widget is None:
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import copy
|
|||
import contextlib
|
||||
import functools
|
||||
import typing
|
||||
from typing import Any
|
||||
from typing import Any, Tuple, MutableMapping
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
||||
|
|
@ -33,7 +33,6 @@ from qutebrowser.misc import objects
|
|||
from qutebrowser.keyinput import keyutils
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import Tuple, MutableMapping
|
||||
from qutebrowser.config import configcache, configfiles
|
||||
from qutebrowser.misc import savemanager
|
||||
|
||||
|
|
@ -283,6 +282,7 @@ class Config(QObject):
|
|||
self._init_values()
|
||||
self.yaml_loaded = False
|
||||
self.config_py_loaded = False
|
||||
self.warn_autoconfig = True
|
||||
|
||||
def _init_values(self) -> None:
|
||||
"""Populate the self._values dict."""
|
||||
|
|
|
|||
|
|
@ -575,12 +575,20 @@ content.headers.user_agent:
|
|||
- qutebrowser_version
|
||||
completions:
|
||||
# See https://techblog.willshouse.com/2012/01/03/most-common-user-agents/
|
||||
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
|
||||
like Gecko) Chrome/83.0.4103.61 Safari/537.36"
|
||||
- Chrome 83 Win10
|
||||
#
|
||||
# To update the following list of user agents, run the script
|
||||
# 'ua_fetch.py'
|
||||
# Vim-protip: Place your cursor below this comment and run
|
||||
# :r!python scripts/dev/ua_fetch.py
|
||||
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
|
||||
Gecko) Chrome/83.0.4103.61 Safari/537.36"
|
||||
- Chrome 83 Linux
|
||||
Gecko) Chrome/86.0.4240.75 Safari/537.36"
|
||||
- Chrome 86 Linux
|
||||
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
|
||||
like Gecko) Chrome/86.0.4240.75 Safari/537.36"
|
||||
- Chrome 86 Win10
|
||||
- - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
|
||||
(KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36"
|
||||
- Chrome 86 macOS
|
||||
supports_pattern: true
|
||||
desc: |
|
||||
User agent to send.
|
||||
|
|
@ -1863,14 +1871,14 @@ tabs.title.format:
|
|||
* `{perc}`: Percentage as a string like `[10%]`.
|
||||
* `{perc_raw}`: Raw percentage, e.g. `10`.
|
||||
* `{current_title}`: Title of the current web page.
|
||||
* `{title_sep}`: The string ` - ` if a title is set, empty otherwise.
|
||||
* `{title_sep}`: The string `" - "` if a title is set, empty otherwise.
|
||||
* `{index}`: Index of this tab.
|
||||
* `{aligned_index}`: Index of this tab padded with spaces to have the same
|
||||
width.
|
||||
* `{id}`: Internal tab ID of this tab.
|
||||
* `{scroll_pos}`: Page scroll position.
|
||||
* `{host}`: Host of the current web page.
|
||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||
* `{backend}`: Either `webkit` or `webengine`
|
||||
* `{private}`: Indicates when private mode is enabled.
|
||||
* `{current_url}`: URL of the current web page.
|
||||
* `{protocol}`: Protocol (http/https/...) of the current web page.
|
||||
|
|
@ -2740,18 +2748,21 @@ colors.webpage.darkmode.enabled:
|
|||
above.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebEngine: Qt 5.10
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.algorithm:
|
||||
default: lightness-cielab
|
||||
desc: "Which algorithm to use for modifying how colors are rendered with
|
||||
darkmode."
|
||||
desc: >-
|
||||
Which algorithm to use for modifying how colors are rendered with darkmode.
|
||||
|
||||
The `lightness-cielab` value was added with QtWebEngine 5.14 and is treated
|
||||
like `lightness-hsl` with older QtWebEngine versions.
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- lightness-cielab: Modify colors by converting them to CIELAB color
|
||||
space and inverting the L value.
|
||||
space and inverting the L value. Not available with Qt < 5.14.
|
||||
- lightness-hsl: Modify colors by converting them to the HSL color space
|
||||
and inverting the lightness (i.e. the "L" in HSL).
|
||||
- brightness-rgb: Modify colors by subtracting each of r, g, and b from
|
||||
|
|
@ -2761,7 +2772,7 @@ colors.webpage.darkmode.algorithm:
|
|||
# Chromium's automated tests
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebEngine: Qt 5.10
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.contrast:
|
||||
|
|
@ -2777,27 +2788,29 @@ colors.webpage.darkmode.contrast:
|
|||
`lightness-hsl` or `brightness-rgb`.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebEngine: Qt 5.10
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.policy.images:
|
||||
default: never
|
||||
default: smart
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- always: Apply dark mode filter to all images.
|
||||
- never: Never apply dark mode filter to any images.
|
||||
- smart: Apply dark mode based on image content.
|
||||
- smart: "Apply dark mode based on image content. Not available with Qt
|
||||
5.10 / 5.15.0."
|
||||
desc: >-
|
||||
Which images to apply dark mode to.
|
||||
|
||||
WARNING: On Qt 5.15.0, this setting can cause frequent renderer process
|
||||
With QtWebEngine 5.15.0, this setting can cause frequent renderer process
|
||||
crashes due to a
|
||||
https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug
|
||||
in Qt].
|
||||
in Qt]. With QtWebEngine 5.10, this is not available at all. In those
|
||||
cases, the 'smart' setting is ignored and treated like 'never'.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebEngine: Qt 5.10
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.policy.page:
|
||||
|
|
@ -2860,7 +2873,7 @@ colors.webpage.darkmode.grayscale.all:
|
|||
`lightness-hsl` or `brightness-rgb`.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebEngine: Qt 5.10
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.grayscale.images:
|
||||
|
|
|
|||
|
|
@ -566,12 +566,21 @@ class ConfigAPI:
|
|||
|
||||
def finalize(self) -> None:
|
||||
"""Do work which needs to be done after reading config.py."""
|
||||
if self._config.warn_autoconfig:
|
||||
desc = configexc.ConfigErrorDesc(
|
||||
"autoconfig loading not specified",
|
||||
("Your config.py should call either `config.load_autoconfig()`"
|
||||
" (to load settings configured via the GUI) or "
|
||||
"`config.load_autoconfig(False)` (to not do so)"))
|
||||
self.errors.append(desc)
|
||||
self._config.update_mutables()
|
||||
|
||||
def load_autoconfig(self) -> None:
|
||||
def load_autoconfig(self, load_config: bool = True) -> None:
|
||||
"""Load the autoconfig.yml file which is used for :set/:bind/etc."""
|
||||
with self._handle_error('reading', 'autoconfig.yml'):
|
||||
read_autoconfig()
|
||||
self._config.warn_autoconfig = False
|
||||
if load_config:
|
||||
with self._handle_error('reading', 'autoconfig.yml'):
|
||||
read_autoconfig()
|
||||
|
||||
def get(self, name: str, pattern: str = None) -> typing.Any:
|
||||
"""Get a setting value from the config, optionally with a pattern."""
|
||||
|
|
@ -689,12 +698,12 @@ class ConfigPyWriter:
|
|||
"still loaded.")
|
||||
yield self._line("# Remove it to not load settings done via the "
|
||||
"GUI.")
|
||||
yield self._line("config.load_autoconfig()")
|
||||
yield self._line("config.load_autoconfig(True)")
|
||||
yield ''
|
||||
else:
|
||||
yield self._line("# Uncomment this to still load settings "
|
||||
yield self._line("# Change the argument to True to still load settings "
|
||||
"configured via autoconfig.yml")
|
||||
yield self._line("# config.load_autoconfig()")
|
||||
yield self._line("config.load_autoconfig(False)")
|
||||
yield ''
|
||||
|
||||
def _gen_options(self) -> typing.Iterator[str]:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ import html
|
|||
import codecs
|
||||
import os.path
|
||||
import itertools
|
||||
import warnings
|
||||
import functools
|
||||
import operator
|
||||
import json
|
||||
|
|
@ -1319,30 +1318,19 @@ class Regex(BaseType):
|
|||
def _compile_regex(self, pattern: str) -> typing.Pattern[str]:
|
||||
"""Check if the given regex is valid.
|
||||
|
||||
This is more complicated than it could be since there's a warning on
|
||||
invalid escapes with newer Python versions, and we want to catch that
|
||||
case and treat it as invalid.
|
||||
Some semi-invalid regexes can also raise warnings - we also treat them as
|
||||
invalid.
|
||||
"""
|
||||
with warnings.catch_warnings(record=True) as recorded_warnings:
|
||||
warnings.simplefilter('always')
|
||||
try:
|
||||
try:
|
||||
with log.py_warning_filter('error', category=FutureWarning):
|
||||
compiled = re.compile(pattern, self.flags)
|
||||
except re.error as e:
|
||||
raise configexc.ValidationError(
|
||||
pattern, "must be a valid regex - " + str(e))
|
||||
except RuntimeError: # pragma: no cover
|
||||
raise configexc.ValidationError(
|
||||
pattern, "must be a valid regex - recursion depth "
|
||||
"exceeded")
|
||||
|
||||
assert recorded_warnings is not None
|
||||
|
||||
for w in recorded_warnings:
|
||||
if (issubclass(w.category, DeprecationWarning) and
|
||||
str(w.message).startswith('bad escape')):
|
||||
raise configexc.ValidationError(
|
||||
pattern, "must be a valid regex - " + str(w.message))
|
||||
warnings.warn(w.message)
|
||||
except (re.error, FutureWarning) as e:
|
||||
raise configexc.ValidationError(
|
||||
pattern, "must be a valid regex - " + str(e))
|
||||
except RuntimeError: # pragma: no cover
|
||||
raise configexc.ValidationError(
|
||||
pattern, "must be a valid regex - recursion depth "
|
||||
"exceeded")
|
||||
|
||||
return compiled
|
||||
|
||||
|
|
|
|||
|
|
@ -61,90 +61,6 @@ def qt_args(namespace: argparse.Namespace) -> typing.List[str]:
|
|||
return argv
|
||||
|
||||
|
||||
def _darkmode_settings() -> typing.Iterator[typing.Tuple[str, str]]:
|
||||
"""Get necessary blink settings to configure dark mode for QtWebEngine."""
|
||||
if not config.val.colors.webpage.darkmode.enabled:
|
||||
return
|
||||
|
||||
# Mapping from a colors.webpage.darkmode.algorithm setting value to
|
||||
# Chromium's DarkModeInversionAlgorithm enum values.
|
||||
algorithms = {
|
||||
# 0: kOff (not exposed)
|
||||
# 1: kSimpleInvertForTesting (not exposed)
|
||||
'brightness-rgb': 2, # kInvertBrightness
|
||||
'lightness-hsl': 3, # kInvertLightness
|
||||
'lightness-cielab': 4, # kInvertLightnessLAB
|
||||
}
|
||||
|
||||
# Mapping from a colors.webpage.darkmode.policy.images setting value to
|
||||
# Chromium's DarkModeImagePolicy enum values.
|
||||
image_policies = {
|
||||
'always': 0, # kFilterAll
|
||||
'never': 1, # kFilterNone
|
||||
'smart': 2, # kFilterSmart
|
||||
}
|
||||
|
||||
# Mapping from a colors.webpage.darkmode.policy.page setting value to
|
||||
# Chromium's DarkModePagePolicy enum values.
|
||||
page_policies = {
|
||||
'always': 0, # kFilterAll
|
||||
'smart': 1, # kFilterByBackground
|
||||
}
|
||||
|
||||
bools = {
|
||||
True: 'true',
|
||||
False: 'false',
|
||||
}
|
||||
|
||||
_setting_description_type = typing.Tuple[
|
||||
str, # qutebrowser option name
|
||||
str, # darkmode setting name
|
||||
# Mapping from the config value to a string (or something convertable
|
||||
# to a string) which gets passed to Chromium.
|
||||
typing.Optional[typing.Mapping[typing.Any, typing.Union[str, int]]],
|
||||
]
|
||||
if qtutils.version_check('5.15', compiled=False):
|
||||
settings = [
|
||||
('enabled', 'Enabled', bools),
|
||||
('algorithm', 'InversionAlgorithm', algorithms),
|
||||
] # type: typing.List[_setting_description_type]
|
||||
mandatory_setting = 'enabled'
|
||||
else:
|
||||
settings = [
|
||||
('algorithm', '', algorithms),
|
||||
]
|
||||
mandatory_setting = 'algorithm'
|
||||
|
||||
settings += [
|
||||
('contrast', 'Contrast', None),
|
||||
('policy.images', 'ImagePolicy', image_policies),
|
||||
('policy.page', 'PagePolicy', page_policies),
|
||||
('threshold.text', 'TextBrightnessThreshold', None),
|
||||
('threshold.background', 'BackgroundBrightnessThreshold', None),
|
||||
('grayscale.all', 'Grayscale', bools),
|
||||
('grayscale.images', 'ImageGrayscale', None),
|
||||
]
|
||||
|
||||
for setting, key, mapping in settings:
|
||||
# To avoid blowing up the commandline length, we only pass modified
|
||||
# settings to Chromium, as our defaults line up with Chromium's.
|
||||
# However, we always pass enabled/algorithm to make sure dark mode gets
|
||||
# actually turned on.
|
||||
value = config.instance.get(
|
||||
'colors.webpage.darkmode.' + setting,
|
||||
fallback=setting == mandatory_setting)
|
||||
if isinstance(value, usertypes.Unset):
|
||||
continue
|
||||
|
||||
if mapping is not None:
|
||||
value = mapping[value]
|
||||
|
||||
# FIXME: This is "forceDarkMode" starting with Chromium 83
|
||||
prefix = 'darkMode'
|
||||
|
||||
yield prefix + key, str(value)
|
||||
|
||||
|
||||
def _qtwebengine_enabled_features(
|
||||
feature_flags: typing.Sequence[str],
|
||||
) -> typing.Iterator[str]:
|
||||
|
|
@ -230,7 +146,11 @@ def _qtwebengine_args(
|
|||
yield '--enable-logging'
|
||||
yield '--v=1'
|
||||
|
||||
blink_settings = list(_darkmode_settings())
|
||||
if 'wait-renderer-process' in namespace.debug_flags:
|
||||
yield '--renderer-startup-dialog'
|
||||
|
||||
from qutebrowser.browser.webengine import darkmode
|
||||
blink_settings = list(darkmode.settings())
|
||||
if blink_settings:
|
||||
yield '--blink-settings=' + ','.join('{}={}'.format(k, v)
|
||||
for k, v in blink_settings)
|
||||
|
|
@ -239,6 +159,10 @@ def _qtwebengine_args(
|
|||
if enabled_features:
|
||||
yield '--enable-features=' + ','.join(enabled_features)
|
||||
|
||||
yield from _qtwebengine_settings_args()
|
||||
|
||||
|
||||
def _qtwebengine_settings_args() -> typing.Iterator[str]:
|
||||
settings = {
|
||||
'qt.force_software_rendering': {
|
||||
'software-opengl': None,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
"""Infrastructure for intercepting requests."""
|
||||
|
||||
import typing
|
||||
import enum
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
import attr
|
||||
|
||||
|
|
@ -76,15 +76,15 @@ class Request:
|
|||
"""A request which can be intercepted/blocked."""
|
||||
|
||||
#: The URL of the page being shown.
|
||||
first_party_url = attr.ib() # type: typing.Optional[QUrl]
|
||||
first_party_url: Optional[QUrl] = attr.ib()
|
||||
|
||||
#: The URL of the file being requested.
|
||||
request_url = attr.ib() # type: QUrl
|
||||
request_url: QUrl = attr.ib()
|
||||
|
||||
is_blocked = attr.ib(False) # type: bool
|
||||
is_blocked: bool = attr.ib(False)
|
||||
|
||||
#: The resource type of the request. None if not supported on this backend.
|
||||
resource_type = attr.ib(None) # type: typing.Optional[ResourceType]
|
||||
resource_type: Optional[ResourceType] = attr.ib(None)
|
||||
|
||||
def block(self) -> None:
|
||||
"""Block this request."""
|
||||
|
|
@ -107,10 +107,10 @@ class Request:
|
|||
|
||||
|
||||
#: Type annotation for an interceptor function.
|
||||
InterceptorType = typing.Callable[[Request], None]
|
||||
InterceptorType = Callable[[Request], None]
|
||||
|
||||
|
||||
_interceptors = [] # type: typing.List[InterceptorType]
|
||||
_interceptors: List[InterceptorType] = []
|
||||
|
||||
|
||||
def register(interceptor: InterceptorType) -> None:
|
||||
|
|
|
|||
|
|
@ -19,12 +19,13 @@
|
|||
|
||||
"""Loader for qutebrowser extensions."""
|
||||
|
||||
import importlib.abc
|
||||
import pkgutil
|
||||
import types
|
||||
import typing
|
||||
import sys
|
||||
import pathlib
|
||||
import importlib
|
||||
import argparse
|
||||
from typing import Callable, Iterator, List, Optional, Set, Tuple
|
||||
|
||||
import attr
|
||||
|
||||
|
|
@ -35,9 +36,6 @@ from qutebrowser.config import config
|
|||
from qutebrowser.utils import log, standarddir
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import argparse
|
||||
|
||||
|
||||
# ModuleInfo objects for all loaded plugins
|
||||
_module_infos = []
|
||||
|
|
@ -48,9 +46,9 @@ class InitContext:
|
|||
|
||||
"""Context an extension gets in its init hook."""
|
||||
|
||||
data_dir = attr.ib() # type: pathlib.Path
|
||||
config_dir = attr.ib() # type: pathlib.Path
|
||||
args = attr.ib() # type: argparse.Namespace
|
||||
data_dir: pathlib.Path = attr.ib()
|
||||
config_dir: pathlib.Path = attr.ib()
|
||||
args: argparse.Namespace = attr.ib()
|
||||
|
||||
|
||||
@attr.s
|
||||
|
|
@ -61,13 +59,11 @@ class ModuleInfo:
|
|||
This gets used by qutebrowser.api.hook.
|
||||
"""
|
||||
|
||||
_ConfigChangedHooksType = typing.List[typing.Tuple[typing.Optional[str],
|
||||
typing.Callable]]
|
||||
_ConfigChangedHooksType = List[Tuple[Optional[str], Callable]]
|
||||
|
||||
skip_hooks = attr.ib(False) # type: bool
|
||||
init_hook = attr.ib(None) # type: typing.Optional[typing.Callable]
|
||||
config_changed_hooks = attr.ib(
|
||||
attr.Factory(list)) # type: _ConfigChangedHooksType
|
||||
skip_hooks: bool = attr.ib(False)
|
||||
init_hook: Optional[Callable] = attr.ib(None)
|
||||
config_changed_hooks: _ConfigChangedHooksType = attr.ib(attr.Factory(list))
|
||||
|
||||
|
||||
@attr.s
|
||||
|
|
@ -75,7 +71,7 @@ class ExtensionInfo:
|
|||
|
||||
"""Information about a qutebrowser extension."""
|
||||
|
||||
name = attr.ib() # type: str
|
||||
name: str = attr.ib()
|
||||
|
||||
|
||||
def add_module_info(module: types.ModuleType) -> ModuleInfo:
|
||||
|
|
@ -92,7 +88,7 @@ def load_components(*, skip_hooks: bool = False) -> None:
|
|||
_load_component(info, skip_hooks=skip_hooks)
|
||||
|
||||
|
||||
def walk_components() -> typing.Iterator[ExtensionInfo]:
|
||||
def walk_components() -> Iterator[ExtensionInfo]:
|
||||
"""Yield ExtensionInfo objects for all modules."""
|
||||
if hasattr(sys, 'frozen'):
|
||||
yield from _walk_pyinstaller()
|
||||
|
|
@ -104,7 +100,7 @@ def _on_walk_error(name: str) -> None:
|
|||
raise ImportError("Failed to import {}".format(name))
|
||||
|
||||
|
||||
def _walk_normal() -> typing.Iterator[ExtensionInfo]:
|
||||
def _walk_normal() -> Iterator[ExtensionInfo]:
|
||||
"""Walk extensions when not using PyInstaller."""
|
||||
for _finder, name, ispkg in pkgutil.walk_packages(
|
||||
# Only packages have a __path__ attribute,
|
||||
|
|
@ -117,7 +113,7 @@ def _walk_normal() -> typing.Iterator[ExtensionInfo]:
|
|||
yield ExtensionInfo(name=name)
|
||||
|
||||
|
||||
def _walk_pyinstaller() -> typing.Iterator[ExtensionInfo]:
|
||||
def _walk_pyinstaller() -> Iterator[ExtensionInfo]:
|
||||
"""Walk extensions when using PyInstaller.
|
||||
|
||||
See https://github.com/pyinstaller/pyinstaller/issues/1905
|
||||
|
|
@ -125,7 +121,7 @@ def _walk_pyinstaller() -> typing.Iterator[ExtensionInfo]:
|
|||
Inspired by:
|
||||
https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py
|
||||
"""
|
||||
toc = set() # type: typing.Set[str]
|
||||
toc: Set[str] = set()
|
||||
for importer in pkgutil.iter_importers('qutebrowser'):
|
||||
if hasattr(importer, 'toc'):
|
||||
toc |= importer.toc
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
<span class="note">Note this warning will only appear once. Use <span class="mono">:open
|
||||
qute://warning/sessions</span> to show it again at a later time.</span>
|
||||
|
||||
<p>You're using qutebrowser with Qt 5.15.</p>
|
||||
<p>You're using qutebrowser with Qt 5.15. While this is the recommended Qt version to use (due to QtWebEngine security updates), qutebrowser only provides partial support for session files.</p>
|
||||
|
||||
<p>Since Qt doesn't provide an API to load the history of a tab, qutebrowser relies on a reverse-engineered binary serialization format to load tab history from session files. With Qt 5.15, unfortunately that format changed (due to the underlying Chromium upgrade), in a way which makes it impossible for qutebrowser to load tab history from existing session data.</p>
|
||||
|
||||
<p>At the time of writing (September 2020), a new session format which stores part of the needed binary data in saved sessions is <a href="https://github.com/qutebrowser/qutebrowser/issues/5359">in development</a> and is expected to be released with qutebrowser v1.15.0.</p>
|
||||
<p>At the time of writing (October 2020), a new session format which stores part of the needed binary data in saved sessions is <a href="https://github.com/qutebrowser/qutebrowser/issues/5359">in development</a> and is expected to be released with qutebrowser v2.0.0 (planned to be released at the end of the year or early 2021).</p>
|
||||
|
||||
<p>As a stop-gap measure:</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import string
|
||||
import types
|
||||
import typing
|
||||
from typing import Mapping, MutableMapping, Optional, Sequence
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, Qt
|
||||
|
|
@ -37,9 +37,9 @@ class MatchResult:
|
|||
|
||||
"""The result of matching a keybinding."""
|
||||
|
||||
match_type = attr.ib() # type: QKeySequence.SequenceMatch
|
||||
command = attr.ib() # type: typing.Optional[str]
|
||||
sequence = attr.ib() # type: keyutils.KeySequence
|
||||
match_type: QKeySequence.SequenceMatch = attr.ib()
|
||||
command: Optional[str] = attr.ib()
|
||||
sequence: keyutils.KeySequence = attr.ib()
|
||||
|
||||
def __attrs_post_init__(self) -> None:
|
||||
if self.match_type == QKeySequence.ExactMatch:
|
||||
|
|
@ -75,9 +75,8 @@ class BindingTrie:
|
|||
__slots__ = 'children', 'command'
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.children = {
|
||||
} # type: typing.MutableMapping[keyutils.KeyInfo, BindingTrie]
|
||||
self.command = None # type: typing.Optional[str]
|
||||
self.children: MutableMapping[keyutils.KeyInfo, BindingTrie] = {}
|
||||
self.command: Optional[str] = None
|
||||
|
||||
def __setitem__(self, sequence: keyutils.KeySequence,
|
||||
command: str) -> None:
|
||||
|
|
@ -99,8 +98,7 @@ class BindingTrie:
|
|||
def __str__(self) -> str:
|
||||
return '\n'.join(self.string_lines(blank=True))
|
||||
|
||||
def string_lines(self, indent: int = 0,
|
||||
blank: bool = False) -> typing.Sequence[str]:
|
||||
def string_lines(self, indent: int = 0, blank: bool = False) -> Sequence[str]:
|
||||
"""Get a list of strings for a pretty-printed version of this trie."""
|
||||
lines = []
|
||||
if self.command is not None:
|
||||
|
|
@ -114,7 +112,7 @@ class BindingTrie:
|
|||
|
||||
return lines
|
||||
|
||||
def update(self, mapping: typing.Mapping) -> None:
|
||||
def update(self, mapping: Mapping) -> None:
|
||||
"""Add data from the given mapping to the trie."""
|
||||
for key in mapping:
|
||||
self[key] = mapping[key]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
"""Global Qt event filter which dispatches key events."""
|
||||
|
||||
import typing
|
||||
from typing import cast
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, QEvent
|
||||
from PyQt5.QtGui import QKeyEvent, QWindow
|
||||
|
|
@ -102,7 +102,7 @@ class EventFilter(QObject):
|
|||
|
||||
handler = self._handlers[typ]
|
||||
try:
|
||||
return handler(typing.cast(QKeyEvent, event))
|
||||
return handler(cast(QKeyEvent, event))
|
||||
except:
|
||||
# If there is an exception in here and we leave the eventfilter
|
||||
# activated, we'll get an infinite loop and a stack overflow.
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ handle what we actually think we do.
|
|||
"""
|
||||
|
||||
import itertools
|
||||
import typing
|
||||
from typing import cast, overload, Iterable, Iterator, List, Mapping, Optional, Union
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import Qt, QEvent
|
||||
|
|
@ -53,10 +53,10 @@ _MODIFIER_MAP = {
|
|||
|
||||
_NIL_KEY = Qt.Key(0)
|
||||
|
||||
_ModifierType = typing.Union[Qt.KeyboardModifier, Qt.KeyboardModifiers]
|
||||
_ModifierType = Union[Qt.KeyboardModifier, Qt.KeyboardModifiers]
|
||||
|
||||
|
||||
def _build_special_names() -> typing.Mapping[Qt.Key, str]:
|
||||
def _build_special_names() -> Mapping[Qt.Key, str]:
|
||||
"""Build _SPECIAL_NAMES dict from the special_names_str mapping below.
|
||||
|
||||
The reason we don't do this directly is that certain Qt versions don't have
|
||||
|
|
@ -231,8 +231,7 @@ def _remap_unicode(key: Qt.Key, text: str) -> Qt.Key:
|
|||
return key
|
||||
|
||||
|
||||
def _check_valid_utf8(s: str,
|
||||
data: typing.Union[Qt.Key, _ModifierType]) -> None:
|
||||
def _check_valid_utf8(s: str, data: Union[Qt.Key, _ModifierType]) -> None:
|
||||
"""Make sure the given string is valid UTF-8.
|
||||
|
||||
Makes sure there are no chars where Qt did fall back to weird UTF-16
|
||||
|
|
@ -288,7 +287,7 @@ class KeyParseError(Exception):
|
|||
|
||||
"""Raised by _parse_single_key/parse_keystring on parse errors."""
|
||||
|
||||
def __init__(self, keystr: typing.Optional[str], error: str) -> None:
|
||||
def __init__(self, keystr: Optional[str], error: str) -> None:
|
||||
if keystr is None:
|
||||
msg = "Could not parse keystring: {}".format(error)
|
||||
else:
|
||||
|
|
@ -296,7 +295,7 @@ class KeyParseError(Exception):
|
|||
super().__init__(msg)
|
||||
|
||||
|
||||
def _parse_keystring(keystr: str) -> typing.Iterator[str]:
|
||||
def _parse_keystring(keystr: str) -> Iterator[str]:
|
||||
key = ''
|
||||
special = False
|
||||
for c in keystr:
|
||||
|
|
@ -363,8 +362,8 @@ class KeyInfo:
|
|||
modifiers: A Qt::KeyboardModifiers enum value.
|
||||
"""
|
||||
|
||||
key = attr.ib() # type: Qt.Key
|
||||
modifiers = attr.ib() # type: _ModifierType
|
||||
key: Qt.Key = attr.ib()
|
||||
modifiers: _ModifierType = attr.ib()
|
||||
|
||||
@classmethod
|
||||
def from_event(cls, e: QKeyEvent) -> 'KeyInfo':
|
||||
|
|
@ -377,7 +376,7 @@ class KeyInfo:
|
|||
modifiers = e.modifiers()
|
||||
_assert_plain_key(key)
|
||||
_assert_plain_modifier(modifiers)
|
||||
return cls(key, typing.cast(Qt.KeyboardModifier, modifiers))
|
||||
return cls(key, cast(Qt.KeyboardModifier, modifiers))
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""Convert KeyInfo to int before hashing.
|
||||
|
|
@ -473,7 +472,7 @@ class KeySequence:
|
|||
_MAX_LEN = 4
|
||||
|
||||
def __init__(self, *keys: int) -> None:
|
||||
self._sequences = [] # type: typing.List[QKeySequence]
|
||||
self._sequences: List[QKeySequence] = []
|
||||
for sub in utils.chunk(keys, self._MAX_LEN):
|
||||
args = [self._convert_key(key) for key in sub]
|
||||
sequence = QKeySequence(*args)
|
||||
|
|
@ -493,7 +492,7 @@ class KeySequence:
|
|||
parts.append(str(info))
|
||||
return ''.join(parts)
|
||||
|
||||
def __iter__(self) -> typing.Iterator[KeyInfo]:
|
||||
def __iter__(self) -> Iterator[KeyInfo]:
|
||||
"""Iterate over KeyInfo objects."""
|
||||
for key_and_modifiers in self._iter_keys():
|
||||
key = Qt.Key(int(key_and_modifiers) & ~Qt.KeyboardModifierMask)
|
||||
|
|
@ -535,17 +534,15 @@ class KeySequence:
|
|||
def __bool__(self) -> bool:
|
||||
return bool(self._sequences)
|
||||
|
||||
@typing.overload
|
||||
@overload
|
||||
def __getitem__(self, item: int) -> KeyInfo:
|
||||
...
|
||||
|
||||
@typing.overload
|
||||
@overload
|
||||
def __getitem__(self, item: slice) -> 'KeySequence':
|
||||
...
|
||||
|
||||
def __getitem__(
|
||||
self, item: typing.Union[int, slice]
|
||||
) -> typing.Union[KeyInfo, 'KeySequence']:
|
||||
def __getitem__(self, item: Union[int, slice]) -> Union[KeyInfo, 'KeySequence']:
|
||||
if isinstance(item, slice):
|
||||
keys = list(self._iter_keys())
|
||||
return self.__class__(*keys[item])
|
||||
|
|
@ -553,9 +550,8 @@ class KeySequence:
|
|||
infos = list(self)
|
||||
return infos[item]
|
||||
|
||||
def _iter_keys(self) -> typing.Iterator[int]:
|
||||
sequences = typing.cast(typing.Iterable[typing.Iterable[int]],
|
||||
self._sequences)
|
||||
def _iter_keys(self) -> Iterator[int]:
|
||||
sequences = cast(Iterable[Iterable[int]], self._sequences)
|
||||
return itertools.chain.from_iterable(sequences)
|
||||
|
||||
def _validate(self, keystr: str = None) -> None:
|
||||
|
|
@ -664,7 +660,7 @@ class KeySequence:
|
|||
|
||||
def with_mappings(
|
||||
self,
|
||||
mappings: typing.Mapping['KeySequence', 'KeySequence']
|
||||
mappings: Mapping['KeySequence', 'KeySequence']
|
||||
) -> 'KeySequence':
|
||||
"""Get a new KeySequence with the given mappings applied."""
|
||||
keys = []
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
"""Keyboard macro system."""
|
||||
|
||||
import typing
|
||||
from typing import cast, Dict, List, Optional, Tuple
|
||||
|
||||
from qutebrowser.commands import runners
|
||||
from qutebrowser.api import cmdutils
|
||||
|
|
@ -28,9 +28,9 @@ from qutebrowser.keyinput import modeman
|
|||
from qutebrowser.utils import message, objreg, usertypes
|
||||
|
||||
|
||||
_CommandType = typing.Tuple[str, int] # command, type
|
||||
_CommandType = Tuple[str, int] # command, type
|
||||
|
||||
macro_recorder = typing.cast('MacroRecorder', None)
|
||||
macro_recorder = cast('MacroRecorder', None)
|
||||
|
||||
|
||||
class MacroRecorder:
|
||||
|
|
@ -47,10 +47,10 @@ class MacroRecorder:
|
|||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._macros = {} # type: typing.Dict[str, typing.List[_CommandType]]
|
||||
self._recording_macro = None # type: typing.Optional[str]
|
||||
self._macro_count = {} # type: typing.Dict[int, int]
|
||||
self._last_register = None # type: typing.Optional[str]
|
||||
self._macros: Dict[str, List[_CommandType]] = {}
|
||||
self._recording_macro: Optional[str] = None
|
||||
self._macro_count: Dict[int, int] = {}
|
||||
self._last_register: Optional[str] = None
|
||||
|
||||
@cmdutils.register(instance='macro-recorder', name='record-macro')
|
||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Mode manager singleton which handles the current keyboard mode."""
|
||||
"""Mode manager (per window) which handles the current keyboard mode."""
|
||||
|
||||
import functools
|
||||
from typing import Mapping, Callable, MutableMapping, Union, Set, cast
|
||||
|
|
@ -78,15 +78,17 @@ class UnavailableError(Exception):
|
|||
|
||||
def init(win_id: int, parent: QObject) -> 'ModeManager':
|
||||
"""Initialize the mode manager and the keyparsers for the given win_id."""
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
|
||||
modeman = ModeManager(win_id, parent)
|
||||
objreg.register('mode-manager', modeman, scope='window', window=win_id)
|
||||
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
|
||||
hintmanager = hints.HintManager(win_id, parent=parent)
|
||||
objreg.register('hintmanager', hintmanager, scope='window',
|
||||
window=win_id, command_only=True)
|
||||
|
||||
modeman.hintmanager = hintmanager
|
||||
|
||||
keyparsers = {
|
||||
usertypes.KeyMode.normal:
|
||||
modeparsers.NormalKeyParser(
|
||||
|
|
@ -227,6 +229,7 @@ class ModeManager(QObject):
|
|||
|
||||
Attributes:
|
||||
mode: The mode we're currently in.
|
||||
hintmanager: The HintManager associated with this window.
|
||||
_win_id: The window ID of this ModeManager
|
||||
_prev_mode: Mode before a prompt popped up
|
||||
parsers: A dictionary of modes and their keyparsers.
|
||||
|
|
@ -260,6 +263,8 @@ class ModeManager(QObject):
|
|||
self._prev_mode = usertypes.KeyMode.normal
|
||||
self.mode = usertypes.KeyMode.normal
|
||||
self._releaseevents_to_pass = set() # type: Set[KeyEvent]
|
||||
# Set after __init__
|
||||
self.hintmanager = cast(hints.HintManager, None)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return utils.get_repr(self, mode=self.mode)
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ Module attributes:
|
|||
STARTCHARS: Possible chars for starting a commandline input.
|
||||
"""
|
||||
|
||||
import typing
|
||||
import traceback
|
||||
import enum
|
||||
from typing import TYPE_CHECKING, Sequence
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QObject
|
||||
from PyQt5.QtGui import QKeySequence, QKeyEvent
|
||||
|
|
@ -35,12 +35,20 @@ from qutebrowser.commands import cmdexc
|
|||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import basekeyparser, keyutils, macros
|
||||
from qutebrowser.utils import usertypes, log, message, objreg, utils
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.commands import runners
|
||||
|
||||
|
||||
STARTCHARS = ":/?"
|
||||
LastPress = enum.Enum('LastPress', ['none', 'filtertext', 'keystring'])
|
||||
|
||||
|
||||
class LastPress(enum.Enum):
|
||||
|
||||
"""Whether the last keypress filtered a text or was part of a keystring."""
|
||||
|
||||
none = enum.auto()
|
||||
filtertext = enum.auto()
|
||||
keystring = enum.auto()
|
||||
|
||||
|
||||
class CommandKeyParser(basekeyparser.BaseKeyParser):
|
||||
|
|
@ -224,7 +232,7 @@ class HintKeyParser(basekeyparser.BaseKeyParser):
|
|||
|
||||
return match
|
||||
|
||||
def update_bindings(self, strings: typing.Sequence[str],
|
||||
def update_bindings(self, strings: Sequence[str],
|
||||
preserve_filter: bool = False) -> None:
|
||||
"""Update bindings when the hint strings changed.
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue