Merge branch 'master' into more-sophisticated-adblock

This commit is contained in:
Árni Dagur 2020-12-19 20:29:38 +00:00
commit 4c5e3d9ea0
182 changed files with 2435 additions and 1748 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.13.1
current_version = 1.14.0
commit = True
message = Release v{new_version}
tag = True

View File

@ -18,6 +18,7 @@ exclude_lines =
raise utils\.Unreachable
if __name__ == ["']__main__["']:
if typing.TYPE_CHECKING:
if TYPE_CHECKING:
\.\.\.
[xml]

View File

@ -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

View File

@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

View File

@ -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

View File

@ -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

10
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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)
--------------------

View File

@ -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 `&lt;3` footnote:[Of course, that says `<3` in HTML.] contributors!
I `&lt;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].

View File

@ -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

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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.

View File

@ -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)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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:

View File

@ -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,
}

View File

@ -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"/>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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#

View File

@ -2,4 +2,4 @@
altgraph==0.17
pyinstaller==4.0
pyinstaller-hooks-contrib==2020.8
pyinstaller-hooks-contrib==2020.9

View File

@ -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

View File

@ -2,4 +2,4 @@
PyQt5==5.15.1
PyQt5-sip==12.8.1
PyQtWebEngine==5.15.1
PyQtWebEngine==5.15.0

View File

@ -1,2 +1,2 @@
PyQt5
PyQtWebEngine
PyQtWebEngine!=5.15.1

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -2,4 +2,4 @@
pathspec==0.8.0
PyYAML==5.3.1
yamllint==1.24.2
yamllint==1.25.0

View File

@ -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/

View File

@ -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;

View File

@ -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

View File

@ -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."

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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.

View File

@ -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:

View File

@ -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):

View File

@ -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:

View File

@ -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()

View File

@ -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)

View File

@ -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%" '

View File

@ -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.

View File

@ -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'}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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):

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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]

View File

@ -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'))

View File

@ -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()

View File

@ -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

View File

@ -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):

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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:

View File

@ -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],

View File

@ -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()):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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."""

View File

@ -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):

View File

@ -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

View File

@ -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(

View File

@ -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=''):

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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."""

View File

@ -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:

View File

@ -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]:

View File

@ -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

View File

@ -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,

View File

@ -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:

View File

@ -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

View File

@ -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>

View File

@ -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]

View File

@ -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.

View File

@ -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 = []

View File

@ -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)

View File

@ -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)

View File

@ -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