Merge branch 'master' into more-sophisticated-adblock
This commit is contained in:
commit
12e1b157e7
|
|
@ -10,8 +10,8 @@ image:
|
|||
|
||||
environment:
|
||||
PYTHONUNBUFFERED: 1
|
||||
PYTHON: C:\Python37-x64\python.exe
|
||||
TESTENV: py37-pyqt514
|
||||
PYTHON: C:\Python38-x64\python.exe
|
||||
TESTENV: py38-pyqt514
|
||||
|
||||
install:
|
||||
- '%PYTHON% --version'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 1.10.1
|
||||
current_version = 1.12.0
|
||||
commit = True
|
||||
message = Release v{new_version}
|
||||
tag = True
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
name: ❓ Support Question
|
||||
about: It's okay to ask questions via GitHub, but IRC/Reddit/Mailinglist might be better.
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
While it's fine to ask questions here, check the documentation for better
|
||||
ways to get help:
|
||||
|
||||
https://github.com/qutebrowser/qutebrowser#getting-help
|
||||
-->
|
||||
|
||||
**Version info (see `:version`)**:
|
||||
|
||||
**If applicable: Does the issue happen if you start with `--temp-basedir`?**:
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
---
|
||||
name: ⛔ Security Issue
|
||||
about: Contact mail@qutebrowser.org for security issues.
|
||||
|
||||
---
|
||||
|
||||
⚠ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW.
|
||||
|
||||
If you have found a security issue in qutebrowser, please send the details to
|
||||
mail [at] qutebrowser.org and don't disclose it publicly until we can provide a
|
||||
fix for it
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ❓ Support Question
|
||||
url: https://github.com/qutebrowser/qutebrowser/discussions
|
||||
about: Use GitHub's new discussions feature for questions
|
||||
|
|
@ -0,0 +1 @@
|
|||
Please report security bugs to [security@qutebrowser.org](mailto:security@qutebrowser.org).
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
name: "Code scanning"
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 3 * * 1'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: javascript, python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
|
@ -27,7 +27,7 @@ __pycache__
|
|||
/.pytest_cache
|
||||
/.testmondata
|
||||
/.hypothesis
|
||||
/.mypy_cache
|
||||
.mypy_cache
|
||||
/prof
|
||||
/venv
|
||||
TODO
|
||||
|
|
@ -44,3 +44,4 @@ TODO
|
|||
/doc/extapi/_build
|
||||
/misc/nsis/include
|
||||
/misc/nsis/plugins
|
||||
/wheels
|
||||
|
|
|
|||
|
|
@ -4,23 +4,23 @@
|
|||
python_version = 3.6
|
||||
|
||||
# --strict
|
||||
warn_redundant_casts = True
|
||||
warn_unused_ignores = True
|
||||
warn_unused_configs = True
|
||||
# disallow_any_generics = True
|
||||
disallow_subclassing_any = True
|
||||
disallow_untyped_decorators = True
|
||||
## https://github.com/python/mypy/issues/5957
|
||||
# warn_unused_configs = True
|
||||
# disallow_untyped_calls = True
|
||||
# disallow_untyped_defs = True
|
||||
## https://github.com/python/mypy/issues/5954
|
||||
# disallow_incomplete_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
check_untyped_defs = True
|
||||
disallow_untyped_decorators = True
|
||||
# no_implicit_optional = True
|
||||
warn_redundant_casts = True
|
||||
warn_unused_ignores = True
|
||||
# warn_return_any = True
|
||||
warn_unreachable = True
|
||||
# no_implicit_reexport = True
|
||||
strict_equality = True
|
||||
|
||||
# Other strictness flags
|
||||
strict_equality = True
|
||||
warn_unreachable = True
|
||||
|
||||
# Output
|
||||
show_error_codes = True
|
||||
|
|
@ -69,76 +69,51 @@ disallow_subclassing_any = False
|
|||
|
||||
[mypy-qutebrowser.browser.browsertab]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.browser.hints]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.misc.objects]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.misc.debugcachestats]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.misc.utilcmds]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.misc.throttle]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.misc.backendproblem]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.misc.eventfilter]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.commands.cmdutils]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.config.*]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.api.*]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.components.*]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.extensions.*]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.browser.webelem]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.browser.webkit.webkitelem]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.browser.webengine.webengineelem]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.keyinput.*]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.utils.*]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
||||
[mypy-qutebrowser.mainwindow.statusbar.command]
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
|
|
@ -60,7 +60,7 @@ no-docstring-rgx=(^_|^main$)
|
|||
|
||||
[FORMAT]
|
||||
max-line-length=79
|
||||
ignore-long-lines=(<?https?://|^# Copyright 201\d|link:)
|
||||
ignore-long-lines=(<?https?://|file://|^# Copyright 201\d|link:)
|
||||
expected-line-ending-format=LF
|
||||
|
||||
[VARIABLES]
|
||||
|
|
|
|||
24
.travis.yml
24
.travis.yml
|
|
@ -19,8 +19,8 @@ matrix:
|
|||
services: docker
|
||||
|
||||
### Archlinux QtWebEngine with testing/KDE-Unstable
|
||||
# - env: DOCKER=archlinux-webengine-unstable QUTE_BDD_WEBENGINE=true
|
||||
# services: docker
|
||||
- env: DOCKER=archlinux-webengine-unstable QUTE_BDD_WEBENGINE=true
|
||||
services: docker
|
||||
|
||||
### PyQt 5.7.1 (Python 3.5)
|
||||
- python: 3.5
|
||||
|
|
@ -45,7 +45,6 @@ matrix:
|
|||
|
||||
### PyQt 5.12 (Python 3.8)
|
||||
- env: TESTENV=py38-pyqt512
|
||||
# http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
|
|
@ -53,20 +52,31 @@ matrix:
|
|||
|
||||
### PyQt 5.13 (Python 3.8)
|
||||
- env: TESTENV=py38-pyqt513
|
||||
# http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libxkbcommon-x11-0
|
||||
|
||||
### PyQt 5.14 (Python 3.8, with coverage)
|
||||
- env: TESTENV=py38-pyqt514-cov
|
||||
# http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
|
||||
### PyQt 5.14 (Python 3.8)
|
||||
- env: TESTENV=py38-pyqt514
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libxkbcommon-x11-0
|
||||
|
||||
### PyQt 5.15 (Python 3.8, with coverage)
|
||||
- env: TESTENV=py38-pyqt515-cov
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libxkbcommon-x11-0
|
||||
- libxcb-icccm4
|
||||
- libxcb-image0
|
||||
- libxcb-keysyms1
|
||||
- libxcb-randr0
|
||||
- libxcb-render-util0
|
||||
- libxcb-xinerama0
|
||||
|
||||
### macOS Mojave (10.14)
|
||||
- os: osx
|
||||
env: TESTENV=py37-pyqt514 OSX=mojave
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
recursive-include qutebrowser *.py
|
||||
recursive-include qutebrowser/img *.svg *.png
|
||||
recursive-include qutebrowser/javascript *.js
|
||||
graft tests
|
||||
graft qutebrowser/html
|
||||
graft qutebrowser/3rdparty
|
||||
graft icons
|
||||
|
|
@ -16,10 +17,10 @@ include misc/org.qutebrowser.qutebrowser.desktop
|
|||
include misc/org.qutebrowser.qutebrowser.appdata.xml
|
||||
include misc/Makefile
|
||||
include requirements.txt
|
||||
include tox.ini
|
||||
include qutebrowser.py
|
||||
include misc/cheatsheet.svg
|
||||
include qutebrowser/config/configdata.yml
|
||||
include pytest.ini
|
||||
|
||||
prune www
|
||||
prune scripts/dev
|
||||
|
|
@ -29,16 +30,17 @@ exclude scripts/asciidoc2html.py
|
|||
recursive-exclude doc *.asciidoc
|
||||
include doc/qutebrowser.1.asciidoc
|
||||
include doc/changelog.asciidoc
|
||||
prune tests
|
||||
prune qutebrowser/3rdparty
|
||||
exclude pytest.ini
|
||||
exclude mypy.ini
|
||||
exclude tox.ini
|
||||
exclude qutebrowser/javascript/.eslintrc.yaml
|
||||
exclude qutebrowser/javascript/.eslintignore
|
||||
exclude doc/help
|
||||
exclude .*
|
||||
exclude misc/qutebrowser.spec
|
||||
exclude misc/qutebrowser.rcc
|
||||
exclude tests/unit/scripts/test_run_vulture.py
|
||||
exclude tests/unit/scripts/test_check_coverage.py
|
||||
prune doc/extapi
|
||||
prune misc/nsis
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,6 @@ time, your help is needed! See the
|
|||
https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more
|
||||
information. Depending on your sign-up date and how long you keep a certain
|
||||
level, you can get qutebrowser t-shirts, stickers and more!
|
||||
|
||||
Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub
|
||||
Sponsors (up to a $5000 total) will be doubled until October 2020.
|
||||
// QUTE_WEB_HIDE_END
|
||||
|
||||
Screenshots
|
||||
|
|
@ -84,6 +81,11 @@ get sent to the general qutebrowser@ list).
|
|||
If you're a reddit user, there's a
|
||||
https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] subreddit there.
|
||||
|
||||
Finally, qutebrowser is participating in the Beta for GitHub's new Discussions
|
||||
feature, so you can also use the
|
||||
https://github.com/qutebrowser/qutebrowser/discussions[discussions tab] on
|
||||
GitHub to get in touch.
|
||||
|
||||
Contributions / Bugs
|
||||
--------------------
|
||||
|
||||
|
|
@ -108,9 +110,10 @@ Requirements
|
|||
|
||||
The following software and libraries are required to run qutebrowser:
|
||||
|
||||
* https://www.python.org/[Python] 3.5.2 or newer (3.6 recommended)
|
||||
* https://www.qt.io/[Qt] 5.7.1 or newer (5.14 recommended; support for < 5.9
|
||||
will be dropped soon) with the following modules:
|
||||
* 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.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
|
||||
- QtQuick (part of qtbase in some distributions)
|
||||
- QtSQL (part of qtbase in some distributions)
|
||||
|
|
@ -123,7 +126,7 @@ The following software and libraries are required to run qutebrowser:
|
|||
avoid visiting untrusted websites and using it for transmission of
|
||||
sensitive data.**
|
||||
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
||||
(5.14 recommended, support for < 5.9 will be dropped soon) for Python 3
|
||||
(5.14 recommended, support for < 5.11 will be dropped soon) for Python 3
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||
* https://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
|
|
@ -154,9 +157,6 @@ https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more
|
|||
information. Depending on your sign-up date and how long you keep a certain
|
||||
level, you can get qutebrowser t-shirts, stickers and more!
|
||||
|
||||
Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub
|
||||
Sponsors (up to a $5000 total) will be doubled until October 2020!
|
||||
|
||||
Alternatively, the following donation methods are available -- note that
|
||||
eligibility for swag (shirts/stickers/etc.) is handled on a case-by-case basis
|
||||
for those, please mailto:mail@qutebrowser.org[get in touch] for details.
|
||||
|
|
@ -212,8 +212,8 @@ link:doc/backers.asciidoc[crowdfunding campaigns]!
|
|||
Similar projects
|
||||
----------------
|
||||
|
||||
Many projects with a similar goal as qutebrowser exist.
|
||||
Most of them were inspirations for qutebrowser in some way, thanks for that!
|
||||
Various projects with a similar goal like qutebrowser exist.
|
||||
Many of them were inspirations for qutebrowser in some way, thanks for that!
|
||||
|
||||
Active
|
||||
~~~~~~
|
||||
|
|
@ -221,8 +221,9 @@ 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://github.com/next-browser/next/[next] (Lisp, Emacs-like but also offers Vim bindings, various backends - note there was a http://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly)
|
||||
* https://next.atlas.engineer/[next] (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://github.com/parkouss/webmacs/[webmacs] (Python, Emacs-like with QtWebEngine)
|
||||
* https://vieb.dev/[Vieb] (JavaScript, Electron)
|
||||
* Chrome/Chromium addons:
|
||||
https://vimium.github.io/[Vimium],
|
||||
* Firefox addons (based on WebExtensions):
|
||||
|
|
|
|||
|
|
@ -15,9 +15,226 @@ 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.11.0 (unreleased)
|
||||
v1.13.0 (unreleased)
|
||||
--------------------
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The `:debug-log-level` command was removed as it's replaced by the new
|
||||
`logging.level.console` setting.
|
||||
- The `qute://plainlog` special page got replaced by `qute://log?plain` - the
|
||||
names of those pages is considered an implementation detail, and
|
||||
`:messages --plain` should be used instead.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- New handling of bindings in hint mode which fixes various bugs and allows for
|
||||
single-letter keybindings in hint mode.
|
||||
- The `tor_identity` userscript now takes the password via a `-p` flag and has
|
||||
a new `-c` flag to customize the Tor control port.
|
||||
- `:config-write-py` now adds a note about `config.py` files being targeted at
|
||||
advanced users.
|
||||
- `:report` now takes two optional arguments for bug/contact information, so
|
||||
that it can be used without the report window popping up.
|
||||
- New `t[Cc][Hh]` default bindings which work similarly to the `t[Ss][Hh]`
|
||||
bindings for JavaScript but toggle cookie permissions.
|
||||
- The `:message` command now takes a `--logfilter` / `-f` argument, which is a
|
||||
list of logging categories to show.
|
||||
- The `:debug-log-filter` command now understands the full logfilter syntax.
|
||||
- Changes to settings:
|
||||
* `fonts.tabs` has been split into `fonts.tabs.{selected,unselected}` (see
|
||||
below).
|
||||
* `statusbar.hide` has been renamed to `statusbar.show` with the possible
|
||||
values being `always` (`hide = False`), `never` (`hide = True`) or
|
||||
`in-mode` (new, only show statusbar outside of normal mode.
|
||||
* The `QtFont` config type formerly used for `fonts.tabs` and
|
||||
`fonts.debug_console` is now removed and entirely replaced by `Font`. The
|
||||
former distinction was mainly an implementation detail, and the accepted
|
||||
values shouldn't have changed.
|
||||
* `input.rocker_gestures` has been renamed to `input.mouse.rocker_gestures`.
|
||||
* `content.dns_prefetch` is now enabled by default again, since the crashes
|
||||
it caused are now fixed (Qt 5.15) or worked around.
|
||||
* `scrolling.bar` supports a new `overlay` value to show an overlay
|
||||
scrollbar, which is now the default. On unsupported configurations (on Qt <
|
||||
5.11, with QtWebKit or on macOS), the value falls back to `when-searching`
|
||||
or `never` (QtWebKit).
|
||||
- The statusbar now shows partial keychains in all modes (e.g. while hinting)
|
||||
- Small performance improvements.
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New settings:
|
||||
* `logging.level.ram` and `logging.level.console` to configure the default
|
||||
logging levels via the config.
|
||||
* `fonts.tabs.selected` and `fonts.tabs.unselected` to set the font of the
|
||||
selected tab independently from unselected tabs (e.g. to make it bold).
|
||||
* `input.mouse.back_forward_buttons` which can be set to `false` to disable
|
||||
back/forward mouse buttons.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Crash when `tabs.focus_stack_size` is set to -1.
|
||||
- Crash when a `pdf.js` file for PDF.js exists, but `viewer.html` does not.
|
||||
- Crash when `:completion-item-yank --sel` is used on a platform without
|
||||
primary selection support (e.g. Windows/macOS).
|
||||
- Crash when there's a feature permission request from Qt with an invalid URL
|
||||
(which seems to happen with Qt 5.15 sometimes).
|
||||
- Crash in rare cases where QtWebKit/QtWebEngine imports fail in unexpected
|
||||
ways.
|
||||
- Crash when something removed qutebrowser's IPC socket file and it's been
|
||||
running for 6 hours.
|
||||
- `:config-write-py` now works with paths starting with `~/...` again.
|
||||
- New site-specific quirk for a missing `globalThis` in Qt <= 5.12 on Reddit
|
||||
and Spotify.
|
||||
- When `;` is added to `hints.chars`, using hint labels containing `;;` now
|
||||
works properly.
|
||||
- Hint letters outside of ASCII should now work.
|
||||
- When `bindings.key_mappings` is used with hints, it now works properly with
|
||||
letters outside of ASCII as well.
|
||||
- With Qt 5.15, the audible/muted indicators are not updated properly due to a
|
||||
Qt bug. This release adds a workaround so that at least the muted indicator
|
||||
is shown properly.
|
||||
- As a workaround for crashes with QtWebEngine versions between 5.12 and 5.14
|
||||
(inclusive), changing the user agent (`content.headers.user_agent`) exposed
|
||||
to JS now requires a restart. The corresponding HTTP header is not affected.
|
||||
|
||||
v1.12.0 (2020-06-01)
|
||||
--------------------
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- `tox -e mkvenv` which was deprecated in qutebrowser v1.10.0 is now
|
||||
removed. Use the `mkvenv.py` script instead.
|
||||
- Support for using `config.bind(key, None)` in `config.py` to unbind a
|
||||
key was deprecated in v1.8.2 and is now removed. Use
|
||||
`config.unbind(key)` instead.
|
||||
- `:yank markdown` was deprecated in v1.7.0 and is now removed. Use
|
||||
`:yank inline [{title}]({url})` instead.
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New `:debug-keytester` command, which shows a "key tester" widget.
|
||||
Previously, that was only available as a separate application via `python3 -m
|
||||
scripts.keytester`.
|
||||
- New `:config-diff` command which opens the `qute://configdiff` page.
|
||||
- New `--debug-flag log-cookies` to log cookies to the debug log.
|
||||
- New `colors.contextmenu.disabled.{fg,bg}` settings to customize colors for
|
||||
disabled items in the context menu.
|
||||
- New line selection mode (`:toggle-selection --line`), bound to `Shift-V` in caret mode.
|
||||
- New `colors.webpage.darkmode.*` settings to control Chromium's dark mode.
|
||||
Note that those settings only work with QtWebEngine on Qt >= 5.14 and require
|
||||
a restart of qutebrowser.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Windows and macOS releases now ship Qt 5.15, which is based on Chromium
|
||||
80.0.3987.163 with security fixes up to 81.0.4044.138.
|
||||
- The `content.cookies.accept` setting now accepts URL patterns.
|
||||
- Tests are now included in release tarballs. Note that only running them with
|
||||
the exact dependencies listed in
|
||||
`misc/requirements/requirements-tests.txt{,-raw}` is supported.
|
||||
- The `:tab-focus` command now has completion for tabs in the current window.
|
||||
- The `bindings.key_mappings` setting now maps `<Ctrl+I>` to the tab key by default.
|
||||
- `:tab-give --private` now detaches a tab into a new private window.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Using `:open -s` now only rewrites `http://` in URLs to `https://`, not other
|
||||
schemes like `qute://`.
|
||||
- When an unhandled exception happens in certain parts of the code (outside of
|
||||
the main thread), qutebrowser did crash or freeze when trying to show its
|
||||
exception handler. This is now fixed.
|
||||
- `:inspector` now works correctly when cookies are disabled globally.
|
||||
- Added workaround for a (Gentoo?) PyQt/packaging issue related to the
|
||||
`QWebEngineFindTextResult` handling added in v1.11.0.
|
||||
- When entering caret selection mode (`v, v`) very early before a page is
|
||||
loaded, an error is now shown instead of a crash happening.
|
||||
- The workaround for session loading with Qt 5.15 now handles
|
||||
`sessions.lazy_restore` so that the saved page is loaded instead of the
|
||||
"stub" page with no possibility to get to the web page.
|
||||
- A site specific quirk to allow typing accented characters on Google
|
||||
Docs was active for docs.google.com, but not drive.google.com. It is
|
||||
now applied for both subdomains.
|
||||
- With older graphics hardware (OpenGL < 4.3) with Qt 5.14 on Wayland, WebGL
|
||||
causes segfaults. Now qutebrowser detects that combination and suggests to
|
||||
disable WebGL or use XWayland.
|
||||
|
||||
v1.11.1 (2020-05-07)
|
||||
--------------------
|
||||
|
||||
Security
|
||||
~~~~~~~~
|
||||
|
||||
- CVE-2020-11054: After a certificate error was overridden by the user,
|
||||
qutebrowser displays the URL as yellow (`colors.statusbar.url.warn.fg`).
|
||||
However, when the affected website was subsequently loaded again, the URL was
|
||||
mistakenly displayed as green (`colors.statusbar.url.success_https`). While
|
||||
the user already has seen a certificate error prompt at this point (or set
|
||||
`content.ssl_strict` to `false` which is not recommended), this could still
|
||||
provide a false sense of security. This is now fixed.
|
||||
|
||||
v1.11.0 (2020-04-27)
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New settings:
|
||||
* `search.wrap` which can be set to false to prevent wrapping around the page
|
||||
when searching. With QtWebEngine, Qt 5.14 or newer is required.
|
||||
* `content.unknown_url_scheme_policy` which allows controlling when an
|
||||
external application is opened for external links (never, from user
|
||||
interaction, always).
|
||||
* `content.fullscreen.overlay_timeout` to configure how long the fullscreen
|
||||
overlay should be displayed. If set to `0`, no overlay is displayed.
|
||||
* `hints.padding` to add additional padding for hints.
|
||||
* `hints.radius` to set a border radius for hints (set to `3` by default).
|
||||
- New placeholders for `url.searchengines` values:
|
||||
* `{unquoted}` inserts the search term without any quoting.
|
||||
* `{semiquoted}` (same as `{}`) quotes most special characters, but slashes
|
||||
remain unquoted.
|
||||
* `{quoted}` (same as `{}` in earlier releases) also quotes slashes.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- First adaptions to Qt 5.15, including a stop-gap measure for session loading
|
||||
not working properly with it.
|
||||
- Searching now wraps around the page by default with QtWebKit (where it didn't
|
||||
before). Set `search.wrap` to `false` to restore the old behavior.
|
||||
- The `{}` placeholder for search engines (the `url.searchengines` setting) now
|
||||
does not quote slashes anymore, but other characters typically encoded in
|
||||
URLs still get encoded. This matches the behavior of search engines in
|
||||
Chromium. To revert to the old behavior, use `{quoted}` instead.
|
||||
- The `content.windowed_fullscreen` setting got renamed to
|
||||
`content.fullscreen.window`.
|
||||
- Mouse-wheel scrolling is now prevented while hints are active.
|
||||
- Changes to userscripts:
|
||||
* `qute-bitwarden` now has an optional `--totp` flag which can be used
|
||||
to copy TOTP codes to clipboard (requires the `pyperclip` module).
|
||||
* `readability-js` now opens readability tabs next to the original
|
||||
tab (using the `:open --related` flag).
|
||||
* `readability-js` now displays a favicon for readability tabs.
|
||||
* `password_fill` now triggers a `change` JavaScript event after filling the
|
||||
data.
|
||||
- The `dictcli.py` script now shows better error messages.
|
||||
- Various improvements to the `mkvenv.py` script (mainly useful for development).
|
||||
- Minor performance improvements.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- A warning about old Qt versions is now also shown with Qt 5.9 and 5.10, as
|
||||
support for Qt < 5.11 will be dropped in qutebrowser v2.0.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
|
|
@ -25,6 +242,25 @@ Fixed
|
|||
- The proxied `window` global is now shared between different
|
||||
Greasemonkey scripts (but still separate from the page's `window`), to
|
||||
match the original Greasemonkey implementation.
|
||||
- The `--output-messages` (`-m`) flag added in v1.9.0 now also works correctly
|
||||
when using `:spawn --userscript`.
|
||||
- `:version` and `--version` now don't crash if there's an (invalid)
|
||||
`/etc/os-release` file which has non-comment lines without a `=` character.
|
||||
- Scripts in `scripts/` now report errors to `stderr` correctly, instead of
|
||||
using `stdout`.
|
||||
|
||||
v1.10.2 (2020-04-17)
|
||||
--------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Windows and macOS releases now bundle Qt 5.14.2, including security fixes up
|
||||
to Chromium 80.0.3987.132.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The WhatsApp workaround now also works when using WhatsApp in languages other
|
||||
than English.
|
||||
- The `mkvenv.py` script now also works properly on Windows.
|
||||
|
|
|
|||
|
|
@ -707,6 +707,9 @@ qutebrowser release
|
|||
* Make sure there are no unstaged changes and the tests are green.
|
||||
* Make sure all issues with the related milestone are closed.
|
||||
* Consider updating the completions for `content.headers.user_agent` in `configdata.yml`.
|
||||
* Minor release: Consider updating some files from master:
|
||||
- `misc/requirements/` and `requirements.txt`
|
||||
- `scripts/`
|
||||
* Make sure Python is up-to-date on build machines.
|
||||
* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones as closed.
|
||||
* Update changelog in master branch
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ Why another browser?::
|
|||
Read the next few questions to find out why I was unhappy with existing
|
||||
software.
|
||||
|
||||
What's wrong with link:https://bitbucket.org/portix/dwb/[dwb]/link:https://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://mason-larobina.github.io/luakit/[luakit]/jumanji/... (projects based on WebKitGTK)?::
|
||||
What's wrong with link:https://bitbucket.org/portix/dwb/[dwb]/link:https://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://luakit.github.io/[luakit]/jumanji/... (projects based on WebKitGTK)?::
|
||||
Most of them are based on the https://webkitgtk.org/[WebKitGTK+]
|
||||
https://webkitgtk.org/reference/webkitgtk/stable/index.html[WebKit1] API,
|
||||
which causes a lot of crashes. As the GTK API using WebKit1 is
|
||||
|
|
@ -305,13 +305,13 @@ If you ever need to renew any of these certificates, you can take a look
|
|||
at the currently imported certificates using:
|
||||
+
|
||||
----
|
||||
certutil -D "sql:${HOME}/.pki/nssdb" -L
|
||||
certutil -d "sql:${HOME}/.pki/nssdb" -L
|
||||
----
|
||||
+
|
||||
Then remove the expired certificates using:
|
||||
+
|
||||
----
|
||||
certutil -D "sql:${HOME}/.pki/nssdb" -D -n "My Fancy Certificate Nickname"
|
||||
certutil -d "sql:${HOME}/.pki/nssdb" -D -n "My Fancy Certificate Nickname"
|
||||
----
|
||||
+
|
||||
And then import the new and valid certificates using the procedure
|
||||
|
|
@ -367,14 +367,15 @@ up for a monthly donation to The-Compiler (qutebrowser's main developer),
|
|||
allowing him to work part-time on qutebrowser. If you keep your donation level
|
||||
for long enough, you can get some qutebrowser stickers!
|
||||
|
||||
Why GitHub Sponsors? What is the GitHub Matching Fund?::
|
||||
Thanks to the
|
||||
https://help.github.com/en/github/supporting-the-open-source-community-with-github-sponsors/about-github-sponsors#about-the-github-sponsors-matching-fund[GitHub Sponsors Matching Fund],
|
||||
all donations are doubled by GitHub in the first year, up to a $5000 total limit.
|
||||
Why GitHub Sponsors?::
|
||||
GitHub Sponsors is a crowdfundign platform nicely integrated with
|
||||
qutebrowser's existing GitHub page and a better offering than alternatives such
|
||||
as Patreon or Liberapay.
|
||||
+
|
||||
Even outside of the matching fund, GitHub Sponsors is nicely integrated with
|
||||
qutebrowser's existing GitHub page and a better offering than alternatives such
|
||||
as Patreon or Liberapay.
|
||||
It also offers a
|
||||
https://help.github.com/en/github/supporting-the-open-source-community-with-github-sponsors/about-github-sponsors#about-the-github-sponsors-matching-fund[Matching Fund]
|
||||
which matches all donations until a cap of $5000, which has already been
|
||||
reached by qutebrowser.
|
||||
|
||||
Is it possible to contribute via a one-time donation instead?::
|
||||
If you prefer a one-time donation, there are various possibilities:
|
||||
|
|
@ -382,8 +383,7 @@ Is it possible to contribute via a one-time donation instead?::
|
|||
- Select a tier which covers the total amount you'd like to donate (note that
|
||||
payments are prorated based on the current date). After the payment is
|
||||
processed, cancel your GitHub sponsors subscription again. This has a big
|
||||
benefit: Thanks to GitHub's matching fund, your donation will be doubled (and
|
||||
nothing will be lost to fees).
|
||||
benefit: There are no fees deducted from your amount.
|
||||
+
|
||||
- Sign up for a lower recurring donation instead.
|
||||
+
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ possible to run or bind multiple commands by separating them with `;;`.
|
|||
|<<config-cycle,config-cycle>>|Cycle an option between multiple values.
|
||||
|<<config-dict-add,config-dict-add>>|Add a key/value pair to a dictionary option.
|
||||
|<<config-dict-remove,config-dict-remove>>|Remove a key from a dict.
|
||||
|<<config-diff,config-diff>>|Show all customized options.
|
||||
|<<config-edit,config-edit>>|Open the config.py file in the editor.
|
||||
|<<config-list-add,config-list-add>>|Append a value to a config option that is a list.
|
||||
|<<config-list-remove,config-list-remove>>|Remove a value from a list.
|
||||
|
|
@ -334,6 +335,16 @@ Remove a key from a dict.
|
|||
==== optional arguments
|
||||
* +*-t*+, +*--temp*+: Remove value temporarily until qutebrowser is closed.
|
||||
|
||||
[[config-diff]]
|
||||
=== config-diff
|
||||
Syntax: +:config-diff [*--old*]+
|
||||
|
||||
Show all customized options.
|
||||
|
||||
==== optional arguments
|
||||
* +*-o*+, +*--old*+: Show difference for the pre-v1.0 files (qutebrowser.conf/keys.conf).
|
||||
|
||||
|
||||
[[config-edit]]
|
||||
=== config-edit
|
||||
Syntax: +:config-edit [*--no-source*]+
|
||||
|
|
@ -805,7 +816,7 @@ Show a warning message in the statusbar.
|
|||
|
||||
[[messages]]
|
||||
=== messages
|
||||
Syntax: +:messages [*--plain*] [*--tab*] [*--bg*] [*--window*] ['level']+
|
||||
Syntax: +:messages [*--plain*] [*--tab*] [*--bg*] [*--window*] [*--logfilter* 'logfilter'] ['level']+
|
||||
|
||||
Show a log of past messages.
|
||||
|
||||
|
|
@ -818,6 +829,9 @@ Show a log of past messages.
|
|||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
* +*-b*+, +*--bg*+: Open in a background tab.
|
||||
* +*-w*+, +*--window*+: Open in a new window.
|
||||
* +*-f*+, +*--logfilter*+: A comma-separated filter string of logging categories. If the filter string starts with an exclamation mark, it
|
||||
is negated.
|
||||
|
||||
|
||||
[[navigate]]
|
||||
=== navigate
|
||||
|
|
@ -1011,8 +1025,15 @@ Which count to pass the command.
|
|||
|
||||
[[report]]
|
||||
=== report
|
||||
Syntax: +:report ['info'] ['contact']+
|
||||
|
||||
Report a bug in qutebrowser.
|
||||
|
||||
==== positional arguments
|
||||
* +'info'+: Information about the bug report. If given, no report dialog shows up.
|
||||
|
||||
* +'contact'+: Contact information for the report.
|
||||
|
||||
[[restart]]
|
||||
=== restart
|
||||
Restart qutebrowser while keeping existing tabs open.
|
||||
|
|
@ -1337,7 +1358,7 @@ The tab index to focus, starting with 1.
|
|||
|
||||
[[tab-give]]
|
||||
=== tab-give
|
||||
Syntax: +:tab-give [*--keep*] ['win-id']+
|
||||
Syntax: +:tab-give [*--keep*] [*--private*] ['win-id']+
|
||||
|
||||
Give the current tab to a new or existing window if win_id given.
|
||||
|
||||
|
|
@ -1348,6 +1369,7 @@ If no win_id is given, the tab will get detached into a new window.
|
|||
|
||||
==== optional arguments
|
||||
* +*-k*+, +*--keep*+: If given, keep the old tab around.
|
||||
* +*-p*+, +*--private*+: If the tab should be detached into a private instance.
|
||||
|
||||
==== count
|
||||
Overrides win_id (index starts at 1 for win_id=0).
|
||||
|
|
@ -1485,8 +1507,6 @@ Yank (copy) something to the clipboard or primary selection.
|
|||
- `title`: The current page's title.
|
||||
- `domain`: The current scheme, domain, and port number.
|
||||
- `selection`: The selection under the cursor.
|
||||
- `markdown`: Yank title and URL in markdown format
|
||||
(deprecated, use `:yank inline [{title}]({url})` instead).
|
||||
- `inline`: Yank the text contained in the 'inline' argument.
|
||||
|
||||
|
||||
|
|
@ -1894,8 +1914,13 @@ This acts like readline's yank.
|
|||
|
||||
[[toggle-selection]]
|
||||
=== toggle-selection
|
||||
Syntax: +:toggle-selection [*--line*]+
|
||||
|
||||
Toggle caret selection mode.
|
||||
|
||||
==== optional arguments
|
||||
* +*-l*+, +*--line*+: Enables line-selection.
|
||||
|
||||
|
||||
== Debugging commands
|
||||
These commands are mainly intended for debugging. They are hidden if qutebrowser was started without the `--debug`-flag.
|
||||
|
|
@ -1911,9 +1936,9 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|
|||
|<<debug-crash,debug-crash>>|Crash for debugging purposes.
|
||||
|<<debug-dump-history,debug-dump-history>>|Dump the history to a file in the old pre-SQL format.
|
||||
|<<debug-dump-page,debug-dump-page>>|Dump the current page's content to a file.
|
||||
|<<debug-keytester,debug-keytester>>|Show a keytester widget.
|
||||
|<<debug-log-capacity,debug-log-capacity>>|Change the number of log lines to be stored in RAM.
|
||||
|<<debug-log-filter,debug-log-filter>>|Change the log filter for console logging.
|
||||
|<<debug-log-level,debug-log-level>>|Change the log level for console logging.
|
||||
|<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a web page.
|
||||
|<<debug-set-fake-clipboard,debug-set-fake-clipboard>>|Put data into the fake clipboard and enable logging, used for tests.
|
||||
|<<debug-trace,debug-trace>>|Trace executed code via hunter.
|
||||
|
|
@ -1965,6 +1990,10 @@ Dump the current page's content to a file.
|
|||
==== optional arguments
|
||||
* +*-p*+, +*--plain*+: Write plain text instead of HTML.
|
||||
|
||||
[[debug-keytester]]
|
||||
=== debug-keytester
|
||||
Show a keytester widget.
|
||||
|
||||
[[debug-log-capacity]]
|
||||
=== debug-log-capacity
|
||||
Syntax: +:debug-log-capacity 'capacity'+
|
||||
|
|
@ -1984,15 +2013,6 @@ Change the log filter for console logging.
|
|||
* +'filters'+: A comma separated list of logger names. Can also be "none" to clear any existing filters.
|
||||
|
||||
|
||||
[[debug-log-level]]
|
||||
=== debug-log-level
|
||||
Syntax: +:debug-log-level 'level'+
|
||||
|
||||
Change the log level for console logging.
|
||||
|
||||
==== positional arguments
|
||||
* +'level'+: The log level to set.
|
||||
|
||||
[[debug-pyeval]]
|
||||
=== debug-pyeval
|
||||
Syntax: +:debug-pyeval [*--file*] [*--quiet*] 's'+
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
|<<colors.completion.odd.bg,colors.completion.odd.bg>>|Background color of the completion widget for odd rows.
|
||||
|<<colors.completion.scrollbar.bg,colors.completion.scrollbar.bg>>|Color of the scrollbar in the completion view.
|
||||
|<<colors.completion.scrollbar.fg,colors.completion.scrollbar.fg>>|Color of the scrollbar handle in the completion view.
|
||||
|<<colors.contextmenu.disabled.bg,colors.contextmenu.disabled.bg>>|Background color of disabled items in the context menu.
|
||||
|<<colors.contextmenu.disabled.fg,colors.contextmenu.disabled.fg>>|Foreground color of disabled items in the context menu.
|
||||
|<<colors.contextmenu.menu.bg,colors.contextmenu.menu.bg>>|Background color of the context menu.
|
||||
|<<colors.contextmenu.menu.fg,colors.contextmenu.menu.fg>>|Foreground color of the context menu.
|
||||
|<<colors.contextmenu.selected.bg,colors.contextmenu.selected.bg>>|Background color of the context menu's selected item.
|
||||
|
|
@ -109,6 +111,15 @@
|
|||
|<<colors.tabs.selected.odd.bg,colors.tabs.selected.odd.bg>>|Background color of selected odd tabs.
|
||||
|<<colors.tabs.selected.odd.fg,colors.tabs.selected.odd.fg>>|Foreground color of selected odd tabs.
|
||||
|<<colors.webpage.bg,colors.webpage.bg>>|Background color for webpages if unset (or empty to use the theme's color).
|
||||
|<<colors.webpage.darkmode.algorithm,colors.webpage.darkmode.algorithm>>|Which algorithm to use for modifying how colors are rendered with darkmode.
|
||||
|<<colors.webpage.darkmode.contrast,colors.webpage.darkmode.contrast>>|Contrast for dark mode.
|
||||
|<<colors.webpage.darkmode.enabled,colors.webpage.darkmode.enabled>>|Render all web contents using a dark theme.
|
||||
|<<colors.webpage.darkmode.grayscale.all,colors.webpage.darkmode.grayscale.all>>|Render all colors as grayscale.
|
||||
|<<colors.webpage.darkmode.grayscale.images,colors.webpage.darkmode.grayscale.images>>|Desaturation factor for images in dark mode.
|
||||
|<<colors.webpage.darkmode.policy.images,colors.webpage.darkmode.policy.images>>|Which images to apply dark mode to.
|
||||
|<<colors.webpage.darkmode.policy.page,colors.webpage.darkmode.policy.page>>|Which pages to apply dark mode to.
|
||||
|<<colors.webpage.darkmode.threshold.background,colors.webpage.darkmode.threshold.background>>|Threshold for inverting background elements with dark mode.
|
||||
|<<colors.webpage.darkmode.threshold.text,colors.webpage.darkmode.threshold.text>>|Threshold for inverting text with dark mode.
|
||||
|<<colors.webpage.prefers_color_scheme_dark,colors.webpage.prefers_color_scheme_dark>>|Force `prefers-color-scheme: dark` colors for websites.
|
||||
|<<completion.cmd_history_max_items,completion.cmd_history_max_items>>|Number of commands to save in the command history.
|
||||
|<<completion.delay,completion.delay>>|Delay (in milliseconds) before updating completions after typing a character.
|
||||
|
|
@ -141,6 +152,8 @@
|
|||
|<<content.desktop_capture,content.desktop_capture>>|Allow websites to share screen content.
|
||||
|<<content.dns_prefetch,content.dns_prefetch>>|Try to pre-fetch DNS entries to speed up browsing.
|
||||
|<<content.frame_flattening,content.frame_flattening>>|Expand each subframe to its contents.
|
||||
|<<content.fullscreen.overlay_timeout,content.fullscreen.overlay_timeout>>|Set fullscreen notification overlay timeout in milliseconds.
|
||||
|<<content.fullscreen.window,content.fullscreen.window>>|Limit fullscreen to the browser window (does not expand to fill the screen).
|
||||
|<<content.geolocation,content.geolocation>>|Allow websites to request geolocations.
|
||||
|<<content.headers.accept_language,content.headers.accept_language>>|Value to send in the `Accept-Language` header.
|
||||
|<<content.headers.custom,content.headers.custom>>|Custom headers for qutebrowser HTTP requests.
|
||||
|
|
@ -175,10 +188,10 @@
|
|||
|<<content.register_protocol_handler,content.register_protocol_handler>>|Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|
||||
|<<content.site_specific_quirks,content.site_specific_quirks>>|Enable quirks (such as faked user agent headers) needed to get specific sites to work properly.
|
||||
|<<content.ssl_strict,content.ssl_strict>>|Validate SSL handshakes.
|
||||
|<<content.unknown_url_scheme_policy,content.unknown_url_scheme_policy>>|How navigation requests to URLs with unknown schemes are handled.
|
||||
|<<content.user_stylesheets,content.user_stylesheets>>|List of user stylesheet filenames to use.
|
||||
|<<content.webgl,content.webgl>>|Enable WebGL.
|
||||
|<<content.webrtc_ip_handling_policy,content.webrtc_ip_handling_policy>>|Which interfaces to expose via WebRTC.
|
||||
|<<content.windowed_fullscreen,content.windowed_fullscreen>>|Limit fullscreen to the browser window (does not expand to fill the screen).
|
||||
|<<content.xss_auditing,content.xss_auditing>>|Monitor load requests for cross-site scripting attempts.
|
||||
|<<downloads.location.directory,downloads.location.directory>>|Directory to save downloads to.
|
||||
|<<downloads.location.prompt,downloads.location.prompt>>|Prompt the user for the download location.
|
||||
|
|
@ -203,7 +216,8 @@
|
|||
|<<fonts.messages.warning,fonts.messages.warning>>|Font used for warning messages.
|
||||
|<<fonts.prompts,fonts.prompts>>|Font used for prompts.
|
||||
|<<fonts.statusbar,fonts.statusbar>>|Font used in the statusbar.
|
||||
|<<fonts.tabs,fonts.tabs>>|Font used in the tab bar.
|
||||
|<<fonts.tabs.selected,fonts.tabs.selected>>|Font used for selected tabs.
|
||||
|<<fonts.tabs.unselected,fonts.tabs.unselected>>|Font used for unselected tabs.
|
||||
|<<fonts.web.family.cursive,fonts.web.family.cursive>>|Font family for cursive fonts.
|
||||
|<<fonts.web.family.fantasy,fonts.web.family.fantasy>>|Font family for fantasy fonts.
|
||||
|<<fonts.web.family.fixed,fonts.web.family.fixed>>|Font family for fixed fonts.
|
||||
|
|
@ -225,7 +239,9 @@
|
|||
|<<hints.min_chars,hints.min_chars>>|Minimum number of characters used for hint strings.
|
||||
|<<hints.mode,hints.mode>>|Mode to use for hints.
|
||||
|<<hints.next_regexes,hints.next_regexes>>|Comma-separated list of regular expressions to use for 'next' links.
|
||||
|<<hints.padding,hints.padding>>|Padding (in pixels) for hints.
|
||||
|<<hints.prev_regexes,hints.prev_regexes>>|Comma-separated list of regular expressions to use for 'prev' links.
|
||||
|<<hints.radius,hints.radius>>|Rounding radius (in pixels) for the edges of hints.
|
||||
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|
||||
|<<hints.selectors,hints.selectors>>|CSS selectors used to determine which elements on a page should have hints.
|
||||
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|
||||
|
|
@ -238,16 +254,19 @@
|
|||
|<<input.insert_mode.leave_on_load,input.insert_mode.leave_on_load>>|Leave insert mode when starting a new page load.
|
||||
|<<input.insert_mode.plugins,input.insert_mode.plugins>>|Switch to insert mode when clicking flash and other plugins.
|
||||
|<<input.links_included_in_focus_chain,input.links_included_in_focus_chain>>|Include hyperlinks in the keyboard focus chain when tabbing.
|
||||
|<<input.mouse.back_forward_buttons,input.mouse.back_forward_buttons>>|Enable back and forward buttons on the mouse.
|
||||
|<<input.mouse.rocker_gestures,input.mouse.rocker_gestures>>|Enable Opera-like mouse rocker gestures.
|
||||
|<<input.partial_timeout,input.partial_timeout>>|Timeout (in milliseconds) for partially typed key bindings.
|
||||
|<<input.rocker_gestures,input.rocker_gestures>>|Enable Opera-like mouse rocker gestures.
|
||||
|<<input.spatial_navigation,input.spatial_navigation>>|Enable spatial navigation.
|
||||
|<<keyhint.blacklist,keyhint.blacklist>>|Keychains that shouldn't be shown in the keyhint dialog.
|
||||
|<<keyhint.delay,keyhint.delay>>|Time (in milliseconds) from pressing a key to seeing the keyhint dialog.
|
||||
|<<keyhint.radius,keyhint.radius>>|Rounding radius (in pixels) for the edges of the keyhint dialog.
|
||||
|<<logging.level.console,logging.level.console>>|Level for console (stdout/stderr) logs. Ignored if the `--loglevel` or `--debug` CLI flags are used.
|
||||
|<<logging.level.ram,logging.level.ram>>|Level for in-memory logs.
|
||||
|<<messages.timeout,messages.timeout>>|Duration (in milliseconds) to show messages in the statusbar for.
|
||||
|<<new_instance_open_target,new_instance_open_target>>|How to open links in an existing instance if a new one is launched.
|
||||
|<<new_instance_open_target_window,new_instance_open_target_window>>|Which window to choose when opening links as new tabs.
|
||||
|<<prompt.filebrowser,prompt.filebrowser>>|Show a filebrowser in upload/download prompts.
|
||||
|<<prompt.filebrowser,prompt.filebrowser>>|Show a filebrowser in download prompts.
|
||||
|<<prompt.radius,prompt.radius>>|Rounding radius (in pixels) for the edges of prompts.
|
||||
|<<qt.args,qt.args>>|Additional arguments to pass to Qt, without leading `--`.
|
||||
|<<qt.force_platform,qt.force_platform>>|Force a Qt platform to use.
|
||||
|
|
@ -256,16 +275,17 @@
|
|||
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
||||
|<<qt.low_end_device_mode,qt.low_end_device_mode>>|When to use Chromium's low-end device mode.
|
||||
|<<qt.process_model,qt.process_model>>|Which Chromium process model to use.
|
||||
|<<scrolling.bar,scrolling.bar>>|When to show the scrollbar.
|
||||
|<<scrolling.bar,scrolling.bar>>|When/how to show the scrollbar.
|
||||
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|
||||
|<<search.ignore_case,search.ignore_case>>|When to find text on a page case-insensitively.
|
||||
|<<search.incremental,search.incremental>>|Find text on a page incrementally, renewing the search for each typed character.
|
||||
|<<search.wrap,search.wrap>>|Wrap around at the top and bottom of the page when advancing through text matches using `:search-next` and `:search-prev`.
|
||||
|<<session.default_name,session.default_name>>|Name of the session to save by default.
|
||||
|<<session.lazy_restore,session.lazy_restore>>|Load a restored tab as soon as it takes focus.
|
||||
|<<spellcheck.languages,spellcheck.languages>>|Languages to use for spell checking.
|
||||
|<<statusbar.hide,statusbar.hide>>|Hide the statusbar unless a message is shown.
|
||||
|<<statusbar.padding,statusbar.padding>>|Padding (in pixels) for the statusbar.
|
||||
|<<statusbar.position,statusbar.position>>|Position of the status bar.
|
||||
|<<statusbar.show,statusbar.show>>|When to show the statusbar.
|
||||
|<<statusbar.widgets,statusbar.widgets>>|List of widgets displayed in the statusbar.
|
||||
|<<tabs.background,tabs.background>>|Open new tabs (middleclick/ctrl+click) in the background.
|
||||
|<<tabs.close_mouse_button,tabs.close_mouse_button>>|Mouse button with which to close tabs.
|
||||
|
|
@ -441,6 +461,7 @@ Default:
|
|||
* +pass:[J]+: +pass:[scroll down]+
|
||||
* +pass:[K]+: +pass:[scroll up]+
|
||||
* +pass:[L]+: +pass:[scroll right]+
|
||||
* +pass:[V]+: +pass:[toggle-selection --line]+
|
||||
* +pass:[Y]+: +pass:[yank selection -s]+
|
||||
* +pass:[[]+: +pass:[move-to-start-of-prev-block]+
|
||||
* +pass:[]]+: +pass:[move-to-start-of-next-block]+
|
||||
|
|
@ -585,6 +606,7 @@ Default:
|
|||
* +pass:[Sq]+: +pass:[open qute://bookmarks]+
|
||||
* +pass:[Ss]+: +pass:[open qute://settings]+
|
||||
* +pass:[T]+: +pass:[tab-focus]+
|
||||
* +pass:[V]+: +pass:[enter-mode caret ;; toggle-selection --line]+
|
||||
* +pass:[ZQ]+: +pass:[quit]+
|
||||
* +pass:[ZZ]+: +pass:[quit --save]+
|
||||
* +pass:[[[]+: +pass:[navigate prev]+
|
||||
|
|
@ -632,6 +654,9 @@ Default:
|
|||
* +pass:[sk]+: +pass:[set-cmd-text -s :bind]+
|
||||
* +pass:[sl]+: +pass:[set-cmd-text -s :set -t]+
|
||||
* +pass:[ss]+: +pass:[set-cmd-text -s :set]+
|
||||
* +pass:[tCH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
|
||||
* +pass:[tCh]+: +pass:[config-cycle -p -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
|
||||
* +pass:[tCu]+: +pass:[config-cycle -p -u {url} content.cookies.accept all no-3rdparty never ;; reload]+
|
||||
* +pass:[tIH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.images ;; reload]+
|
||||
* +pass:[tIh]+: +pass:[config-cycle -p -u *://{url:host}/* content.images ;; reload]+
|
||||
* +pass:[tIu]+: +pass:[config-cycle -p -u {url} content.images ;; reload]+
|
||||
|
|
@ -641,6 +666,9 @@ Default:
|
|||
* +pass:[tSH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.javascript.enabled ;; reload]+
|
||||
* +pass:[tSh]+: +pass:[config-cycle -p -u *://{url:host}/* content.javascript.enabled ;; reload]+
|
||||
* +pass:[tSu]+: +pass:[config-cycle -p -u {url} content.javascript.enabled ;; reload]+
|
||||
* +pass:[tcH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
|
||||
* +pass:[tch]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
|
||||
* +pass:[tcu]+: +pass:[config-cycle -p -t -u {url} content.cookies.accept all no-3rdparty never ;; reload]+
|
||||
* +pass:[th]+: +pass:[back -t]+
|
||||
* +pass:[tiH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.images ;; reload]+
|
||||
* +pass:[tih]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.images ;; reload]+
|
||||
|
|
@ -734,6 +762,7 @@ Default:
|
|||
|
||||
- +pass:[<Ctrl-6>]+: +pass:[<Ctrl-^>]+
|
||||
- +pass:[<Ctrl-Enter>]+: +pass:[<Ctrl-Return>]+
|
||||
- +pass:[<Ctrl-I>]+: +pass:[<Tab>]+
|
||||
- +pass:[<Ctrl-J>]+: +pass:[<Return>]+
|
||||
- +pass:[<Ctrl-M>]+: +pass:[<Return>]+
|
||||
- +pass:[<Ctrl-[>]+: +pass:[<Escape>]+
|
||||
|
|
@ -866,6 +895,24 @@ Type: <<types,QssColor>>
|
|||
|
||||
Default: +pass:[white]+
|
||||
|
||||
[[colors.contextmenu.disabled.bg]]
|
||||
=== colors.contextmenu.disabled.bg
|
||||
Background color of disabled items in the context menu.
|
||||
If set to null, the Qt default is used.
|
||||
|
||||
Type: <<types,QssColor>>
|
||||
|
||||
Default: empty
|
||||
|
||||
[[colors.contextmenu.disabled.fg]]
|
||||
=== colors.contextmenu.disabled.fg
|
||||
Foreground color of disabled items in the context menu.
|
||||
If set to null, the Qt default is used.
|
||||
|
||||
Type: <<types,QssColor>>
|
||||
|
||||
Default: empty
|
||||
|
||||
[[colors.contextmenu.menu.bg]]
|
||||
=== colors.contextmenu.menu.bg
|
||||
Background color of the context menu.
|
||||
|
|
@ -1508,6 +1555,161 @@ Type: <<types,QtColor>>
|
|||
|
||||
Default: +pass:[white]+
|
||||
|
||||
[[colors.webpage.darkmode.algorithm]]
|
||||
=== colors.webpage.darkmode.algorithm
|
||||
Which algorithm to use for modifying how colors are rendered with darkmode.
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +lightness-cielab+: Modify colors by converting them to CIELAB color space and inverting the L value.
|
||||
* +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 QtWebKit, this setting is unavailable.
|
||||
|
||||
[[colors.webpage.darkmode.contrast]]
|
||||
=== colors.webpage.darkmode.contrast
|
||||
Contrast for dark mode.
|
||||
This only has an effect when `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or `brightness-rgb`.
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Float>>
|
||||
|
||||
Default: +pass:[0.0]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[colors.webpage.darkmode.enabled]]
|
||||
=== colors.webpage.darkmode.enabled
|
||||
Render all web contents using a dark theme.
|
||||
Example configurations from Chromium's `chrome://flags`:
|
||||
|
||||
- "With simple HSL/CIELAB/RGB-based inversion": Set
|
||||
`colors.webpage.darkmode.algorithm` accordingly.
|
||||
|
||||
- "With selective image inversion": Set
|
||||
`colors.webpage.darkmode.policy.images` to `smart`.
|
||||
|
||||
- "With selective inversion of non-image elements": Set
|
||||
`colors.webpage.darkmode.threshold.text` to 150 and
|
||||
`colors.webpage.darkmode.threshold.background` to 205.
|
||||
|
||||
- "With selective inversion of everything": Combines the two variants
|
||||
above.
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[colors.webpage.darkmode.grayscale.all]]
|
||||
=== colors.webpage.darkmode.grayscale.all
|
||||
Render all colors as grayscale.
|
||||
This only has an effect when `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or `brightness-rgb`.
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[colors.webpage.darkmode.grayscale.images]]
|
||||
=== colors.webpage.darkmode.grayscale.images
|
||||
Desaturation factor for images in dark mode.
|
||||
If set to 0, images are left as-is. If set to 1, images are completely grayscale. Values between 0 and 1 desaturate the colors accordingly.
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Float>>
|
||||
|
||||
Default: +pass:[0.0]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
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].
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,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.
|
||||
|
||||
Default: +pass:[never]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[colors.webpage.darkmode.policy.page]]
|
||||
=== colors.webpage.darkmode.policy.page
|
||||
Which pages to apply dark mode to.
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +always+: Apply dark mode filter to all frames, regardless of content.
|
||||
* +smart+: Apply dark mode filter to frames based on background color.
|
||||
|
||||
Default: +pass:[smart]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[colors.webpage.darkmode.threshold.background]]
|
||||
=== colors.webpage.darkmode.threshold.background
|
||||
Threshold for inverting background elements with dark mode.
|
||||
Background elements with brightness above this threshold will be inverted, and below it will be left as in the original, non-dark-mode page. Set to 256 to never invert the color or to 0 to always invert it.
|
||||
Note: This behavior is the opposite of `colors.webpage.darkmode.threshold.text`!
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[0]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[colors.webpage.darkmode.threshold.text]]
|
||||
=== colors.webpage.darkmode.threshold.text
|
||||
Threshold for inverting text with dark mode.
|
||||
Text colors with brightness below this threshold will be inverted, and above it will be left as in the original, non-dark-mode page. Set to 256 to always invert text color or to 0 to never invert text color.
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[256]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[colors.webpage.prefers_color_scheme_dark]]
|
||||
=== colors.webpage.prefers_color_scheme_dark
|
||||
Force `prefers-color-scheme: dark` colors for websites.
|
||||
|
|
@ -1800,6 +2002,10 @@ This setting is only available with the QtWebEngine backend.
|
|||
[[content.cookies.accept]]
|
||||
=== content.cookies.accept
|
||||
Which cookies to accept.
|
||||
With QtWebEngine, this setting also controls other features with tracking capabilities similar to those of cookies; including IndexedDB, DOM storage, filesystem API, service workers, and AppCache.
|
||||
Note that with QtWebKit, only `all` and `never` are supported as per-domain values. Setting `no-3rdparty` or `no-unknown-3rdparty` per-domain on QtWebKit will have the same effect as `all`.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
|
|
@ -1857,7 +2063,7 @@ This setting supports URL patterns.
|
|||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
Default: +pass:[true]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.12 or newer.
|
||||
|
||||
|
|
@ -1874,6 +2080,23 @@ Default: +pass:[false]+
|
|||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.fullscreen.overlay_timeout]]
|
||||
=== content.fullscreen.overlay_timeout
|
||||
Set fullscreen notification overlay timeout in milliseconds.
|
||||
If set to 0, no overlay will be displayed.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[3000]+
|
||||
|
||||
[[content.fullscreen.window]]
|
||||
=== content.fullscreen.window
|
||||
Limit fullscreen to the browser window (does not expand to fill the screen).
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[content.geolocation]]
|
||||
=== content.geolocation
|
||||
Allow websites to request geolocations.
|
||||
|
|
@ -1957,7 +2180,9 @@ The following placeholders are defined:
|
|||
The default value is equal to the unchanged user agent of
|
||||
QtWebKit/QtWebEngine.
|
||||
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
Note that the value read from JavaScript is always the global value. With
|
||||
QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed
|
||||
to JavaScript requires a restart.
|
||||
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
|
@ -2305,6 +2530,26 @@ Valid values:
|
|||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
[[content.unknown_url_scheme_policy]]
|
||||
=== content.unknown_url_scheme_policy
|
||||
How navigation requests to URLs with unknown schemes are handled.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +disallow+: Disallows all navigation requests to URLs with unknown schemes.
|
||||
* +allow-from-user-interaction+: Allows navigation requests to URLs with unknown schemes that are issued from user-interaction (like a mouse-click), whereas other navigation requests (for example from JavaScript) are suppressed.
|
||||
* +allow-all+: Allows all navigation requests to URLs with unknown schemes.
|
||||
|
||||
Default: +pass:[allow-from-user-interaction]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.11 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[content.user_stylesheets]]
|
||||
=== content.user_stylesheets
|
||||
List of user stylesheet filenames to use.
|
||||
|
|
@ -2344,14 +2589,6 @@ On QtWebEngine, this setting requires Qt 5.9.2 or newer.
|
|||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[content.windowed_fullscreen]]
|
||||
=== content.windowed_fullscreen
|
||||
Limit fullscreen to the browser window (does not expand to fill the screen).
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[content.xss_auditing]]
|
||||
=== content.xss_auditing
|
||||
Monitor load requests for cross-site scripting attempts.
|
||||
|
|
@ -2495,7 +2732,7 @@ Default: empty
|
|||
=== fonts.debug_console
|
||||
Font used for the debugging console.
|
||||
|
||||
Type: <<types,QtFont>>
|
||||
Type: <<types,Font>>
|
||||
|
||||
Default: +pass:[default_size default_family]+
|
||||
|
||||
|
|
@ -2583,11 +2820,19 @@ Type: <<types,Font>>
|
|||
|
||||
Default: +pass:[default_size default_family]+
|
||||
|
||||
[[fonts.tabs]]
|
||||
=== fonts.tabs
|
||||
Font used in the tab bar.
|
||||
[[fonts.tabs.selected]]
|
||||
=== fonts.tabs.selected
|
||||
Font used for selected tabs.
|
||||
|
||||
Type: <<types,QtFont>>
|
||||
Type: <<types,Font>>
|
||||
|
||||
Default: +pass:[default_size default_family]+
|
||||
|
||||
[[fonts.tabs.unselected]]
|
||||
=== fonts.tabs.unselected
|
||||
Font used for unselected tabs.
|
||||
|
||||
Type: <<types,Font>>
|
||||
|
||||
Default: +pass:[default_size default_family]+
|
||||
|
||||
|
|
@ -2786,6 +3031,19 @@ Default:
|
|||
- +pass:[\b(>>|»)\b]+
|
||||
- +pass:[\bcontinue\b]+
|
||||
|
||||
[[hints.padding]]
|
||||
=== hints.padding
|
||||
Padding (in pixels) for hints.
|
||||
|
||||
Type: <<types,Padding>>
|
||||
|
||||
Default:
|
||||
|
||||
- +pass:[bottom]+: +pass:[0]+
|
||||
- +pass:[left]+: +pass:[3]+
|
||||
- +pass:[right]+: +pass:[3]+
|
||||
- +pass:[top]+: +pass:[0]+
|
||||
|
||||
[[hints.prev_regexes]]
|
||||
=== hints.prev_regexes
|
||||
Comma-separated list of regular expressions to use for 'prev' links.
|
||||
|
|
@ -2800,6 +3058,14 @@ Default:
|
|||
- +pass:[\b[<←≪]\b]+
|
||||
- +pass:[\b(<<|«)\b]+
|
||||
|
||||
[[hints.radius]]
|
||||
=== hints.radius
|
||||
Rounding radius (in pixels) for the edges of hints.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[3]+
|
||||
|
||||
[[hints.scatter]]
|
||||
=== hints.scatter
|
||||
Scatter hint key chains (like Vimium) or not (like dwb).
|
||||
|
|
@ -2971,6 +3237,23 @@ Type: <<types,Bool>>
|
|||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[input.mouse.back_forward_buttons]]
|
||||
=== input.mouse.back_forward_buttons
|
||||
Enable back and forward buttons on the mouse.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[input.mouse.rocker_gestures]]
|
||||
=== input.mouse.rocker_gestures
|
||||
Enable Opera-like mouse rocker gestures.
|
||||
This disables the context menu.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[input.partial_timeout]]
|
||||
=== input.partial_timeout
|
||||
Timeout (in milliseconds) for partially typed key bindings.
|
||||
|
|
@ -2980,15 +3263,6 @@ Type: <<types,Int>>
|
|||
|
||||
Default: +pass:[5000]+
|
||||
|
||||
[[input.rocker_gestures]]
|
||||
=== input.rocker_gestures
|
||||
Enable Opera-like mouse rocker gestures.
|
||||
This disables the context menu.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[input.spatial_navigation]]
|
||||
=== input.spatial_navigation
|
||||
Enable spatial navigation.
|
||||
|
|
@ -3025,6 +3299,40 @@ Type: <<types,Int>>
|
|||
|
||||
Default: +pass:[6]+
|
||||
|
||||
[[logging.level.console]]
|
||||
=== logging.level.console
|
||||
Level for console (stdout/stderr) logs. Ignored if the `--loglevel` or `--debug` CLI flags are used.
|
||||
|
||||
Type: <<types,LogLevel>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +vdebug+
|
||||
* +debug+
|
||||
* +info+
|
||||
* +warning+
|
||||
* +error+
|
||||
* +critical+
|
||||
|
||||
Default: +pass:[info]+
|
||||
|
||||
[[logging.level.ram]]
|
||||
=== logging.level.ram
|
||||
Level for in-memory logs.
|
||||
|
||||
Type: <<types,LogLevel>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +vdebug+
|
||||
* +debug+
|
||||
* +info+
|
||||
* +warning+
|
||||
* +error+
|
||||
* +critical+
|
||||
|
||||
Default: +pass:[debug]+
|
||||
|
||||
[[messages.timeout]]
|
||||
=== messages.timeout
|
||||
Duration (in milliseconds) to show messages in the statusbar for.
|
||||
|
|
@ -3070,7 +3378,7 @@ Default: +pass:[last-focused]+
|
|||
|
||||
[[prompt.filebrowser]]
|
||||
=== prompt.filebrowser
|
||||
Show a filebrowser in upload/download prompts.
|
||||
Show a filebrowser in download prompts.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
|
|
@ -3186,7 +3494,7 @@ This setting is only available with the QtWebEngine backend.
|
|||
|
||||
[[scrolling.bar]]
|
||||
=== scrolling.bar
|
||||
When to show the scrollbar.
|
||||
When/how to show the scrollbar.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
|
|
@ -3195,8 +3503,9 @@ Valid values:
|
|||
* +always+: Always show the scrollbar.
|
||||
* +never+: Never show the scrollbar.
|
||||
* +when-searching+: Show the scrollbar when searching for text in the webpage. With the QtWebKit backend, this is equal to `never`.
|
||||
* +overlay+: Show an overlay scrollbar. With Qt < 5.11, this is equal to `when-searching`; with the QtWebKit backend, this is equal to `never`. Enabling/disabling overlay scrollbars requires a restart.
|
||||
|
||||
Default: +pass:[when-searching]+
|
||||
Default: +pass:[overlay]+
|
||||
|
||||
[[scrolling.smooth]]
|
||||
=== scrolling.smooth
|
||||
|
|
@ -3231,6 +3540,16 @@ Type: <<types,Bool>>
|
|||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[search.wrap]]
|
||||
=== search.wrap
|
||||
Wrap around at the top and bottom of the page when advancing through text matches using `:search-next` and `:search-prev`.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
[[session.default_name]]
|
||||
=== session.default_name
|
||||
Name of the session to save by default.
|
||||
|
|
@ -3307,14 +3626,6 @@ On QtWebEngine, this setting requires Qt 5.8 or newer.
|
|||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[statusbar.hide]]
|
||||
=== statusbar.hide
|
||||
Hide the statusbar unless a message is shown.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[statusbar.padding]]
|
||||
=== statusbar.padding
|
||||
Padding (in pixels) for the statusbar.
|
||||
|
|
@ -3341,6 +3652,20 @@ Valid values:
|
|||
|
||||
Default: +pass:[bottom]+
|
||||
|
||||
[[statusbar.show]]
|
||||
=== statusbar.show
|
||||
When to show the statusbar.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +always+: Always show the statusbar.
|
||||
* +never+: Always hide the statusbar.
|
||||
* +in-mode+: Show the statusbar when in modes other than normal mode.
|
||||
|
||||
Default: +pass:[always]+
|
||||
|
||||
[[statusbar.widgets]]
|
||||
=== statusbar.widgets
|
||||
List of widgets displayed in the statusbar.
|
||||
|
|
@ -3776,8 +4101,28 @@ Default: +pass:[false]+
|
|||
[[url.searchengines]]
|
||||
=== url.searchengines
|
||||
Search engines which can be used via the address bar.
|
||||
Maps a search engine name (such as `DEFAULT`, or `ddg`) to a URL with a `{}` placeholder. The placeholder will be replaced by the search term, use `{{` and `}}` for literal `{`/`}` signs.
|
||||
The search engine named `DEFAULT` is used when `url.auto_search` is turned on and something else than a URL was entered to be opened. Other search engines can be used by prepending the search engine name to the search term, e.g. `:open google qutebrowser`.
|
||||
|
||||
Maps a search engine name (such as `DEFAULT`, or `ddg`) to a URL with a
|
||||
`{}` placeholder. The placeholder will be replaced by the search term, use
|
||||
`{{` and `}}` for literal `{`/`}` braces.
|
||||
|
||||
The following further placeholds are defined to configure how special
|
||||
characters in the search terms are replaced by safe characters (called
|
||||
'quoting'):
|
||||
|
||||
* `{}` and `{semiquoted}` quote everything except slashes; this is the most
|
||||
sensible choice for almost all search engines (for the search term
|
||||
`slash/and&` this placeholder expands to `slash/and%26amp`).
|
||||
* `{quoted}` quotes all characters (for `slash/and&` this placeholder
|
||||
expands to `slash%2Fand%26amp`).
|
||||
* `{unquoted}` quotes nothing (for `slash/and&` this placeholder
|
||||
expands to `slash/and&`).
|
||||
|
||||
The search engine named `DEFAULT` is used when `url.auto_search` is turned
|
||||
on and something else than a URL was entered to be opened. Other search
|
||||
engines can be used by prepending the search engine name to the search
|
||||
term, e.g. `:open google qutebrowser`.
|
||||
|
||||
|
||||
Type: <<types,Dict>>
|
||||
|
||||
|
|
@ -3917,6 +4262,7 @@ Lists with duplicate flags are invalid. Each item is checked against the valid v
|
|||
|
||||
When setting from a string, pass a json-like list, e.g. `["one", "two"]`.
|
||||
|ListOrValue|A list of values, or a single value.
|
||||
|LogLevel|A logging level.
|
||||
|NewTabPosition|How new tabs are positioned.
|
||||
|Padding|Setting for paddings around elements.
|
||||
|Perc|A percentage.
|
||||
|
|
@ -3929,9 +4275,6 @@ A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/
|
|||
|QtColor|A color value.
|
||||
|
||||
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in http://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color) * `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages) * `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359)
|
||||
|QtFont|A font family, with optional style/weight/size.
|
||||
|
||||
* Style: `normal`/`italic`/`oblique` * Weight: `normal`, `bold`, `100`..`900` * Size: _number_ `px`/`pt`
|
||||
|Regex|A regular expression.
|
||||
|
||||
When setting from `config.py`, both a string or a `re.compile(...)` object are valid.
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ mailinglist] to get notified on new releases). You can install a newer version
|
|||
without uninstalling the older one.
|
||||
|
||||
The binary release ships with a QtWebEngine built without proprietary codec
|
||||
support. To get support for e.g. h264/h265 videos, you'll need to build
|
||||
support. To get support for e.g. h264/mp4 videos, you'll need to build
|
||||
QtWebEngine from source yourself with support for that enabled.
|
||||
|
||||
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
|
||||
|
|
@ -299,7 +299,7 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrows
|
|||
mailinglist] to get notified on new releases).
|
||||
|
||||
The binary release ships with a QtWebEngine built without proprietary codec
|
||||
support. To get support for e.g. h264/h265 videos, you'll need to build
|
||||
support. To get support for e.g. h264/mp4 videos, you'll need to build
|
||||
QtWebEngine from source yourself with support for that enabled.
|
||||
|
||||
This binary is also available through the
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ show it.
|
|||
|
||||
=== debug arguments
|
||||
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
|
||||
Set loglevel
|
||||
Override the configured console loglevel
|
||||
|
||||
*--logfilter* 'LOGFILTER'::
|
||||
Comma-separated list of things to be logged to the debug log on stdout.
|
||||
|
|
|
|||
|
|
@ -34,6 +34,27 @@ is available in the repositories:
|
|||
# apt-get install python3-pyqt5-dbg python3-pyqt5.qtwebkit-dbg python3-dbg libqt5webkit5-dbg
|
||||
----
|
||||
|
||||
Fedora
|
||||
^^^^^^
|
||||
|
||||
For Fedora you first need to install the dnf/yum-utils:
|
||||
|
||||
----
|
||||
# dnf install dnf-utils
|
||||
----
|
||||
|
||||
Or:
|
||||
|
||||
----
|
||||
# yum install yum-utils
|
||||
----
|
||||
|
||||
Then install the needed debuginfo packages:
|
||||
|
||||
----
|
||||
# debuginfo-install python3 qt5-qtwebengine python3-qt5-webengine python3-qt5-base python-qt5 python3-qt5 python3-qt5-webkit
|
||||
----
|
||||
|
||||
Archlinux
|
||||
^^^^^^^^^
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@
|
|||
</content_rating>
|
||||
<releases>
|
||||
<!-- Add new releases here -->
|
||||
<release version="1.12.0" date="2020-06-01"/>
|
||||
<release version="1.11.1" date="2020-05-07"/>
|
||||
<release version="1.11.0" date="2020-04-27"/>
|
||||
<release version="1.10.2" date="2020-04-17"/>
|
||||
<release version="1.10.1" date="2020-02-15"/>
|
||||
<release version="1.10.0" date="2020-02-02"/>
|
||||
<release version="1.9.0" date="2020-01-08"/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
check-manifest==0.41
|
||||
pep517==0.8.1
|
||||
toml==0.10.0
|
||||
check-manifest==0.42
|
||||
pep517==0.8.2
|
||||
toml==0.10.1
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
certifi==2019.11.28
|
||||
certifi==2020.4.5.2
|
||||
chardet==3.0.4
|
||||
codecov==2.0.16
|
||||
coverage==5.0.3
|
||||
codecov==2.1.4
|
||||
coverage==5.1
|
||||
idna==2.9
|
||||
requests==2.23.0
|
||||
urllib3==1.25.8
|
||||
urllib3==1.25.9
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
bump2version==1.0.0
|
||||
certifi==2019.11.28
|
||||
certifi==2020.4.5.2
|
||||
cffi==1.14.0
|
||||
chardet==3.0.4
|
||||
colorama==0.4.3
|
||||
cryptography==2.8
|
||||
cryptography==2.9.2
|
||||
cssutils==1.0.2
|
||||
github3.py==1.3.0
|
||||
hunter==3.1.3
|
||||
idna==2.9
|
||||
jwcrypto==0.7
|
||||
lxml==4.5.0
|
||||
manhole==1.6.0
|
||||
packaging==20.3
|
||||
packaging==20.4
|
||||
pycparser==2.20
|
||||
Pympler==0.8
|
||||
pyparsing==2.4.6
|
||||
PyQt-builder==1.2.0
|
||||
pyparsing==2.4.7
|
||||
PyQt-builder==1.4.0
|
||||
python-dateutil==2.8.1
|
||||
requests==2.23.0
|
||||
sip==5.1.1
|
||||
six==1.14.0
|
||||
toml==0.10.0
|
||||
sip==5.3.0
|
||||
six==1.15.0
|
||||
toml==0.10.1
|
||||
uritemplate==3.0.1
|
||||
urllib3==1.25.8
|
||||
urllib3==1.25.9
|
||||
|
|
|
|||
|
|
@ -4,5 +4,4 @@ pympler
|
|||
github3.py
|
||||
bump2version
|
||||
requests
|
||||
lxml
|
||||
pyqt-builder
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==19.3.0
|
||||
entrypoints==0.3
|
||||
flake8==3.7.9
|
||||
flake8==3.8.2
|
||||
flake8-bugbear==20.1.4
|
||||
flake8-builtins==1.4.2
|
||||
flake8-comprehensions==3.2.2
|
||||
flake8-builtins==1.5.3
|
||||
flake8-comprehensions==3.2.3
|
||||
flake8-copyright==0.2.2
|
||||
flake8-debugger==3.2.1
|
||||
flake8-deprecated==1.3
|
||||
|
|
@ -14,12 +13,12 @@ flake8-future-import==0.4.6
|
|||
flake8-mock==0.3
|
||||
flake8-polyfill==1.0.2
|
||||
flake8-string-format==0.3.0
|
||||
flake8-tidy-imports==4.0.0
|
||||
flake8-tidy-imports==4.1.0
|
||||
flake8-tuple==0.4.1
|
||||
mccabe==0.6.1
|
||||
pep8-naming==0.9.1
|
||||
pycodestyle==2.5.0
|
||||
pep8-naming==0.10.0
|
||||
pycodestyle==2.6.0
|
||||
pydocstyle==5.0.2
|
||||
pyflakes==2.1.1
|
||||
six==1.14.0
|
||||
pyflakes==2.2.0
|
||||
six==1.15.0
|
||||
snowballstemmer==2.0.0
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
mypy==0.770
|
||||
mypy==0.780
|
||||
mypy-extensions==0.4.3
|
||||
# PyQt5==5.11.3
|
||||
# PyQt5-sip==4.19.19
|
||||
-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5_stubs
|
||||
-e git+https://github.com/stlehmann/PyQt5-stubs.git@master#egg=PyQt5_stubs
|
||||
typed-ast==1.4.1
|
||||
typing-extensions==3.7.4.1
|
||||
typing-extensions==3.7.4.2
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
mypy
|
||||
-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5-stubs
|
||||
-e git+https://github.com/stlehmann/PyQt5-stubs.git@master#egg=PyQt5-stubs
|
||||
|
||||
# remove @commit-id for scm installs
|
||||
#@ replace: @.*# @wip#
|
||||
#@ ignore: PyQt5, PyQt5-sip
|
||||
#@ replace: @.*# @master#
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
appdirs==1.4.3
|
||||
packaging==20.3
|
||||
pyparsing==2.4.6
|
||||
setuptools==46.0.0
|
||||
six==1.14.0
|
||||
appdirs==1.4.4
|
||||
packaging==20.4
|
||||
pyparsing==2.4.7
|
||||
setuptools==47.1.1
|
||||
six==1.15.0
|
||||
wheel==0.34.2
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
altgraph==0.17
|
||||
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=pyinstaller
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==2.3.3
|
||||
certifi==2019.11.28
|
||||
astroid==2.3.3 # rq.filter: < 2.4
|
||||
certifi==2020.4.5.2
|
||||
cffi==1.14.0
|
||||
chardet==3.0.4
|
||||
cryptography==2.8
|
||||
cryptography==2.9.2
|
||||
github3.py==1.3.0
|
||||
idna==2.9
|
||||
isort==4.3.21
|
||||
jwcrypto==0.7
|
||||
lazy-object-proxy==1.4.3
|
||||
lazy-object-proxy==1.5.0
|
||||
mccabe==0.6.1
|
||||
pycparser==2.20
|
||||
pylint==2.4.4
|
||||
pylint==2.4.4 # rq.filter: < 2.5
|
||||
python-dateutil==2.8.1
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.23.0
|
||||
six==1.14.0
|
||||
six==1.15.0
|
||||
typed-ast==1.4.1 ; python_version<"3.8"
|
||||
uritemplate==3.0.1
|
||||
urllib3==1.25.8
|
||||
urllib3==1.25.9
|
||||
wrapt==1.12.1
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
pylint
|
||||
pylint<2.5
|
||||
./scripts/dev/pylint_checkers
|
||||
requests
|
||||
github3.py
|
||||
|
|
@ -6,3 +6,5 @@ github3.py
|
|||
# fix qute-pylint location
|
||||
#@ replace: qute-pylint==.* ./scripts/dev/pylint_checkers
|
||||
#@ markers: typed-ast python_version<"3.8"
|
||||
#@ filter: pylint < 2.5
|
||||
#@ filter: astroid < 2.4
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.12.3 # rq.filter: < 5.13
|
||||
PyQt5-sip==12.7.1
|
||||
PyQt5-sip==12.8.0
|
||||
PyQtWebEngine==5.12.1 # rq.filter: < 5.13
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.13.2 # rq.filter: < 5.14
|
||||
PyQt5-sip==12.7.1
|
||||
PyQt5-sip==12.8.0
|
||||
PyQtWebEngine==5.13.2 # rq.filter: < 5.14
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.14.1 # rq.filter: < 5.15
|
||||
PyQt5-sip==12.7.1
|
||||
PyQt5==5.14.2 # rq.filter: < 5.15
|
||||
PyQt5-sip==12.8.0
|
||||
PyQtWebEngine==5.14.0 # rq.filter: < 5.15
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.0 # rq.filter: < 6
|
||||
PyQt5-sip==12.8.0
|
||||
PyQtWebEngine==5.15.0 # rq.filter: < 6
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#@ filter: PyQt5 < 6
|
||||
#@ filter: PyQtWebEngine < 6
|
||||
PyQt5 >= 5.15, < 6
|
||||
PyQtWebEngine >= 5.15, < 6
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.14.1
|
||||
PyQt5-sip==12.7.1
|
||||
PyQtWebEngine==5.14.0
|
||||
PyQt5==5.15.0
|
||||
PyQt5-sip==12.8.0
|
||||
PyQtWebEngine==5.15.0
|
||||
|
|
|
|||
|
|
@ -2,25 +2,25 @@
|
|||
|
||||
alabaster==0.7.12
|
||||
Babel==2.8.0
|
||||
certifi==2019.11.28
|
||||
certifi==2020.4.5.2
|
||||
chardet==3.0.4
|
||||
docutils==0.16
|
||||
idna==2.9
|
||||
imagesize==1.2.0
|
||||
Jinja2==2.11.1
|
||||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
packaging==20.3
|
||||
packaging==20.4
|
||||
Pygments==2.6.1
|
||||
pyparsing==2.4.6
|
||||
pytz==2019.3
|
||||
pyparsing==2.4.7
|
||||
pytz==2020.1
|
||||
requests==2.23.0
|
||||
six==1.14.0
|
||||
six==1.15.0
|
||||
snowballstemmer==2.0.0
|
||||
Sphinx==2.4.4
|
||||
Sphinx==3.1.0
|
||||
sphinxcontrib-applehelp==1.0.2
|
||||
sphinxcontrib-devhelp==1.0.2
|
||||
sphinxcontrib-htmlhelp==1.0.3
|
||||
sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==1.0.3
|
||||
sphinxcontrib-serializinghtml==1.1.4
|
||||
urllib3==1.25.8
|
||||
urllib3==1.25.9
|
||||
|
|
|
|||
|
|
@ -1,47 +1,47 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==19.3.0
|
||||
beautifulsoup4==4.8.2
|
||||
beautifulsoup4==4.9.1
|
||||
cheroot==8.3.0
|
||||
click==7.1.1
|
||||
click==7.1.2
|
||||
# colorama==0.4.3
|
||||
coverage==5.0.3
|
||||
EasyProcess==0.2.10
|
||||
Flask==1.1.1
|
||||
coverage==5.1
|
||||
EasyProcess==0.3
|
||||
Flask==1.1.2
|
||||
glob2==0.7
|
||||
hunter==3.1.3
|
||||
hypothesis==5.6.0
|
||||
hypothesis==5.16.0
|
||||
itsdangerous==1.1.0
|
||||
jaraco.functools==3.0.0 ; python_version>="3.6"
|
||||
# Jinja2==2.11.1
|
||||
Mako==1.1.2
|
||||
jaraco.functools==3.0.1 ; python_version>="3.6"
|
||||
# Jinja2==2.11.2
|
||||
Mako==1.1.3
|
||||
manhole==1.6.0
|
||||
# MarkupSafe==1.1.1
|
||||
more-itertools==8.2.0
|
||||
packaging==20.3
|
||||
more-itertools==8.3.0
|
||||
packaging==20.4
|
||||
parse==1.15.0
|
||||
parse-type==0.5.2
|
||||
pluggy==0.13.1
|
||||
py==1.8.1
|
||||
py-cpuinfo==5.0.0
|
||||
Pygments==2.6.1
|
||||
pyparsing==2.4.6
|
||||
pytest==5.4.1
|
||||
pytest-bdd==3.2.1
|
||||
pyparsing==2.4.7
|
||||
pytest==5.4.3
|
||||
pytest-bdd==3.4.0
|
||||
pytest-benchmark==3.2.3
|
||||
pytest-cov==2.8.1
|
||||
pytest-cov==2.9.0
|
||||
pytest-instafail==0.4.1.post0
|
||||
pytest-mock==2.0.0
|
||||
pytest-mock==3.1.1
|
||||
pytest-qt==3.3.0
|
||||
pytest-repeat==0.8.0
|
||||
pytest-rerunfailures==9.0
|
||||
pytest-travis-fold==1.3.0
|
||||
pytest-xvfb==1.2.0
|
||||
PyVirtualDisplay==0.2.5
|
||||
six==1.14.0
|
||||
sortedcontainers==2.1.0
|
||||
soupsieve==2.0
|
||||
vulture==1.3
|
||||
wcwidth==0.1.8
|
||||
Werkzeug==1.0.0
|
||||
PyVirtualDisplay==0.2.5 # rq.filter: < 1.0
|
||||
six==1.15.0
|
||||
sortedcontainers==2.2.2
|
||||
soupsieve==2.0.1
|
||||
vulture==1.5
|
||||
wcwidth==0.2.4
|
||||
Werkzeug==1.0.1
|
||||
jaraco.functools==2.0; python_version<"3.6" # rq.filter: <= 2.0
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ beautifulsoup4
|
|||
cheroot
|
||||
coverage
|
||||
Flask
|
||||
hunter
|
||||
hypothesis
|
||||
pytest
|
||||
pytest-bdd
|
||||
|
|
@ -11,13 +10,24 @@ pytest-cov
|
|||
pytest-instafail
|
||||
pytest-mock
|
||||
pytest-qt
|
||||
pytest-repeat
|
||||
pytest-rerunfailures
|
||||
pytest-travis-fold
|
||||
pytest-xvfb
|
||||
# https://github.com/The-Compiler/pytest-xvfb/issues/22
|
||||
PyVirtualDisplay < 1.0
|
||||
|
||||
## optional:
|
||||
# To test :debug-trace, gets skipped if hunter is not installed
|
||||
hunter
|
||||
# To test scripts/dev/run_vulture.py which is not part of the release tarball
|
||||
vulture
|
||||
# For colored pytest output (though also a direct qutebrowser dependency))
|
||||
pygments
|
||||
# Output folding on Travis
|
||||
pytest-travis-fold
|
||||
# --repeat switch (used to manually repeat tests)
|
||||
pytest-repeat
|
||||
|
||||
#@ markers: jaraco.functools python_version>="3.6"
|
||||
#@ add: jaraco.functools==2.0; python_version<"3.6" # rq.filter: <= 2.0
|
||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||
#@ filter: PyVirtualDisplay < 1.0
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
appdirs==1.4.3
|
||||
appdirs==1.4.4
|
||||
distlib==0.3.0
|
||||
filelock==3.0.12
|
||||
packaging==20.3
|
||||
packaging==20.4
|
||||
pluggy==0.13.1
|
||||
py==1.8.1
|
||||
pyparsing==2.4.6
|
||||
six==1.14.0
|
||||
toml==0.10.0
|
||||
tox==3.14.5
|
||||
pyparsing==2.4.7
|
||||
six==1.15.0
|
||||
toml==0.10.1
|
||||
tox==3.15.2
|
||||
tox-pip-version==0.0.7
|
||||
tox-venv==0.4.0
|
||||
virtualenv==20.0.10
|
||||
virtualenv==20.0.21
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
vulture==1.3
|
||||
vulture==1.5
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ https://ocefpaf.github.io/python4oceanographers/blog/2014/05/19/doi2bibtex/
|
|||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import re
|
||||
from collections import Counter
|
||||
from urllib import parse as url_parse
|
||||
|
|
|
|||
|
|
@ -358,11 +358,13 @@ cat <<EOF
|
|||
if (isVisible(input) && (input.type == "text" || input.type == "email")) {
|
||||
input.focus();
|
||||
input.value = "$(javascript_escape "${username}")";
|
||||
input.dispatchEvent(new Event('change'));
|
||||
input.blur();
|
||||
}
|
||||
if (input.type == "password") {
|
||||
input.focus();
|
||||
input.value = "$(javascript_escape "${password}")";
|
||||
input.dispatchEvent(new Event('change'));
|
||||
input.blur();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ USAGE = """The domain of the site has to be in the name of the Bitwarden entry,
|
|||
"websites/github.com". The login information is inserted by emulating key events using qutebrowser's fake-key command in this manner:
|
||||
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms.
|
||||
|
||||
If enabled, with the `--totp` flag, it will also move the TOTP code to the
|
||||
clipboard, much like the Firefox add-on.
|
||||
|
||||
You must log into Bitwarden CLI using `bw login` prior to use of this script.
|
||||
The session key will be stored using keyctl for the number of seconds passed to
|
||||
the --auto-lock option.
|
||||
|
|
@ -34,18 +37,17 @@ the --auto-lock option.
|
|||
To use in qutebrowser, run: `spawn --userscript qute-bitwarden`
|
||||
"""
|
||||
|
||||
EPILOG = """Dependencies: tldextract (Python 3 module), Bitwarden CLI (1.7.4 is
|
||||
known to work but older versions may well also work)
|
||||
EPILOG = """Dependencies: tldextract (Python 3 module), pyperclip (optional
|
||||
Python module, used for TOTP codes), Bitwarden CLI (1.7.4 is known to work
|
||||
but older versions may well also work)
|
||||
|
||||
WARNING: The login details are viewable as plaintext in qutebrowser's debug log
|
||||
(qute://log) and might be shared if you decide to submit a crash report!"""
|
||||
|
||||
import argparse
|
||||
import enum
|
||||
import fnmatch
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -62,6 +64,8 @@ argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu -i
|
|||
help='Invocation used to execute a dmenu-provider')
|
||||
argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false',
|
||||
help="Don't automatically enter insert mode")
|
||||
argument_parser.add_argument('--totp', '-t', action='store_true',
|
||||
help="Copy TOTP key to clipboard")
|
||||
argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
|
||||
help='Encoding used to communicate with subprocesses')
|
||||
argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
|
||||
|
|
@ -73,6 +77,8 @@ group.add_argument('--username-only', '-e',
|
|||
action='store_true', help='Only insert username')
|
||||
group.add_argument('--password-only', '-w',
|
||||
action='store_true', help='Only insert password')
|
||||
group.add_argument('--totp-only', '-T',
|
||||
action='store_true', help='Only insert totp code')
|
||||
|
||||
stderr = functools.partial(print, file=sys.stderr)
|
||||
|
||||
|
|
@ -158,6 +164,26 @@ def pass_(domain, encoding, auto_lock):
|
|||
return out
|
||||
|
||||
|
||||
def get_totp_code(selection_id, domain_name, encoding, auto_lock):
|
||||
session_key = get_session_key(auto_lock)
|
||||
process = subprocess.run(
|
||||
['bw', 'get', 'totp', '--session', session_key, selection_id],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
|
||||
err = process.stderr.decode(encoding).strip()
|
||||
if err:
|
||||
# domain_name instead of selection_id to make it more user-friendly
|
||||
msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain_name, err)
|
||||
stderr(msg)
|
||||
return '[]'
|
||||
|
||||
out = process.stdout.decode(encoding).strip()
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def dmenu(items, invocation, encoding):
|
||||
command = shlex.split(invocation)
|
||||
process = subprocess.run(command, input='\n'.join(
|
||||
|
|
@ -227,11 +253,22 @@ def main(arguments):
|
|||
|
||||
username = selection['login']['username']
|
||||
password = selection['login']['password']
|
||||
totp = selection['login']['totp']
|
||||
|
||||
if arguments.username_only:
|
||||
fake_key_raw(username)
|
||||
elif arguments.password_only:
|
||||
fake_key_raw(password)
|
||||
elif arguments.totp_only:
|
||||
# No point in moving it to the clipboard in this case
|
||||
fake_key_raw(
|
||||
get_totp_code(
|
||||
selection['id'],
|
||||
selection['name'],
|
||||
arguments.io_encoding,
|
||||
arguments.auto_lock
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
|
||||
# back into insert-mode, so the form can be directly submitted by
|
||||
|
|
@ -243,6 +280,20 @@ def main(arguments):
|
|||
if arguments.insert_mode:
|
||||
qute_command('enter-mode insert')
|
||||
|
||||
# If it finds a TOTP code, it copies it to the clipboard,
|
||||
# which is the same behaviour as the Firefox add-on.
|
||||
if not arguments.totp_only and totp and arguments.totp:
|
||||
# The import is done here, to make pyperclip an optional dependency
|
||||
import pyperclip
|
||||
pyperclip.copy(
|
||||
get_totp_code(
|
||||
selection['id'],
|
||||
selection['name'],
|
||||
arguments.io_encoding,
|
||||
arguments.auto_lock
|
||||
)
|
||||
)
|
||||
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,10 +39,8 @@ you decide to submit a crash report!"""
|
|||
|
||||
import argparse
|
||||
import enum
|
||||
import fnmatch
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -46,10 +46,20 @@ const HEADER = `
|
|||
line-height: 1.2;
|
||||
}
|
||||
</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 -->
|
||||
<link rel="shortcut icon" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2ZXJzaW9uPSIxLjEiI
|
||||
HZpZXdCb3g9IjAgMCA2NCA2NCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgZmlsbD0iI2ZmZiI+CjxwYXRoIGQ9Im01MiAwaC00
|
||||
MGMtNC40MiAwLTggMy41OC04IDh2NDhjMCA0LjQyIDMuNTggOCA4IDhoNDBjNC40MiAwIDgtMy41OCA4LTh2LTQ4YzAtNC40Mi0zLjU4LTgtOC04em0wIDU
|
||||
yYzAgMi4yMS0xLjc5IDQtNCA0aC0zMmMtMi4yMSAwLTQtMS43OS00LTR2LTQwYzAtMi4yMSAxLjc5LTQgNC00aDMyYzIuMjEgMCA0IDEuNzkgNCA0em0tMT
|
||||
AtMzZoLTIwYy0xLjExIDAtMiAwLjg5NS0yIDJzMC44OTUgMiAyIDJoMjBjMS4xMSAwIDItMC44OTUgMi0ycy0wLjg5NS0yLTItMnptMCA4aC0yMGMtMS4xM
|
||||
SAwLTIgMC44OTUtMiAyczAuODk1IDIgMiAyaDIwYzEuMTEgMCAyLTAuODk1IDItMnMtMC44OTUtMi0yLTJ6bTAgOGgtMjBjLTEuMTEgMC0yIDAuODk1LTIg
|
||||
MnMwLjg5NSAyIDIgMmgyMGMxLjExIDAgMi0wLjg5NSAyLTJzLTAuODk1LTItMi0yem0tMTIgOGgtOGMtMS4xMSAwLTIgMC44OTUtMiAyczAuODk1IDIgMiA
|
||||
yaDhjMS4xMSAwIDItMC44OTUgMi0ycy0wLjg5NS0yLTItMnoiIGZpbGw9IiNmZmYiLz4KPC9nPgo8L3N2Zz4K"/>
|
||||
</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"}
|
||||
const domOpts = {url: process.env.QUTE_URL, contentType: "text/html; charset=utf-8"};
|
||||
|
||||
if (!fs.existsSync(scriptsDir)){
|
||||
fs.mkdirSync(scriptsDir);
|
||||
|
|
@ -66,6 +76,6 @@ JSDOM.fromFile(process.env.QUTE_HTML, domOpts).then(dom => {
|
|||
return 1;
|
||||
}
|
||||
// Success
|
||||
qute.open(['-t', tmpFile]);
|
||||
qute.open(['-t', '-r', tmpFile]);
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2018 jnphilipp <mail@jnphilipp.org>
|
||||
# Copyright 2018-2020 J. Nathanael Philipp (jnphilipp) <nathanael@philipp.land>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
|
@ -30,6 +30,8 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
||||
try:
|
||||
from stem import Signal
|
||||
from stem.control import Controller
|
||||
|
|
@ -41,12 +43,19 @@ except ImportError:
|
|||
print('Failed to import stem.')
|
||||
|
||||
|
||||
password = sys.argv[1]
|
||||
with Controller.from_port(port=9051) as controller:
|
||||
controller.authenticate(password)
|
||||
controller.signal(Signal.NEWNYM)
|
||||
if os.getenv('QUTE_FIFO'):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as f:
|
||||
f.write('message-info "Tor identity changed."')
|
||||
else:
|
||||
print('Tor identity changed.')
|
||||
if __name__ == '__main__':
|
||||
parser = ArgumentParser(prog='tor_identity')
|
||||
parser.add_argument('-c', '--control-port', default=9051,
|
||||
help='Tor control port (default 9051).')
|
||||
parser.add_argument('-p', '--password', type=str, default=None,
|
||||
help='Tor control port password.')
|
||||
args = parser.parse_args()
|
||||
|
||||
with Controller.from_port(port=args.control_port) as controller:
|
||||
controller.authenticate(args.password)
|
||||
controller.signal(Signal.NEWNYM)
|
||||
if os.getenv('QUTE_FIFO'):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as f:
|
||||
f.write('message-info "Tor identity changed."')
|
||||
else:
|
||||
print('Tor identity changed.')
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ markers =
|
|||
fake_os: Fake utils.is_* to a fake operating system
|
||||
unicode_locale: Tests which need an unicode locale to work
|
||||
qtwebkit6021_xfail: Tests which would fail on WebKit version 602.1
|
||||
js_headers: Sets JS headers dynamically on QtWebEngine (unsupported on some versions)
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
^SpellCheck: .*
|
||||
|
|
@ -71,5 +72,5 @@ xfail_strict = true
|
|||
filterwarnings =
|
||||
error
|
||||
# See https://github.com/HypothesisWorks/hypothesis/issues/2370
|
||||
ignore:.*, but function-scoped fixtures:hypothesis.errors.HypothesisDeprecationWarning
|
||||
ignore:.*which is reset between function calls but not between test cases generated by:hypothesis.errors.HypothesisDeprecationWarning
|
||||
faulthandler_timeout = 90
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2020 Florian Bruhin (The Compiler)"
|
|||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version__ = "1.10.1"
|
||||
__version__ = "1.12.0"
|
||||
__version_info__ = tuple(int(part) for part in __version__.split('.'))
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
|
|
|||
|
|
@ -208,12 +208,12 @@ class argument: # noqa: N801,N806 pylint: disable=invalid-name
|
|||
raise ValueError("{} has no argument {}!".format(funcname,
|
||||
self._argname))
|
||||
if not hasattr(func, 'qute_args'):
|
||||
func.qute_args = {} # type: ignore
|
||||
elif func.qute_args is None: # type: ignore
|
||||
func.qute_args = {} # type: ignore[attr-defined]
|
||||
elif func.qute_args is None: # type: ignore[attr-defined]
|
||||
raise ValueError("@cmdutils.argument got called above (after) "
|
||||
"@cmdutils.register for {}!".format(funcname))
|
||||
|
||||
arginfo = command.ArgInfo(**self._kwargs)
|
||||
func.qute_args[self._argname] = arginfo # type: ignore
|
||||
func.qute_args[self._argname] = arginfo # type: ignore[attr-defined]
|
||||
|
||||
return func
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ def run(args):
|
|||
q_app.setApplicationVersion(qutebrowser.__version__)
|
||||
|
||||
if args.version:
|
||||
print(version.version())
|
||||
print(version.version_info())
|
||||
sys.exit(usertypes.Exit.ok)
|
||||
|
||||
quitter.init(args)
|
||||
|
|
@ -159,13 +159,13 @@ def init(*, args: argparse.Namespace) -> None:
|
|||
eventfilter.init()
|
||||
|
||||
log.init.debug("Connecting signals...")
|
||||
q_app.focusChanged.connect(on_focus_changed) # type: ignore
|
||||
q_app.focusChanged.connect(on_focus_changed)
|
||||
|
||||
_process_args(args)
|
||||
|
||||
for scheme in ['http', 'https', 'qute']:
|
||||
QDesktopServices.setUrlHandler(
|
||||
scheme, open_desktopservices_url) # type: ignore
|
||||
scheme, open_desktopservices_url)
|
||||
|
||||
log.init.debug("Init done!")
|
||||
crashsignal.crash_handler.raise_crashdlg()
|
||||
|
|
@ -173,7 +173,6 @@ def init(*, args: argparse.Namespace) -> None:
|
|||
|
||||
def _init_icon():
|
||||
"""Initialize the icon of qutebrowser."""
|
||||
icon = QIcon()
|
||||
fallback_icon = QIcon()
|
||||
for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]:
|
||||
filename = ':/icons/qutebrowser-{size}x{size}.png'.format(size=size)
|
||||
|
|
@ -334,8 +333,12 @@ def _open_special_pages(args):
|
|||
'qute://warning/webkit'),
|
||||
|
||||
('old-qt-warning-shown',
|
||||
not qtutils.version_check('5.9'),
|
||||
not qtutils.version_check('5.11'),
|
||||
'qute://warning/old-qt'),
|
||||
|
||||
('session-warning-shown',
|
||||
qtutils.version_check('5.15', compiled=False),
|
||||
'qute://warning/sessions'),
|
||||
]
|
||||
|
||||
for state, condition, url in pages:
|
||||
|
|
@ -370,12 +373,23 @@ def open_desktopservices_url(url):
|
|||
tabbed_browser.tabopen(url)
|
||||
|
||||
|
||||
# This is effectively a @config.change_filter
|
||||
# Howerver, logging is initialized too early to use that annotation
|
||||
def _on_config_changed(name: str) -> None:
|
||||
if name.startswith('logging.'):
|
||||
log.init_from_config(config.val)
|
||||
|
||||
|
||||
def _init_modules(*, args):
|
||||
"""Initialize all 'modules' which need to be initialized.
|
||||
|
||||
Args:
|
||||
args: The argparse namespace.
|
||||
"""
|
||||
log.init.debug("Initializing logging from config...")
|
||||
log.init_from_config(config.val)
|
||||
config.instance.changed.connect(_on_config_changed)
|
||||
|
||||
log.init.debug("Initializing save manager...")
|
||||
save_manager = savemanager.SaveManager(q_app)
|
||||
objreg.register('save-manager', save_manager)
|
||||
|
|
@ -471,7 +485,9 @@ class Application(QApplication):
|
|||
self._last_focus_object = None
|
||||
|
||||
qt_args = configinit.qt_args(args)
|
||||
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
|
||||
log.init.debug("Commandline args: {}".format(sys.argv[1:]))
|
||||
log.init.debug("Parsed: {}".format(args))
|
||||
log.init.debug("Qt arguments: {}".format(qt_args[1:]))
|
||||
super().__init__(qt_args)
|
||||
|
||||
objects.args = args
|
||||
|
|
@ -479,7 +495,7 @@ class Application(QApplication):
|
|||
log.init.debug("Initializing application...")
|
||||
|
||||
self.launch_time = datetime.datetime.now()
|
||||
self.focusObjectChanged.connect( # type: ignore
|
||||
self.focusObjectChanged.connect( # type: ignore[attr-defined]
|
||||
self.on_focus_object_changed)
|
||||
self.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
import enum
|
||||
import itertools
|
||||
import typing
|
||||
import functools
|
||||
import typing
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt,
|
||||
|
|
@ -71,7 +71,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
|
||||
tab_class = webenginetab.WebEngineTab # type: typing.Type[AbstractTab]
|
||||
else:
|
||||
from qutebrowser.browser.webkit import webkittab
|
||||
tab_class = webkittab.WebKitTab
|
||||
|
|
@ -317,6 +317,7 @@ class AbstractSearch(QObject):
|
|||
def search(self, text: str, *,
|
||||
ignore_case: usertypes.IgnoreCase = usertypes.IgnoreCase.never,
|
||||
reverse: bool = False,
|
||||
wrap: bool = True,
|
||||
result_cb: _Callback = None) -> None:
|
||||
"""Find the given text on the page.
|
||||
|
||||
|
|
@ -324,6 +325,7 @@ class AbstractSearch(QObject):
|
|||
text: The text to search for.
|
||||
ignore_case: Search case-insensitively.
|
||||
reverse: Reverse search direction.
|
||||
wrap: Allow wrapping at the top or bottom of the page.
|
||||
result_cb: Called with a bool indicating whether a match was found.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
|
@ -425,27 +427,36 @@ class AbstractZoom(QObject):
|
|||
self._set_factor_internal(self._zoom_factor)
|
||||
|
||||
|
||||
class SelectionState(enum.Enum):
|
||||
|
||||
"""Possible states of selection in caret mode.
|
||||
|
||||
NOTE: Names need to line up with SelectionState in caret.js!
|
||||
"""
|
||||
|
||||
none = 1
|
||||
normal = 2
|
||||
line = 3
|
||||
|
||||
|
||||
class AbstractCaret(QObject):
|
||||
|
||||
"""Attribute ``caret`` of AbstractTab for caret browsing."""
|
||||
|
||||
#: Signal emitted when the selection was toggled.
|
||||
#: (argument - whether the selection is now active)
|
||||
selection_toggled = pyqtSignal(bool)
|
||||
selection_toggled = pyqtSignal(SelectionState)
|
||||
#: Emitted when a ``follow_selection`` action is done.
|
||||
follow_selected_done = pyqtSignal()
|
||||
|
||||
def __init__(self,
|
||||
tab: 'AbstractTab',
|
||||
mode_manager: modeman.ModeManager,
|
||||
parent: QWidget = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self.selection_enabled = False
|
||||
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.
|
||||
|
||||
def _on_mode_entered(self, mode: usertypes.KeyMode) -> None:
|
||||
raise NotImplementedError
|
||||
|
|
@ -498,7 +509,7 @@ class AbstractCaret(QObject):
|
|||
def move_to_end_of_document(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def toggle_selection(self) -> None:
|
||||
def toggle_selection(self, line: bool = False) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def drop_selection(self) -> None:
|
||||
|
|
@ -540,7 +551,7 @@ class AbstractScroller(QObject):
|
|||
|
||||
@pyqtSlot()
|
||||
def _log_scroll_pos_change(self) -> None:
|
||||
log.webview.vdebug( # type: ignore
|
||||
log.webview.vdebug( # type: ignore[attr-defined]
|
||||
"Scroll position changed to {}".format(self.pos_px()))
|
||||
|
||||
def _init_widget(self, widget: QWidget) -> None:
|
||||
|
|
@ -687,9 +698,9 @@ class AbstractElements:
|
|||
[typing.Optional['webelem.AbstractWebElement']], None]
|
||||
_ErrorCallback = typing.Callable[[Exception], None]
|
||||
|
||||
def __init__(self, tab: 'AbstractTab') -> None:
|
||||
def __init__(self) -> None:
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._tab = tab
|
||||
# self._tab is set by subclasses so mypy knows its concrete type.
|
||||
|
||||
def find_css(self, selector: str,
|
||||
callback: _MultiCallback,
|
||||
|
|
@ -824,6 +835,15 @@ class AbstractTabPrivate:
|
|||
def shutdown(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def run_js_sync(self, code: str) -> None:
|
||||
"""Run javascript sync.
|
||||
|
||||
Result will be returned when running JS is complete.
|
||||
This is only implemented for QtWebKit.
|
||||
For QtWebEngine, always raises UnsupportedOperationError.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractTab(QWidget):
|
||||
|
||||
|
|
@ -867,8 +887,18 @@ class AbstractTab(QWidget):
|
|||
# arg 1: The exit code.
|
||||
renderer_process_terminated = pyqtSignal(TerminationStatus, int)
|
||||
|
||||
def __init__(self, *, win_id: int, private: bool,
|
||||
# Hosts for which a certificate error happened. Shared between all tabs.
|
||||
#
|
||||
# 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]
|
||||
|
||||
def __init__(self, *, win_id: int,
|
||||
mode_manager: modeman.ModeManager,
|
||||
private: bool,
|
||||
parent: QWidget = None) -> None:
|
||||
utils.unused(mode_manager) # needed for mypy
|
||||
self.is_private = private
|
||||
self.win_id = win_id
|
||||
self.tab_id = next(tab_id_gen)
|
||||
|
|
@ -884,7 +914,6 @@ class AbstractTab(QWidget):
|
|||
self._layout = miscwidgets.WrapperLayout(self)
|
||||
self._widget = typing.cast(QWidget, None)
|
||||
self._progress = 0
|
||||
self._has_ssl_errors = False
|
||||
self._load_status = usertypes.LoadStatus.none
|
||||
self._tab_event_filter = eventfilter.TabEventFilter(
|
||||
self, parent=self)
|
||||
|
|
@ -946,7 +975,7 @@ class AbstractTab(QWidget):
|
|||
log.webview.warning("Unable to find event target!")
|
||||
return
|
||||
|
||||
evt.posted = True
|
||||
evt.posted = True # type: ignore[attr-defined]
|
||||
QApplication.postEvent(recipient, evt)
|
||||
|
||||
def navigation_blocked(self) -> bool:
|
||||
|
|
@ -971,7 +1000,6 @@ class AbstractTab(QWidget):
|
|||
@pyqtSlot()
|
||||
def _on_load_started(self) -> None:
|
||||
self._progress = 0
|
||||
self._has_ssl_errors = False
|
||||
self.data.viewing_source = False
|
||||
self._set_load_status(usertypes.LoadStatus.loading)
|
||||
self.load_started.emit()
|
||||
|
|
@ -1030,15 +1058,19 @@ class AbstractTab(QWidget):
|
|||
Needs to be called by subclasses to trigger a load status update, e.g.
|
||||
as a response to a loadFinished signal.
|
||||
"""
|
||||
if ok and not self._has_ssl_errors:
|
||||
if self.url().scheme() == 'https':
|
||||
self._set_load_status(usertypes.LoadStatus.success_https)
|
||||
else:
|
||||
self._set_load_status(usertypes.LoadStatus.success)
|
||||
elif ok:
|
||||
self._set_load_status(usertypes.LoadStatus.warn)
|
||||
url = self.url()
|
||||
is_https = url.scheme() == 'https'
|
||||
|
||||
if not ok:
|
||||
loadstatus = usertypes.LoadStatus.error
|
||||
elif is_https and url.host() in self._insecure_hosts:
|
||||
loadstatus = usertypes.LoadStatus.warn
|
||||
elif is_https:
|
||||
loadstatus = usertypes.LoadStatus.success_https
|
||||
else:
|
||||
self._set_load_status(usertypes.LoadStatus.error)
|
||||
loadstatus = usertypes.LoadStatus.success
|
||||
|
||||
self._set_load_status(loadstatus)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_history_trigger(self) -> None:
|
||||
|
|
@ -1126,7 +1158,8 @@ class AbstractTab(QWidget):
|
|||
def __repr__(self) -> str:
|
||||
try:
|
||||
qurl = self.url()
|
||||
url = qurl.toDisplayString(QUrl.EncodeUnicode) # type: ignore
|
||||
url = qurl.toDisplayString(
|
||||
QUrl.EncodeUnicode) # type: ignore[arg-type]
|
||||
except (AttributeError, RuntimeError) as exc:
|
||||
url = '<{}>'.format(exc.__class__.__name__)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class CommandDispatcher:
|
|||
else:
|
||||
return None
|
||||
|
||||
def _tab_focus_stack(self, mode: str, *, show_error=True):
|
||||
def _tab_focus_stack(self, mode: str, *, show_error: bool = True) -> None:
|
||||
"""Select the tab which was last focused."""
|
||||
tab_deque = self._tabbed_browser.tab_deque
|
||||
cur_tab = self._cntwidget()
|
||||
|
|
@ -308,8 +308,9 @@ class CommandDispatcher:
|
|||
urls = self._parse_url_input(url)
|
||||
|
||||
for i, cur_url in enumerate(urls):
|
||||
if secure:
|
||||
if secure and cur_url.scheme() == 'http':
|
||||
cur_url.setScheme('https')
|
||||
|
||||
if not window and i > 0:
|
||||
tab = False
|
||||
bg = True
|
||||
|
|
@ -452,7 +453,7 @@ class CommandDispatcher:
|
|||
@cmdutils.argument('win_id', completion=miscmodels.window)
|
||||
@cmdutils.argument('count', value=cmdutils.Value.count)
|
||||
def tab_give(self, win_id: int = None, keep: bool = False,
|
||||
count: int = None) -> None:
|
||||
count: int = None, private: bool = False) -> None:
|
||||
"""Give the current tab to a new or existing window if win_id given.
|
||||
|
||||
If no win_id is given, the tab will get detached into a new window.
|
||||
|
|
@ -461,6 +462,7 @@ class CommandDispatcher:
|
|||
win_id: The window ID of the window to give the current tab to.
|
||||
keep: If given, keep the old tab around.
|
||||
count: Overrides win_id (index starts at 1 for win_id=0).
|
||||
private: If the tab should be detached into a private instance.
|
||||
"""
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
raise cmdutils.CommandError("Can't give tabs when using "
|
||||
|
|
@ -478,7 +480,7 @@ class CommandDispatcher:
|
|||
"only one tab")
|
||||
|
||||
tabbed_browser = self._new_tabbed_browser(
|
||||
private=self._tabbed_browser.is_private)
|
||||
private=private or self._tabbed_browser.is_private)
|
||||
else:
|
||||
if win_id not in objreg.window_registry:
|
||||
raise cmdutils.CommandError(
|
||||
|
|
@ -487,6 +489,10 @@ class CommandDispatcher:
|
|||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
|
||||
if private and not tabbed_browser.is_private:
|
||||
raise cmdutils.CommandError(
|
||||
"The window with id {} is not private".format(win_id))
|
||||
|
||||
tabbed_browser.tabopen(self._current_url())
|
||||
if not keep:
|
||||
self._tabbed_browser.close_tab(self._current_widget(),
|
||||
|
|
@ -645,12 +651,13 @@ class CommandDispatcher:
|
|||
|
||||
def _yank_url(self, what):
|
||||
"""Helper method for yank() to get the URL to copy."""
|
||||
assert what in ['url', 'pretty-url', 'markdown'], what
|
||||
flags = QUrl.RemovePassword
|
||||
assert what in ['url', 'pretty-url'], what
|
||||
|
||||
if what == 'pretty-url':
|
||||
flags |= QUrl.DecodeReserved # type: ignore
|
||||
flags = QUrl.RemovePassword | QUrl.DecodeReserved
|
||||
else:
|
||||
flags |= QUrl.FullyEncoded # type: ignore
|
||||
flags = QUrl.RemovePassword | QUrl.FullyEncoded
|
||||
|
||||
url = QUrl(self._current_url())
|
||||
url_query = QUrlQuery()
|
||||
url_query_str = urlutils.query_string(url)
|
||||
|
|
@ -661,12 +668,11 @@ class CommandDispatcher:
|
|||
if key in config.val.url.yank_ignored_parameters:
|
||||
url_query.removeQueryItem(key)
|
||||
url.setQuery(url_query)
|
||||
return url.toString(flags) # type: ignore
|
||||
return url.toString(flags) # type: ignore[arg-type]
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('what', choices=['selection', 'url', 'pretty-url',
|
||||
'title', 'domain', 'markdown',
|
||||
'inline'])
|
||||
'title', 'domain', 'inline'])
|
||||
def yank(self, what='url', inline=None,
|
||||
sel=False, keep=False, quiet=False):
|
||||
"""Yank (copy) something to the clipboard or primary selection.
|
||||
|
|
@ -679,8 +685,6 @@ class CommandDispatcher:
|
|||
- `title`: The current page's title.
|
||||
- `domain`: The current scheme, domain, and port number.
|
||||
- `selection`: The selection under the cursor.
|
||||
- `markdown`: Yank title and URL in markdown format
|
||||
(deprecated, use `:yank inline [{title}]({url})` instead).
|
||||
- `inline`: Yank the text contained in the 'inline' argument.
|
||||
|
||||
sel: Use the primary selection instead of the clipboard.
|
||||
|
|
@ -712,14 +716,6 @@ class CommandDispatcher:
|
|||
caret = self._current_widget().caret
|
||||
caret.selection(callback=_selection_callback)
|
||||
return
|
||||
elif what == 'markdown':
|
||||
message.warning(":yank markdown is deprecated, use `:yank inline "
|
||||
"[{title}]({url})` instead.")
|
||||
idx = self._current_index()
|
||||
title = self._tabbed_browser.widget.page_title(idx)
|
||||
url = self._yank_url(what)
|
||||
s = '[{}]({})'.format(title, url)
|
||||
what = 'markdown URL' # For printing
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Invalid value {!r} for `what'.".format(what))
|
||||
|
||||
|
|
@ -909,7 +905,8 @@ class CommandDispatcher:
|
|||
tabbed_browser.widget.setCurrentWidget(tab)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', choices=['last', 'stack-next', 'stack-prev'])
|
||||
@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,
|
||||
count: int = None, no_last: bool = False) -> None:
|
||||
|
|
@ -1046,7 +1043,8 @@ class CommandDispatcher:
|
|||
if userscript:
|
||||
def _selection_callback(s):
|
||||
try:
|
||||
runner = self._run_userscript(s, cmd, args, verbose, count)
|
||||
runner = self._run_userscript(
|
||||
s, cmd, args, verbose, output_messages, count)
|
||||
runner.finished.connect(_on_proc_finished)
|
||||
except cmdutils.CommandError as e:
|
||||
message.error(str(e))
|
||||
|
|
@ -1072,13 +1070,15 @@ class CommandDispatcher:
|
|||
proc.start(cmd, args)
|
||||
proc.finished.connect(_on_proc_finished)
|
||||
|
||||
def _run_userscript(self, selection, cmd, args, verbose, count):
|
||||
def _run_userscript(self, selection, cmd, args, verbose, output_messages,
|
||||
count):
|
||||
"""Run a userscript given as argument.
|
||||
|
||||
Args:
|
||||
cmd: The userscript to run.
|
||||
args: Arguments to pass to the userscript.
|
||||
verbose: Show notifications when the command started/exited.
|
||||
output_messages: Show the output as messages.
|
||||
count: Exposed to the userscript.
|
||||
"""
|
||||
env = {
|
||||
|
|
@ -1105,7 +1105,8 @@ class CommandDispatcher:
|
|||
|
||||
try:
|
||||
runner = userscripts.run_async(
|
||||
tab, cmd, *args, win_id=self._win_id, env=env, verbose=verbose)
|
||||
tab, cmd, *args, win_id=self._win_id, env=env, verbose=verbose,
|
||||
output_messages=output_messages)
|
||||
except userscripts.Error as e:
|
||||
raise cmdutils.CommandError(e)
|
||||
return runner
|
||||
|
|
@ -1383,24 +1384,40 @@ class CommandDispatcher:
|
|||
self._open(url, tab, bg, window)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def messages(self, level='info', plain=False, tab=False, bg=False,
|
||||
window=False):
|
||||
@cmdutils.argument('logfilter', flag='f')
|
||||
def messages(self, level='info', *, plain=False, tab=False, bg=False,
|
||||
window=False, logfilter=None):
|
||||
"""Show a log of past messages.
|
||||
|
||||
Args:
|
||||
level: Include messages with `level` or higher severity.
|
||||
Valid values: vdebug, debug, info, warning, error, critical.
|
||||
plain: Whether to show plaintext (as opposed to html).
|
||||
logfilter: A comma-separated filter string of logging categories.
|
||||
If the filter string starts with an exclamation mark, it
|
||||
is negated.
|
||||
tab: Open in a new tab.
|
||||
bg: Open in a background tab.
|
||||
window: Open in a new window.
|
||||
"""
|
||||
if level.upper() not in log.LOG_LEVELS:
|
||||
raise cmdutils.CommandError("Invalid log level {}!".format(level))
|
||||
|
||||
query = QUrlQuery()
|
||||
query.addQueryItem('level', level)
|
||||
if plain:
|
||||
url = QUrl('qute://plainlog?level={}'.format(level))
|
||||
else:
|
||||
url = QUrl('qute://log?level={}'.format(level))
|
||||
query.addQueryItem('plain', typing.cast(str, None))
|
||||
|
||||
if logfilter:
|
||||
try:
|
||||
log.LogFilter.parse(logfilter)
|
||||
except log.InvalidLogFilterError as e:
|
||||
raise cmdutils.CommandError(e)
|
||||
query.addQueryItem('logfilter', logfilter)
|
||||
|
||||
url = QUrl('qute://log')
|
||||
url.setQuery(query)
|
||||
|
||||
self._open(url, tab, bg, window)
|
||||
|
||||
def _open_editor_cb(self, elem):
|
||||
|
|
@ -1503,6 +1520,7 @@ class CommandDispatcher:
|
|||
options = {
|
||||
'ignore_case': config.val.search.ignore_case,
|
||||
'reverse': reverse,
|
||||
'wrap': config.val.search.wrap,
|
||||
}
|
||||
|
||||
self._tabbed_browser.search_text = text
|
||||
|
|
|
|||
|
|
@ -860,7 +860,7 @@ class AbstractDownloadManager(QObject):
|
|||
self.data_changed.emit(-1)
|
||||
|
||||
@pyqtSlot(str, QUrl)
|
||||
def _on_pdfjs_requested(self, filename: str, original_url: QUrl):
|
||||
def _on_pdfjs_requested(self, filename: str, original_url: QUrl) -> None:
|
||||
"""Open PDF.js when a download requests it."""
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ class DownloadView(QListView):
|
|||
def __repr__(self):
|
||||
model = self.model()
|
||||
if model is None:
|
||||
count = 'None' # type: ignore
|
||||
count = 'None' # type: ignore[unreachable]
|
||||
else:
|
||||
count = model.rowCount()
|
||||
return utils.get_repr(self, count=count)
|
||||
|
|
@ -132,7 +132,10 @@ class DownloadView(QListView):
|
|||
item.open_file()
|
||||
item.remove()
|
||||
|
||||
def _get_menu_actions(self, item) -> _ActionListType:
|
||||
def _get_menu_actions(
|
||||
self,
|
||||
item: downloads.AbstractDownloadItem
|
||||
) -> _ActionListType:
|
||||
"""Get the available context menu actions for a given DownloadItem.
|
||||
|
||||
Args:
|
||||
|
|
|
|||
|
|
@ -108,8 +108,15 @@ class TabEventFilter(QObject):
|
|||
self._check_insertmode_on_release = False
|
||||
|
||||
def _handle_mouse_press(self, e):
|
||||
"""Handle pressing of a mouse button."""
|
||||
is_rocker_gesture = (config.val.input.rocker_gestures and
|
||||
"""Handle pressing of a mouse button.
|
||||
|
||||
Args:
|
||||
e: The QMouseEvent.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
is_rocker_gesture = (config.val.input.mouse.rocker_gestures and
|
||||
e.buttons() == Qt.LeftButton | Qt.RightButton)
|
||||
|
||||
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
|
||||
|
|
@ -129,7 +136,14 @@ class TabEventFilter(QObject):
|
|||
return False
|
||||
|
||||
def _handle_mouse_release(self, _e):
|
||||
"""Handle releasing of a mouse button."""
|
||||
"""Handle releasing of a mouse button.
|
||||
|
||||
Args:
|
||||
e: The QMouseEvent.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
# We want to make sure we check the focus element after the WebView is
|
||||
# updated completely.
|
||||
QTimer.singleShot(0, self._mouserelease_insertmode)
|
||||
|
|
@ -140,27 +154,39 @@ class TabEventFilter(QObject):
|
|||
|
||||
Args:
|
||||
e: The QWheelEvent.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
if self._ignore_wheel_event:
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/395
|
||||
self._ignore_wheel_event = False
|
||||
return True
|
||||
|
||||
if e.modifiers() & Qt.ControlModifier:
|
||||
mode = modeman.instance(self._tab.win_id).mode
|
||||
# Don't allow scrolling while hinting
|
||||
mode = modeman.instance(self._tab.win_id).mode
|
||||
if mode == usertypes.KeyMode.hint:
|
||||
return True
|
||||
|
||||
elif e.modifiers() & Qt.ControlModifier:
|
||||
if mode == usertypes.KeyMode.passthrough:
|
||||
return False
|
||||
|
||||
divider = config.val.zoom.mouse_divider
|
||||
if divider == 0:
|
||||
return False
|
||||
# Disable mouse zooming
|
||||
return True
|
||||
|
||||
factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
|
||||
if factor < 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
perc = int(100 * factor)
|
||||
message.info("Zoom level: {}%".format(perc), replace=True)
|
||||
self._tab.zoom.set_factor(factor)
|
||||
elif e.modifiers() & Qt.ShiftModifier:
|
||||
return True
|
||||
elif (e.modifiers() & Qt.ShiftModifier and
|
||||
not qtutils.version_check('5.9', compiled=False)):
|
||||
if e.angleDelta().y() > 0:
|
||||
self._tab.scroller.left()
|
||||
else:
|
||||
|
|
@ -170,16 +196,30 @@ class TabEventFilter(QObject):
|
|||
return False
|
||||
|
||||
def _handle_context_menu(self, _e):
|
||||
"""Suppress context menus if rocker gestures are turned on."""
|
||||
return config.val.input.rocker_gestures
|
||||
"""Suppress context menus if rocker gestures are turned on.
|
||||
|
||||
Args:
|
||||
e: The QContextMenuEvent.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
return config.val.input.mouse.rocker_gestures
|
||||
|
||||
def _handle_key_release(self, e):
|
||||
"""Ignore repeated key release events going to the website.
|
||||
|
||||
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-77208
|
||||
|
||||
Args:
|
||||
e: The QKeyEvent.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
return (e.isAutoRepeat() and
|
||||
qtutils.version_check('5.10') and
|
||||
qtutils.version_check('5.10', compiled=False) and
|
||||
not qtutils.version_check('5.14', compiled=False) and
|
||||
objects.backend == usertypes.Backend.QtWebEngine)
|
||||
|
||||
def _mousepress_insertmode_cb(self, elem):
|
||||
|
|
@ -232,7 +272,15 @@ class TabEventFilter(QObject):
|
|||
|
||||
Args:
|
||||
e: The QMouseEvent.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
if (not config.val.input.mouse.back_forward_buttons and
|
||||
e.button() in [Qt.XButton1, Qt.XButton2]):
|
||||
# Back and forward on mice are disabled
|
||||
return
|
||||
|
||||
if e.button() in [Qt.XButton1, Qt.LeftButton]:
|
||||
# Back button on mice which have it, or rocker gesture
|
||||
if self._tab.history.can_go_back():
|
||||
|
|
@ -247,7 +295,11 @@ class TabEventFilter(QObject):
|
|||
message.error("At end of history.")
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Filter events going to a QWeb(Engine)View."""
|
||||
"""Filter events going to a QWeb(Engine)View.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
evtype = event.type()
|
||||
if evtype not in self._handlers:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ class HintActions:
|
|||
flags = QUrl.FullyEncoded | QUrl.RemovePassword
|
||||
if url.scheme() == 'mailto':
|
||||
flags |= QUrl.RemoveScheme
|
||||
urlstr = url.toString(flags) # type: ignore
|
||||
urlstr = url.toString(flags) # type: ignore[arg-type]
|
||||
|
||||
new_content = urlstr
|
||||
|
||||
|
|
@ -256,14 +256,15 @@ class HintActions:
|
|||
|
||||
def run_cmd(self, url: QUrl, context: HintContext) -> None:
|
||||
"""Run the command based on a hint URL."""
|
||||
urlstr = url.toString(QUrl.FullyEncoded) # type: ignore
|
||||
urlstr = url.toString(QUrl.FullyEncoded) # type: ignore[arg-type]
|
||||
args = context.get_args(urlstr)
|
||||
commandrunner = runners.CommandRunner(self._win_id)
|
||||
commandrunner.run_safely(' '.join(args))
|
||||
|
||||
def preset_cmd_text(self, url: QUrl, context: HintContext) -> None:
|
||||
"""Preset a commandline text based on a hint URL."""
|
||||
urlstr = url.toDisplayString(QUrl.FullyEncoded) # type: ignore
|
||||
flags = QUrl.FullyEncoded
|
||||
urlstr = url.toDisplayString(flags) # type: ignore[arg-type]
|
||||
args = context.get_args(urlstr)
|
||||
text = ' '.join(args)
|
||||
if text[0] not in modeparsers.STARTCHARS:
|
||||
|
|
@ -308,7 +309,8 @@ class HintActions:
|
|||
}
|
||||
url = elem.resolve_url(context.baseurl)
|
||||
if url is not None:
|
||||
env['QUTE_URL'] = url.toString(QUrl.FullyEncoded) # type: ignore
|
||||
flags = QUrl.FullyEncoded
|
||||
env['QUTE_URL'] = url.toString(flags) # type: ignore[arg-type]
|
||||
|
||||
try:
|
||||
userscripts.run_async(context.tab, cmd, *args, win_id=self._win_id,
|
||||
|
|
@ -328,7 +330,7 @@ class HintActions:
|
|||
context: The HintContext to use.
|
||||
"""
|
||||
urlstr = url.toString(
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword) # type: ignore
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword) # type: ignore[arg-type]
|
||||
args = context.get_args(urlstr)
|
||||
commandrunner = runners.CommandRunner(self._win_id)
|
||||
commandrunner.run_safely('spawn ' + ' '.join(args))
|
||||
|
|
@ -893,7 +895,7 @@ class HintManager(QObject):
|
|||
|
||||
if self._context.hint_mode == 'number':
|
||||
# renumber filtered hints
|
||||
strings = self._hint_strings(visible)
|
||||
strings = self._hint_strings([label.elem for label in visible])
|
||||
self._context.labels = {}
|
||||
for label, string in zip(visible, strings):
|
||||
label.update_text('', string)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class HistoryProgress:
|
|||
self._progress.setMinimumDuration(500)
|
||||
self._progress.setLabelText(text)
|
||||
self._progress.setMaximum(maximum)
|
||||
self._progress.setCancelButton(None) # type: ignore
|
||||
self._progress.setCancelButton(None)
|
||||
self._progress.show()
|
||||
QApplication.processEvents()
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class AbstractWebInspector(QWidget):
|
|||
|
||||
def closeEvent(self, e):
|
||||
"""Save the geometry when closed."""
|
||||
data = bytes(self.saveGeometry())
|
||||
data = self.saveGeometry().data()
|
||||
geom = base64.b64encode(data).decode('ASCII')
|
||||
configfiles.state['geometry']['inspector'] = geom
|
||||
|
||||
|
|
|
|||
|
|
@ -162,8 +162,8 @@ def _find_prevnext(prev, elems):
|
|||
# pylint: disable=bad-config-option
|
||||
for regex in getattr(config.val.hints, option):
|
||||
# pylint: enable=bad-config-option
|
||||
log.hints.vdebug("== Checking regex '{}'." # type: ignore
|
||||
.format(regex.pattern))
|
||||
log.hints.vdebug( # type: ignore[attr-defined]
|
||||
"== Checking regex '{}'.".format(regex.pattern))
|
||||
for e in elems:
|
||||
text = str(e)
|
||||
if not text:
|
||||
|
|
@ -173,8 +173,8 @@ def _find_prevnext(prev, elems):
|
|||
regex.pattern, text))
|
||||
return e
|
||||
else:
|
||||
log.hints.vdebug("No match on '{}'!" # type: ignore
|
||||
.format(text))
|
||||
log.hints.vdebug( # type: ignore[attr-defined]
|
||||
"No match on '{}'!".format(text))
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,9 @@ def _js_slot(*args):
|
|||
# pylint: disable=protected-access
|
||||
return self._error_con.callAsConstructor([e])
|
||||
# pylint: enable=protected-access
|
||||
return pyqtSlot(*args, result=QJSValue)(new_method)
|
||||
|
||||
deco = pyqtSlot(*args, result=QJSValue) # type: ignore[arg-type]
|
||||
return deco(new_method)
|
||||
return _decorator
|
||||
|
||||
|
||||
|
|
@ -215,10 +217,10 @@ class PACResolver:
|
|||
if from_file:
|
||||
string_flags = QUrl.PrettyDecoded
|
||||
else:
|
||||
string_flags = QUrl.RemoveUserInfo # type: ignore
|
||||
string_flags = QUrl.RemoveUserInfo # type: ignore[assignment]
|
||||
if query.url().scheme() == 'https':
|
||||
string_flags |= QUrl.RemovePath # type: ignore
|
||||
string_flags |= QUrl.RemoveQuery # type: ignore
|
||||
string_flags |= QUrl.RemovePath # type: ignore[assignment]
|
||||
string_flags |= QUrl.RemoveQuery # type: ignore[assignment]
|
||||
|
||||
result = self._resolver.call([query.url().toString(string_flags),
|
||||
query.peerHostName()])
|
||||
|
|
@ -266,7 +268,8 @@ class PACFetcher(QObject):
|
|||
"""Fetch the proxy from the remote URL."""
|
||||
assert self._manager is not None
|
||||
self._reply = self._manager.get(QNetworkRequest(self._pac_url))
|
||||
self._reply.finished.connect(self._finish) # type: ignore
|
||||
self._reply.finished.connect( # type: ignore[attr-defined]
|
||||
self._finish)
|
||||
|
||||
@pyqtSlot()
|
||||
def _finish(self):
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
"""Handling of proxies."""
|
||||
|
||||
import typing
|
||||
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot
|
||||
from PyQt5.QtNetwork import QNetworkProxy, QNetworkProxyFactory
|
||||
|
||||
|
|
@ -54,7 +52,8 @@ def _warn_for_pac():
|
|||
|
||||
@pyqtSlot()
|
||||
def shutdown():
|
||||
QNetworkProxyFactory.setApplicationProxyFactory(None) # type: ignore
|
||||
QNetworkProxyFactory.setApplicationProxyFactory(
|
||||
None) # type: ignore[arg-type]
|
||||
|
||||
|
||||
class ProxyFactory(QNetworkProxyFactory):
|
||||
|
|
@ -73,6 +72,18 @@ class ProxyFactory(QNetworkProxyFactory):
|
|||
else:
|
||||
return None
|
||||
|
||||
def _set_capabilities(self, proxy):
|
||||
if proxy.type() == QNetworkProxy.NoProxy:
|
||||
return
|
||||
|
||||
capabilities = proxy.capabilities()
|
||||
lookup_cap = QNetworkProxy.HostNameLookupCapability
|
||||
if config.val.content.proxy_dns_requests:
|
||||
capabilities |= lookup_cap
|
||||
else:
|
||||
capabilities &= ~lookup_cap
|
||||
proxy.setCapabilities(capabilities)
|
||||
|
||||
def queryProxy(self, query):
|
||||
"""Get the QNetworkProxies for a query.
|
||||
|
||||
|
|
@ -91,18 +102,13 @@ class ProxyFactory(QNetworkProxyFactory):
|
|||
elif isinstance(proxy, pac.PACFetcher):
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
# Looks like query.url() is always invalid on QtWebEngine...
|
||||
proxies = [urlutils.proxy_from_url(QUrl('direct://'))]
|
||||
proxy = urlutils.proxy_from_url(QUrl('direct://'))
|
||||
assert not isinstance(proxy, pac.PACFetcher)
|
||||
proxies = [proxy]
|
||||
else:
|
||||
proxies = proxy.resolve(query)
|
||||
else:
|
||||
proxies = [proxy]
|
||||
for p in proxies:
|
||||
if p.type() != QNetworkProxy.NoProxy:
|
||||
capabilities = p.capabilities()
|
||||
lookup_cap = QNetworkProxy.HostNameLookupCapability
|
||||
if config.val.content.proxy_dns_requests:
|
||||
capabilities |= lookup_cap # type: ignore
|
||||
else:
|
||||
capabilities &= ~lookup_cap # type: ignore
|
||||
p.setCapabilities(capabilities)
|
||||
for proxy in proxies:
|
||||
self._set_capabilities(proxy)
|
||||
return proxies
|
||||
|
|
|
|||
|
|
@ -30,6 +30,19 @@ from qutebrowser.misc import objects
|
|||
from qutebrowser.config import config
|
||||
|
||||
|
||||
_SYSTEM_PATHS = [
|
||||
# Debian pdf.js-common
|
||||
# Arch Linux pdfjs (AUR)
|
||||
'/usr/share/pdf.js/',
|
||||
# Flatpak (Flathub)
|
||||
'/app/share/pdf.js/',
|
||||
# Arch Linux pdf.js (AUR)
|
||||
'/usr/share/javascript/pdf.js/',
|
||||
# Debian libjs-pdf
|
||||
'/usr/share/javascript/pdf/',
|
||||
]
|
||||
|
||||
|
||||
class PDFJSNotFound(Exception):
|
||||
|
||||
"""Raised when no pdf.js installation is found.
|
||||
|
|
@ -84,6 +97,9 @@ def _generate_pdfjs_script(filename):
|
|||
url_query.addQueryItem('filename', filename)
|
||||
url.setQuery(url_query)
|
||||
|
||||
js_url = javascript.to_js(
|
||||
url.toString(QUrl.FullyEncoded)) # type: ignore[arg-type]
|
||||
|
||||
return jinja.js_environment.from_string("""
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
if (typeof window.PDFJS !== 'undefined') {
|
||||
|
|
@ -105,7 +121,7 @@ def _generate_pdfjs_script(filename):
|
|||
viewer.open({{ url }});
|
||||
});
|
||||
""").render(
|
||||
url=javascript.to_js(url.toString(QUrl.FullyEncoded)), # type: ignore
|
||||
url=js_url,
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70420
|
||||
disable_create_object_url=(
|
||||
not qtutils.version_check('5.12') and
|
||||
|
|
@ -127,16 +143,7 @@ def get_pdfjs_res_and_path(path):
|
|||
content = None
|
||||
file_path = None
|
||||
|
||||
system_paths = [
|
||||
# Debian pdf.js-common
|
||||
# Arch Linux pdfjs (AUR)
|
||||
'/usr/share/pdf.js/',
|
||||
# Flatpak (Flathub)
|
||||
'/app/share/pdf.js/',
|
||||
# Arch Linux pdf.js (AUR)
|
||||
'/usr/share/javascript/pdf.js/',
|
||||
# Debian libjs-pdf
|
||||
'/usr/share/javascript/pdf/',
|
||||
system_paths = _SYSTEM_PATHS + [
|
||||
# fallback
|
||||
os.path.join(standarddir.data(), 'pdfjs'),
|
||||
# hardcoded fallback for --temp-basedir
|
||||
|
|
@ -221,6 +228,7 @@ def is_available():
|
|||
"""Return true if a pdfjs installation is available."""
|
||||
try:
|
||||
get_pdfjs_res('build/pdf.js')
|
||||
get_pdfjs_res('web/viewer.html')
|
||||
except PDFJSNotFound:
|
||||
return False
|
||||
else:
|
||||
|
|
@ -243,7 +251,7 @@ def get_main_url(filename: str, original_url: QUrl) -> QUrl:
|
|||
query = QUrlQuery()
|
||||
query.addQueryItem('filename', filename) # read from our JS
|
||||
query.addQueryItem('file', '') # to avoid pdfjs opening the default PDF
|
||||
urlstr = original_url.toString(QUrl.FullyEncoded) # type: ignore
|
||||
urlstr = original_url.toString(QUrl.FullyEncoded) # type: ignore[arg-type]
|
||||
query.addQueryItem('source', urlstr)
|
||||
url.setQuery(query)
|
||||
return url
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ try:
|
|||
import secrets
|
||||
except ImportError:
|
||||
# New in Python 3.6
|
||||
secrets = None # type: ignore
|
||||
secrets = None # type: ignore[assignment]
|
||||
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl, qVersion
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ import qutebrowser
|
|||
from qutebrowser.browser import pdfjs, downloads, history
|
||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg, urlutils)
|
||||
objreg, urlutils, standarddir)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
|
|
@ -98,8 +98,8 @@ class Redirect(Exception):
|
|||
|
||||
|
||||
# Return value: (mimetype, data) (encoded as utf-8 if a str is returned)
|
||||
_Handler = TypeVar('_Handler',
|
||||
bound=Callable[[QUrl], Tuple[str, Union[str, bytes]]])
|
||||
_HandlerRet = Tuple[str, Union[str, bytes]]
|
||||
_Handler = TypeVar('_Handler', bound=Callable[[QUrl], _HandlerRet])
|
||||
|
||||
|
||||
class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
|
|
@ -125,7 +125,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
|||
return self._function(*args, **kwargs)
|
||||
|
||||
|
||||
def data_for_url(url):
|
||||
def data_for_url(url: QUrl) -> typing.Tuple[str, bytes]:
|
||||
"""Get the data to show for the given URL.
|
||||
|
||||
Args:
|
||||
|
|
@ -134,8 +134,9 @@ def data_for_url(url):
|
|||
Return:
|
||||
A (mimetype, data) tuple.
|
||||
"""
|
||||
norm_url = url.adjusted(QUrl.NormalizePathSegments |
|
||||
QUrl.StripTrailingSlash)
|
||||
norm_url = url.adjusted(
|
||||
QUrl.NormalizePathSegments | # type: ignore[arg-type]
|
||||
QUrl.StripTrailingSlash)
|
||||
if norm_url != url:
|
||||
raise Redirect(norm_url)
|
||||
|
||||
|
|
@ -181,7 +182,7 @@ def data_for_url(url):
|
|||
|
||||
|
||||
@add_handler('bookmarks')
|
||||
def qute_bookmarks(_url):
|
||||
def qute_bookmarks(_url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://bookmarks. Display all quickmarks / bookmarks."""
|
||||
bookmarks = sorted(objreg.get('bookmark-manager').marks.items(),
|
||||
key=lambda x: x[1]) # Sort by title
|
||||
|
|
@ -196,7 +197,7 @@ def qute_bookmarks(_url):
|
|||
|
||||
|
||||
@add_handler('tabs')
|
||||
def qute_tabs(_url):
|
||||
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]]]
|
||||
|
|
@ -217,7 +218,10 @@ def qute_tabs(_url):
|
|||
return 'text/html', src
|
||||
|
||||
|
||||
def history_data(start_time, offset=None):
|
||||
def history_data(
|
||||
start_time: float,
|
||||
offset: int = None
|
||||
) -> typing.Sequence[typing.Dict[str, typing.Union[str, int]]]:
|
||||
"""Return history data.
|
||||
|
||||
Arguments:
|
||||
|
|
@ -240,7 +244,7 @@ def history_data(start_time, offset=None):
|
|||
|
||||
|
||||
@add_handler('history')
|
||||
def qute_history(url):
|
||||
def qute_history(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://history. Display and serve history."""
|
||||
if url.path() == '/data':
|
||||
q_offset = QUrlQuery(url).queryItemValue("offset")
|
||||
|
|
@ -266,7 +270,7 @@ def qute_history(url):
|
|||
|
||||
|
||||
@add_handler('javascript')
|
||||
def qute_javascript(url):
|
||||
def qute_javascript(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://javascript.
|
||||
|
||||
Return content of file given as query parameter.
|
||||
|
|
@ -280,14 +284,14 @@ def qute_javascript(url):
|
|||
|
||||
|
||||
@add_handler('pyeval')
|
||||
def qute_pyeval(_url):
|
||||
def qute_pyeval(_url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://pyeval."""
|
||||
src = jinja.render('pre.html', title='pyeval', content=pyeval_output)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('spawn-output')
|
||||
def qute_spawn_output(_url):
|
||||
def qute_spawn_output(_url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://spawn-output."""
|
||||
src = jinja.render('pre.html', title='spawn output', content=spawn_output)
|
||||
return 'text/html', src
|
||||
|
|
@ -298,57 +302,60 @@ def qute_spawn_output(_url):
|
|||
def qute_version(_url):
|
||||
"""Handler for qute://version."""
|
||||
src = jinja.render('version.html', title='Version info',
|
||||
version=version.version(),
|
||||
version=version.version_info(),
|
||||
copyright=qutebrowser.__copyright__)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('plainlog')
|
||||
def qute_plainlog(url):
|
||||
"""Handler for qute://plainlog.
|
||||
|
||||
An optional query parameter specifies the minimum log level to print.
|
||||
For example, qute://log?level=warning prints warnings and errors.
|
||||
Level can be one of: vdebug, debug, info, warning, error, critical.
|
||||
"""
|
||||
if log.ram_handler is None:
|
||||
text = "Log output was disabled."
|
||||
else:
|
||||
level = QUrlQuery(url).queryItemValue('level')
|
||||
if not level:
|
||||
level = 'vdebug'
|
||||
text = log.ram_handler.dump_log(html=False, level=level)
|
||||
src = jinja.render('pre.html', title='log', content=text)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('log')
|
||||
def qute_log(url):
|
||||
def qute_log(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://log.
|
||||
|
||||
An optional query parameter specifies the minimum log level to print.
|
||||
There are three query parameters:
|
||||
|
||||
- level: The minimum log level to print.
|
||||
For example, qute://log?level=warning prints warnings and errors.
|
||||
Level can be one of: vdebug, debug, info, warning, error, critical.
|
||||
|
||||
- plain: If given (and not 'false'), plaintext is shown.
|
||||
|
||||
- logfilter: A filter string like the --logfilter commandline argument
|
||||
accepts.
|
||||
"""
|
||||
query = QUrlQuery(url)
|
||||
plain = (query.hasQueryItem('plain') and
|
||||
query.queryItemValue('plain').lower() != 'false')
|
||||
|
||||
if log.ram_handler is None:
|
||||
html_log = None
|
||||
content = "Log output was disabled." if plain else None
|
||||
else:
|
||||
level = QUrlQuery(url).queryItemValue('level')
|
||||
level = query.queryItemValue('level')
|
||||
if not level:
|
||||
level = 'vdebug'
|
||||
html_log = log.ram_handler.dump_log(html=True, level=level)
|
||||
|
||||
src = jinja.render('log.html', title='log', content=html_log)
|
||||
filter_str = query.queryItemValue('logfilter')
|
||||
|
||||
try:
|
||||
logfilter = (log.LogFilter.parse(filter_str, only_debug=False)
|
||||
if filter_str else None)
|
||||
except log.InvalidLogFilterError as e:
|
||||
raise UrlInvalidError(e)
|
||||
|
||||
content = log.ram_handler.dump_log(html=not plain,
|
||||
level=level, logfilter=logfilter)
|
||||
|
||||
template = 'pre.html' if plain else 'log.html'
|
||||
src = jinja.render(template, title='log', content=content)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('gpl')
|
||||
def qute_gpl(_url):
|
||||
def qute_gpl(_url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://gpl. Return HTML content as string."""
|
||||
return 'text/html', utils.read_file('html/license.html')
|
||||
|
||||
|
||||
def _asciidoc_fallback_path(html_path):
|
||||
def _asciidoc_fallback_path(html_path: str) -> typing.Optional[str]:
|
||||
"""Fall back to plaintext asciidoc if the HTML is unavailable."""
|
||||
path = html_path.replace('.html', '.asciidoc')
|
||||
try:
|
||||
|
|
@ -358,7 +365,7 @@ def _asciidoc_fallback_path(html_path):
|
|||
|
||||
|
||||
@add_handler('help')
|
||||
def qute_help(url):
|
||||
def qute_help(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://help."""
|
||||
urlpath = url.path()
|
||||
if not urlpath or urlpath == '/':
|
||||
|
|
@ -407,7 +414,7 @@ def qute_help(url):
|
|||
return 'text/html', data
|
||||
|
||||
|
||||
def _qute_settings_set(url):
|
||||
def _qute_settings_set(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://settings/set."""
|
||||
query = QUrlQuery(url)
|
||||
option = query.queryItemValue('option', QUrl.FullyDecoded)
|
||||
|
|
@ -429,7 +436,7 @@ def _qute_settings_set(url):
|
|||
|
||||
|
||||
@add_handler('settings')
|
||||
def qute_settings(url):
|
||||
def qute_settings(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://settings. View/change qute configuration."""
|
||||
global csrf_token
|
||||
|
||||
|
|
@ -457,7 +464,7 @@ def qute_settings(url):
|
|||
|
||||
|
||||
@add_handler('bindings')
|
||||
def qute_bindings(_url):
|
||||
def qute_bindings(_url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://bindings. View keybindings."""
|
||||
bindings = {}
|
||||
defaults = config.val.bindings.default
|
||||
|
|
@ -475,7 +482,7 @@ def qute_bindings(_url):
|
|||
|
||||
|
||||
@add_handler('back')
|
||||
def qute_back(url):
|
||||
def qute_back(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://back.
|
||||
|
||||
Simple page to free ram / lazy load a site, goes back on focusing the tab.
|
||||
|
|
@ -487,7 +494,7 @@ def qute_back(url):
|
|||
|
||||
|
||||
@add_handler('configdiff')
|
||||
def qute_configdiff(url):
|
||||
def qute_configdiff(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://configdiff."""
|
||||
if url.path() == '/old':
|
||||
try:
|
||||
|
|
@ -502,7 +509,7 @@ def qute_configdiff(url):
|
|||
|
||||
|
||||
@add_handler('pastebin-version')
|
||||
def qute_pastebin_version(_url):
|
||||
def qute_pastebin_version(_url: QUrl) -> _HandlerRet:
|
||||
"""Handler that pastebins the version string."""
|
||||
version.pastebin_version()
|
||||
return 'text/plain', b'Paste called.'
|
||||
|
|
@ -515,7 +522,7 @@ def _pdf_path(filename: str) -> str:
|
|||
|
||||
|
||||
@add_handler('pdfjs')
|
||||
def qute_pdfjs(url: QUrl):
|
||||
def qute_pdfjs(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://pdfjs.
|
||||
|
||||
Return the pdf.js viewer or redirect to original URL if the file does not
|
||||
|
|
@ -566,7 +573,7 @@ def qute_pdfjs(url: QUrl):
|
|||
|
||||
|
||||
@add_handler('warning')
|
||||
def qute_warning(url):
|
||||
def qute_warning(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://warning."""
|
||||
path = url.path()
|
||||
if path == '/old-qt':
|
||||
|
|
@ -576,6 +583,11 @@ def qute_warning(url):
|
|||
elif path == '/webkit':
|
||||
src = jinja.render('warning-webkit.html',
|
||||
title='QtWebKit backend warning')
|
||||
elif path == '/sessions':
|
||||
src = jinja.render('warning-sessions.html',
|
||||
title='Qt 5.15 sessions warning',
|
||||
datadir=standarddir.data(),
|
||||
sep=os.sep)
|
||||
else:
|
||||
raise NotFoundError("Invalid warning page {}".format(path))
|
||||
return 'text/html', src
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ import typing
|
|||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
|
||||
from qutebrowser.utils import (usertypes, message, log, objreg, jinja, utils,
|
||||
qtutils)
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
|
||||
|
||||
|
|
@ -160,7 +161,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
|||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
ssl_strict = config.instance.get('content.ssl_strict', url=url)
|
||||
log.webview.debug("Certificate errors {!r}, strict {}".format(
|
||||
log.network.debug("Certificate errors {!r}, strict {}".format(
|
||||
errors, ssl_strict))
|
||||
|
||||
for error in errors:
|
||||
|
|
@ -186,7 +187,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
|||
ignore = False
|
||||
return ignore
|
||||
elif ssl_strict is False:
|
||||
log.webview.debug("ssl_strict is False, only warning about errors")
|
||||
log.network.debug("ssl_strict is False, only warning about errors")
|
||||
for err in errors:
|
||||
# FIXME we might want to use warn here (non-fatal error)
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/114
|
||||
|
|
@ -285,8 +286,13 @@ def get_user_stylesheet(searching=False):
|
|||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
css += f.read()
|
||||
|
||||
if (config.val.scrolling.bar == 'never' or
|
||||
config.val.scrolling.bar == 'when-searching' and not searching):
|
||||
setting = config.val.scrolling.bar
|
||||
overlay_bar_available = (qtutils.version_check('5.11', compiled=False) and
|
||||
not utils.is_mac)
|
||||
if setting == 'overlay' and not overlay_bar_available:
|
||||
setting = 'when-searching'
|
||||
|
||||
if setting == 'never' or setting == 'when-searching' and not searching:
|
||||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||
|
||||
return css
|
||||
|
|
|
|||
|
|
@ -392,7 +392,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||
window.show()
|
||||
# FIXME:typing Why can't mypy determine the type of
|
||||
# window.tabbed_browser?
|
||||
window.tabbed_browser.tabopen(url) # type: ignore
|
||||
window.tabbed_browser.tabopen(url) # type: ignore[has-type]
|
||||
else:
|
||||
raise ValueError("Unknown ClickTarget {}".format(click_target))
|
||||
|
||||
|
|
|
|||
|
|
@ -20,21 +20,40 @@
|
|||
"""Filter for QtWebEngine cookies."""
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, qtutils
|
||||
from qutebrowser.utils import utils, qtutils, log
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
@utils.prevent_exceptions(False) # Runs in I/O thread
|
||||
def _accept_cookie(request):
|
||||
"""Check whether the given cookie should be accepted."""
|
||||
accept = config.val.content.cookies.accept
|
||||
url = request.firstPartyUrl
|
||||
if not url.isValid():
|
||||
url = None
|
||||
|
||||
if qtutils.version_check('5.11.3', compiled=False):
|
||||
third_party = request.thirdParty
|
||||
else:
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-71393
|
||||
third_party = (request.thirdParty and
|
||||
not request.firstPartyUrl.isEmpty())
|
||||
|
||||
accept = config.instance.get('content.cookies.accept',
|
||||
url=url)
|
||||
|
||||
if 'log-cookies' in objects.debug_flags:
|
||||
first_party_str = ("<unknown>" if not request.firstPartyUrl.isValid()
|
||||
else request.firstPartyUrl.toDisplayString())
|
||||
origin_str = ("<unknown>" if not request.origin.isValid()
|
||||
else request.origin.toDisplayString())
|
||||
log.network.debug('Cookie from origin {} on {} (third party: {}) '
|
||||
'-> applying setting {}'
|
||||
.format(origin_str, first_party_str, third_party,
|
||||
accept))
|
||||
|
||||
if accept == 'all':
|
||||
return True
|
||||
elif accept in ['no-3rdparty', 'no-unknown-3rdparty']:
|
||||
if qtutils.version_check('5.11.3', compiled=False):
|
||||
third_party = request.thirdParty
|
||||
else:
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-71393
|
||||
third_party = (request.thirdParty and
|
||||
not request.firstPartyUrl.isEmpty())
|
||||
return not third_party
|
||||
elif accept == 'never':
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
|||
info.resourceType())
|
||||
navigation_type_str = debug.qenum_key(QWebEngineUrlRequestInfo,
|
||||
info.navigationType())
|
||||
log.webview.debug("{} {}, first-party {}, resource {}, "
|
||||
log.network.debug("{} {}, first-party {}, resource {}, "
|
||||
"navigation {}".format(
|
||||
bytes(info.requestMethod()).decode('ascii'),
|
||||
info.requestUrl().toDisplayString(),
|
||||
|
|
@ -164,7 +164,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
|||
url = info.requestUrl()
|
||||
first_party = info.firstPartyUrl()
|
||||
if not url.isValid():
|
||||
log.webview.debug("Ignoring invalid intercepted URL: {}".format(
|
||||
log.network.debug("Ignoring invalid intercepted URL: {}".format(
|
||||
url.errorString()))
|
||||
return
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
|||
try:
|
||||
resource_type = self._resource_types[info.resourceType()]
|
||||
except KeyError:
|
||||
log.webview.warning(
|
||||
log.network.warning(
|
||||
"Resource type {} not found in RequestInterceptor dict."
|
||||
.format(debug.qenum_key(QWebEngineUrlRequestInfo,
|
||||
info.resourceType())))
|
||||
|
|
@ -184,7 +184,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
|||
if (first_party != QUrl('qute://settings/') or
|
||||
info.resourceType() !=
|
||||
QWebEngineUrlRequestInfo.ResourceTypeXhr):
|
||||
log.webview.warning("Blocking malicious request from {} to {}"
|
||||
log.network.warning("Blocking malicious request from {} to {}"
|
||||
.format(first_party.toDisplayString(),
|
||||
url.toDisplayString()))
|
||||
info.block(True)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
|
|||
from qutebrowser.utils import qtutils
|
||||
|
||||
|
||||
# kHistoryStreamVersion = 3 was originally set when history serializing was
|
||||
# implemented in QtWebEngine:
|
||||
# https://codereview.qt-project.org/c/qt/qtwebengine/+/81529
|
||||
#
|
||||
# Qt 5.14 added version 4 which also serializes favicons:
|
||||
# https://codereview.qt-project.org/c/qt/qtwebengine/+/279407
|
||||
# However, we don't care about those, so let's keep it at 3.
|
||||
HISTORY_STREAM_VERSION = 3
|
||||
|
||||
|
||||
|
|
@ -36,32 +43,63 @@ def _serialize_item(item, stream):
|
|||
item: The WebHistoryItem to write.
|
||||
stream: The QDataStream to write to.
|
||||
"""
|
||||
### Thanks to Otter Browser:
|
||||
### https://github.com/OtterBrowser/otter-browser/blob/v0.9.10/src/modules/backends/web/qtwebengine/QtWebEngineWebWidget.cpp#L1210
|
||||
### src/core/web_contents_adapter.cpp serializeNavigationHistory
|
||||
# Thanks to Otter Browser:
|
||||
# https://github.com/OtterBrowser/otter-browser/blob/v1.0.01/src/modules/backends/web/qtwebengine/QtWebEnginePage.cpp#L260
|
||||
#
|
||||
# Relevant QtWebEngine source:
|
||||
# src/core/web_contents_adapter.cpp serializeNavigationHistory
|
||||
#
|
||||
# Sample data:
|
||||
# [TabHistoryItem(active=True,
|
||||
# original_url=QUrl('file:///home/florian/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt'),
|
||||
# title='1.txt',
|
||||
# url=QUrl('file:///home/florian/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt'),
|
||||
# user_data={'zoom': 1.0, 'scroll-pos': QPoint()})]
|
||||
|
||||
## toQt(entry->GetVirtualURL());
|
||||
# \x00\x00\x00Jfile:///home/florian/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt
|
||||
qtutils.serialize_stream(stream, item.url)
|
||||
|
||||
## toQt(entry->GetTitle());
|
||||
# \x00\x00\x00\n\x001\x00.\x00t\x00x\x00t
|
||||
stream.writeQString(item.title)
|
||||
|
||||
## QByteArray(encodedPageState.data(), encodedPageState.size());
|
||||
# \xff\xff\xff\xff
|
||||
qtutils.serialize_stream(stream, QByteArray())
|
||||
|
||||
## static_cast<qint32>(entry->GetTransitionType());
|
||||
# chromium/ui/base/page_transition_types.h
|
||||
# \x00\x00\x00\x00
|
||||
stream.writeInt32(0) # PAGE_TRANSITION_LINK
|
||||
|
||||
## entry->GetHasPostData();
|
||||
# \x00
|
||||
stream.writeBool(False)
|
||||
|
||||
## toQt(entry->GetReferrer().url);
|
||||
# \xff\xff\xff\xff
|
||||
qtutils.serialize_stream(stream, QUrl())
|
||||
|
||||
## static_cast<qint32>(entry->GetReferrer().policy);
|
||||
# chromium/third_party/WebKit/public/platform/WebReferrerPolicy.h
|
||||
# \x00\x00\x00\x00
|
||||
stream.writeInt32(0) # WebReferrerPolicyAlways
|
||||
|
||||
## toQt(entry->GetOriginalRequestURL());
|
||||
# \x00\x00\x00Jfile:///home/florian/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt
|
||||
qtutils.serialize_stream(stream, item.original_url)
|
||||
|
||||
## entry->GetIsOverridingUserAgent();
|
||||
# \x00
|
||||
stream.writeBool(False)
|
||||
|
||||
## static_cast<qint64>(entry->GetTimestamp().ToInternalValue());
|
||||
# \x00\x00\x00\x00^\x97$\xe7
|
||||
stream.writeInt64(int(time.time()))
|
||||
|
||||
## entry->GetHttpStatusCode();
|
||||
# \x00\x00\x00\xc8
|
||||
stream.writeInt(200)
|
||||
|
||||
|
||||
|
|
@ -102,12 +140,13 @@ def serialize(items):
|
|||
current_idx = -1
|
||||
|
||||
### src/core/web_contents_adapter.cpp serializeNavigationHistory
|
||||
# sample data:
|
||||
# kHistoryStreamVersion
|
||||
stream.writeInt(HISTORY_STREAM_VERSION)
|
||||
stream.writeInt(HISTORY_STREAM_VERSION) # \x00\x00\x00\x03
|
||||
# count
|
||||
stream.writeInt(len(items))
|
||||
stream.writeInt(len(items)) # \x00\x00\x00\x01
|
||||
# currentIndex
|
||||
stream.writeInt(current_idx)
|
||||
stream.writeInt(current_idx) # \x00\x00\x00\x00
|
||||
|
||||
for item in items:
|
||||
_serialize_item(item, stream)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import os.path
|
|||
import urllib
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QUrl
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QObject
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
|
||||
|
||||
from qutebrowser.browser import downloads, pdfjs
|
||||
|
|
@ -39,16 +39,18 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
_qt_item: The wrapped item.
|
||||
"""
|
||||
|
||||
def __init__(self, qt_item: QWebEngineDownloadItem, parent=None):
|
||||
def __init__(self, qt_item: QWebEngineDownloadItem,
|
||||
parent: QObject = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._qt_item = qt_item
|
||||
qt_item.downloadProgress.connect( # type: ignore
|
||||
qt_item.downloadProgress.connect( # type: ignore[attr-defined]
|
||||
self.stats.on_download_progress)
|
||||
qt_item.stateChanged.connect(self._on_state_changed) # type: ignore
|
||||
qt_item.stateChanged.connect( # type: ignore[attr-defined]
|
||||
self._on_state_changed)
|
||||
|
||||
# Ensure wrapped qt_item is deleted manually when the wrapper object
|
||||
# is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373
|
||||
self.destroyed.connect(self._qt_item.deleteLater) # type: ignore
|
||||
self.destroyed.connect(self._qt_item.deleteLater)
|
||||
|
||||
def _is_page_download(self):
|
||||
"""Check if this item is a page (i.e. mhtml) download."""
|
||||
|
|
@ -93,7 +95,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||
"{}".format(state_name))
|
||||
|
||||
def _do_die(self):
|
||||
self._qt_item.downloadProgress.disconnect() # type: ignore
|
||||
progress_signal = self._qt_item.downloadProgress
|
||||
progress_signal.disconnect() # type: ignore[attr-defined]
|
||||
if self._qt_item.state() != QWebEngineDownloadItem.DownloadInterrupted:
|
||||
self._qt_item.cancel()
|
||||
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
# (it does so with a 0ms QTimer...)
|
||||
# This is also used in Qt's tests:
|
||||
# https://github.com/qt/qtwebengine/commit/5e572e88efa7ba7c2b9138ec19e606d3e345ac90
|
||||
QApplication.processEvents( # type: ignore
|
||||
QApplication.processEvents( # type: ignore[call-overload]
|
||||
QEventLoop.ExcludeSocketNotifiers |
|
||||
QEventLoop.ExcludeUserInputEvents)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ from PyQt5.QtCore import QBuffer, QIODevice, QUrl
|
|||
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
|
||||
QWebEngineUrlRequestJob)
|
||||
try:
|
||||
from PyQt5.QtWebEngineCore import QWebEngineUrlScheme # type: ignore
|
||||
from PyQt5.QtWebEngineCore import QWebEngineUrlScheme
|
||||
except ImportError:
|
||||
# Added in Qt 5.12
|
||||
QWebEngineUrlScheme = None
|
||||
QWebEngineUrlScheme = None # type: ignore[misc, assignment]
|
||||
|
||||
from qutebrowser.browser import qutescheme
|
||||
from qutebrowser.utils import log, qtutils
|
||||
|
|
@ -86,9 +86,9 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||
return True
|
||||
|
||||
if initiator.isValid() and initiator.scheme() != 'qute':
|
||||
log.misc.warning("Blocking malicious request from {} to {}".format(
|
||||
initiator.toDisplayString(),
|
||||
request_url.toDisplayString()))
|
||||
log.network.warning("Blocking malicious request from {} to {}"
|
||||
.format(initiator.toDisplayString(),
|
||||
request_url.toDisplayString()))
|
||||
job.fail(QWebEngineUrlRequestJob.RequestDenied)
|
||||
return False
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||
|
||||
assert url.scheme() == 'qute'
|
||||
|
||||
log.misc.debug("Got request for {}".format(url.toDisplayString()))
|
||||
log.network.debug("Got request for {}".format(url.toDisplayString()))
|
||||
try:
|
||||
mimetype, data = qutescheme.data_for_url(url)
|
||||
except qutescheme.Error as e:
|
||||
|
|
@ -136,14 +136,14 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||
QWebEngineUrlRequestJob.RequestFailed,
|
||||
}
|
||||
exctype = type(e)
|
||||
log.misc.error("{} while handling qute://* URL".format(
|
||||
log.network.error("{} while handling qute://* URL".format(
|
||||
exctype.__name__))
|
||||
job.fail(errors[exctype])
|
||||
except qutescheme.Redirect as e:
|
||||
qtutils.ensure_valid(e.url)
|
||||
job.redirect(e.url)
|
||||
else:
|
||||
log.misc.debug("Returning {} data".format(mimetype))
|
||||
log.network.debug("Returning {} data".format(mimetype))
|
||||
|
||||
# We can't just use the QBuffer constructor taking a QByteArray,
|
||||
# because that somehow segfaults...
|
||||
|
|
@ -165,6 +165,7 @@ def init():
|
|||
if QWebEngineUrlScheme is not None:
|
||||
assert not QWebEngineUrlScheme.schemeByName(b'qute').name()
|
||||
scheme = QWebEngineUrlScheme(b'qute')
|
||||
scheme.setFlags(QWebEngineUrlScheme.LocalScheme |
|
||||
QWebEngineUrlScheme.LocalAccessAllowed)
|
||||
scheme.setFlags(
|
||||
QWebEngineUrlScheme.LocalScheme | # type: ignore[arg-type]
|
||||
QWebEngineUrlScheme.LocalAccessAllowed)
|
||||
QWebEngineUrlScheme.registerScheme(scheme)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ from qutebrowser.browser.webengine import spell, webenginequtescheme
|
|||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.config.websettings import AttributeInfo as Attr
|
||||
from qutebrowser.utils import (utils, standarddir, qtutils, message, log,
|
||||
urlmatch)
|
||||
urlmatch, usertypes)
|
||||
|
||||
# The default QWebEngineProfile
|
||||
default_profile = typing.cast(QWebEngineProfile, None)
|
||||
|
|
@ -76,6 +76,10 @@ class _SettingsWrapper:
|
|||
for settings in self._settings:
|
||||
settings.setDefaultTextEncoding(encoding)
|
||||
|
||||
def setUnknownUrlSchemePolicy(self, policy):
|
||||
for settings in self._settings:
|
||||
settings.setUnknownUrlSchemePolicy(policy)
|
||||
|
||||
def testAttribute(self, attribute):
|
||||
return self._settings[0].testAttribute(attribute)
|
||||
|
||||
|
|
@ -88,6 +92,9 @@ class _SettingsWrapper:
|
|||
def defaultTextEncoding(self):
|
||||
return self._settings[0].defaultTextEncoding()
|
||||
|
||||
def unknownUrlSchemePolicy(self):
|
||||
return self._settings[0].unknownUrlSchemePolicy()
|
||||
|
||||
|
||||
class WebEngineSettings(websettings.AbstractSettings):
|
||||
|
||||
|
|
@ -151,6 +158,19 @@ class WebEngineSettings(websettings.AbstractSettings):
|
|||
'fonts.web.family.fantasy': QWebEngineSettings.FantasyFont,
|
||||
}
|
||||
|
||||
# Only Qt >= 5.11 support UnknownUrlSchemePolicy
|
||||
try:
|
||||
_UNKNOWN_URL_SCHEME_POLICY = {
|
||||
'disallow':
|
||||
QWebEngineSettings.DisallowUnknownUrlSchemes,
|
||||
'allow-from-user-interaction':
|
||||
QWebEngineSettings.AllowUnknownUrlSchemesFromUserInteraction,
|
||||
'allow-all':
|
||||
QWebEngineSettings.AllowAllUnknownUrlSchemes,
|
||||
}
|
||||
except AttributeError:
|
||||
_UNKNOWN_URL_SCHEME_POLICY = None
|
||||
|
||||
# Mapping from WebEngineSettings::initDefaults in
|
||||
# qtwebengine/src/core/web_engine_settings.cpp
|
||||
_FONT_TO_QFONT = {
|
||||
|
|
@ -162,6 +182,33 @@ class WebEngineSettings(websettings.AbstractSettings):
|
|||
QWebEngineSettings.FantasyFont: QFont.Fantasy,
|
||||
}
|
||||
|
||||
def set_unknown_url_scheme_policy(
|
||||
self, policy: typing.Union[str, usertypes.Unset]) -> bool:
|
||||
"""Set the UnknownUrlSchemePolicy to use.
|
||||
|
||||
Return:
|
||||
True if there was a change, False otherwise.
|
||||
"""
|
||||
old_value = self._settings.unknownUrlSchemePolicy()
|
||||
if isinstance(policy, usertypes.Unset):
|
||||
self._settings.resetUnknownUrlSchemePolicy()
|
||||
new_value = self._settings.unknownUrlSchemePolicy()
|
||||
else:
|
||||
new_value = self._UNKNOWN_URL_SCHEME_POLICY[policy]
|
||||
self._settings.setUnknownUrlSchemePolicy(new_value)
|
||||
return old_value != new_value
|
||||
|
||||
def _update_setting(self, setting, value):
|
||||
if setting == 'content.unknown_url_scheme_policy':
|
||||
if self._UNKNOWN_URL_SCHEME_POLICY:
|
||||
return self.set_unknown_url_scheme_policy(value)
|
||||
return False
|
||||
return super()._update_setting(setting, value)
|
||||
|
||||
def init_settings(self):
|
||||
super().init_settings()
|
||||
self.update_setting('content.unknown_url_scheme_policy')
|
||||
|
||||
def __init__(self, settings):
|
||||
super().__init__(settings)
|
||||
# Attributes which don't exist in all Qt versions.
|
||||
|
|
@ -279,8 +326,13 @@ def _update_settings(option):
|
|||
"""Update global settings when qwebsettings changed."""
|
||||
global_settings.update_setting(option)
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
|
||||
# (note this isn't actually fixed properly before Qt 5.15)
|
||||
header_bug_fixed = (not qtutils.version_check('5.12', compiled=False) or
|
||||
qtutils.version_check('5.15', compiled=False))
|
||||
|
||||
if option in ['content.headers.user_agent',
|
||||
'content.headers.accept_language']:
|
||||
'content.headers.accept_language'] and header_bug_fixed:
|
||||
default_profile.setter.set_http_headers()
|
||||
if private_profile:
|
||||
private_profile.setter.set_http_headers()
|
||||
|
|
@ -315,7 +367,8 @@ def _init_profiles():
|
|||
default_profile = QWebEngineProfile.defaultProfile()
|
||||
init_user_agent()
|
||||
|
||||
default_profile.setter = ProfileSetter(default_profile)
|
||||
default_profile.setter = ProfileSetter( # type: ignore[attr-defined]
|
||||
default_profile)
|
||||
default_profile.setCachePath(
|
||||
os.path.join(standarddir.cache(), 'webengine'))
|
||||
default_profile.setPersistentStoragePath(
|
||||
|
|
@ -325,7 +378,8 @@ def _init_profiles():
|
|||
|
||||
if not qtutils.is_single_process():
|
||||
private_profile = QWebEngineProfile()
|
||||
private_profile.setter = ProfileSetter(private_profile)
|
||||
private_profile.setter = ProfileSetter( # type: ignore[attr-defined]
|
||||
private_profile)
|
||||
assert private_profile.isOffTheRecord()
|
||||
private_profile.setter.init_profile()
|
||||
|
||||
|
|
@ -354,6 +408,7 @@ def _init_site_specific_quirks():
|
|||
'https://accounts.google.com/*': firefox_ua,
|
||||
'https://*.slack.com/*': new_chrome_ua,
|
||||
'https://docs.google.com/*': firefox_ua,
|
||||
'https://drive.google.com/*': firefox_ua,
|
||||
}
|
||||
|
||||
if not qtutils.version_check('5.9'):
|
||||
|
|
@ -367,9 +422,16 @@ def _init_site_specific_quirks():
|
|||
|
||||
def _init_devtools_settings():
|
||||
"""Make sure the devtools always get images/JS permissions."""
|
||||
for setting in ['content.javascript.enabled', 'content.images']:
|
||||
settings = [
|
||||
('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'))
|
||||
|
||||
for setting, value in settings:
|
||||
for pattern in ['chrome-devtools://*', 'devtools://*']:
|
||||
config.instance.set_obj(setting, True,
|
||||
config.instance.set_obj(setting, value,
|
||||
pattern=urlmatch.UrlPattern(pattern),
|
||||
hide_userconfig=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
|||
from qutebrowser.misc import miscwidgets, objects
|
||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||
message, objreg, jinja, debug)
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
|
|
@ -124,7 +125,7 @@ class WebEngineAction(browsertab.AbstractAction):
|
|||
tb = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._tab.win_id)
|
||||
urlstr = self._tab.url().toString(
|
||||
QUrl.RemoveUserInfo) # type: ignore
|
||||
QUrl.RemoveUserInfo) # type: ignore[arg-type]
|
||||
# The original URL becomes the path of a view-source: URL
|
||||
# (without a host), but query/fragment should stay.
|
||||
url = QUrl('view-source:' + urlstr)
|
||||
|
|
@ -156,6 +157,92 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
|
|||
self._widget.page().print(printer, callback)
|
||||
|
||||
|
||||
class _WebEngineSearchWrapHandler:
|
||||
|
||||
"""QtWebEngine implementations related to wrapping when searching.
|
||||
|
||||
Attributes:
|
||||
flag_wrap: An additional flag indicating whether the last search
|
||||
used wrapping.
|
||||
_active_match: The 1-based index of the currently active match
|
||||
on the page.
|
||||
_total_matches: The total number of search matches on the page.
|
||||
_nowrap_available: Whether the functionality to prevent wrapping
|
||||
is available.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._active_match = 0
|
||||
self._total_matches = 0
|
||||
self.flag_wrap = True
|
||||
self._nowrap_available = False
|
||||
|
||||
def connect_signal(self, page):
|
||||
"""Connect to the findTextFinished signal of the page.
|
||||
|
||||
Args:
|
||||
page: The QtWebEnginePage to connect to this handler.
|
||||
"""
|
||||
if not qtutils.version_check("5.14"):
|
||||
return
|
||||
|
||||
try:
|
||||
# pylint: disable=unused-import
|
||||
from PyQt5.QtWebEngineCore import QWebEngineFindTextResult
|
||||
except ImportError:
|
||||
# WORKAROUND for some odd PyQt/packaging bug where the
|
||||
# findTextResult signal is available, but QWebEngineFindTextResult
|
||||
# is not. Seems to happen on e.g. Gentoo.
|
||||
log.webview.warning("Could not import QWebEngineFindTextResult "
|
||||
"despite running on Qt 5.14. You might need "
|
||||
"to rebuild PyQtWebEngine.")
|
||||
return
|
||||
|
||||
page.findTextFinished.connect(self._store_match_data)
|
||||
self._nowrap_available = True
|
||||
|
||||
def _store_match_data(self, result):
|
||||
"""Store information on the last match.
|
||||
|
||||
The information will be checked against when wrapping is turned off.
|
||||
|
||||
Args:
|
||||
result: A FindTextResult passed by the findTextFinished signal.
|
||||
"""
|
||||
self._active_match = result.activeMatch()
|
||||
self._total_matches = result.numberOfMatches()
|
||||
log.webview.debug("Active search match: {}/{}"
|
||||
.format(self._active_match, self._total_matches))
|
||||
|
||||
def reset_match_data(self):
|
||||
"""Reset match information.
|
||||
|
||||
Stale information could lead to next_result or prev_result misbehaving.
|
||||
"""
|
||||
self._active_match = 0
|
||||
self._total_matches = 0
|
||||
|
||||
def prevent_wrapping(self, *, going_up):
|
||||
"""Prevent wrapping if possible and required.
|
||||
|
||||
Returns True if a wrap was prevented and False if not.
|
||||
|
||||
Args:
|
||||
going_up: Whether the search would scroll the page up or down.
|
||||
"""
|
||||
if (not self._nowrap_available or
|
||||
self.flag_wrap or self._total_matches == 0):
|
||||
return False
|
||||
elif going_up and self._active_match == 1:
|
||||
message.info("Search hit TOP")
|
||||
return True
|
||||
elif not going_up and self._active_match == self._total_matches:
|
||||
message.info("Search hit BOTTOM")
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class WebEngineSearch(browsertab.AbstractSearch):
|
||||
|
||||
"""QtWebEngine implementations related to searching on the page.
|
||||
|
|
@ -168,8 +255,16 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(tab, parent)
|
||||
self._flags = QWebEnginePage.FindFlags(0) # type: ignore
|
||||
self._flags = self._empty_flags()
|
||||
self._pending_searches = 0
|
||||
# The API necessary to stop wrapping was added in this version
|
||||
self._wrap_handler = _WebEngineSearchWrapHandler()
|
||||
|
||||
def _empty_flags(self):
|
||||
return QWebEnginePage.FindFlags(0) # type: ignore[call-overload]
|
||||
|
||||
def connect_signals(self):
|
||||
self._wrap_handler.connect_signal(self._widget.page())
|
||||
|
||||
def _find(self, text, flags, callback, caller):
|
||||
"""Call findText on the widget."""
|
||||
|
|
@ -207,10 +302,10 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||
callback(found)
|
||||
self.finished.emit(found)
|
||||
|
||||
self._widget.findText(text, flags, wrapped_callback)
|
||||
self._widget.page().findText(text, flags, wrapped_callback)
|
||||
|
||||
def search(self, text, *, ignore_case=usertypes.IgnoreCase.never,
|
||||
reverse=False, result_cb=None):
|
||||
reverse=False, wrap=True, result_cb=None):
|
||||
# Don't go to next entry on duplicate search
|
||||
if self.text == text and self.search_displayed:
|
||||
log.webview.debug("Ignoring duplicate search request"
|
||||
|
|
@ -218,7 +313,9 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||
return
|
||||
|
||||
self.text = text
|
||||
self._flags = QWebEnginePage.FindFlags(0) # type: ignore
|
||||
self._flags = self._empty_flags()
|
||||
self._wrap_handler.reset_match_data()
|
||||
self._wrap_handler.flag_wrap = wrap
|
||||
if self._is_case_sensitive(ignore_case):
|
||||
self._flags |= QWebEnginePage.FindCaseSensitively
|
||||
if reverse:
|
||||
|
|
@ -230,18 +327,27 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||
if self.search_displayed:
|
||||
self.cleared.emit()
|
||||
self.search_displayed = False
|
||||
self._widget.findText('')
|
||||
self._wrap_handler.reset_match_data()
|
||||
self._widget.page().findText('')
|
||||
|
||||
def prev_result(self, *, result_cb=None):
|
||||
# The int() here makes sure we get a copy of the flags.
|
||||
flags = QWebEnginePage.FindFlags(int(self._flags)) # type: ignore
|
||||
flags = QWebEnginePage.FindFlags(
|
||||
int(self._flags)) # type: ignore[call-overload]
|
||||
if flags & QWebEnginePage.FindBackward:
|
||||
if self._wrap_handler.prevent_wrapping(going_up=False):
|
||||
return
|
||||
flags &= ~QWebEnginePage.FindBackward
|
||||
else:
|
||||
if self._wrap_handler.prevent_wrapping(going_up=True):
|
||||
return
|
||||
flags |= QWebEnginePage.FindBackward
|
||||
self._find(self.text, flags, result_cb, 'prev_result')
|
||||
|
||||
def next_result(self, *, result_cb=None):
|
||||
going_up = self._flags & QWebEnginePage.FindBackward
|
||||
if self._wrap_handler.prevent_wrapping(going_up=going_up):
|
||||
return
|
||||
self._find(self.text, self._flags, result_cb, 'next_result')
|
||||
|
||||
|
||||
|
|
@ -249,6 +355,13 @@ 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
|
||||
|
||||
def _flags(self):
|
||||
"""Get flags to pass to JS."""
|
||||
flags = set()
|
||||
|
|
@ -282,7 +395,10 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||
if enabled is None:
|
||||
log.webview.debug("Ignoring selection status None")
|
||||
return
|
||||
self.selection_toggled.emit(enabled)
|
||||
if enabled:
|
||||
self.selection_toggled.emit(browsertab.SelectionState.normal)
|
||||
else:
|
||||
self.selection_toggled.emit(browsertab.SelectionState.none)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_left(self, mode):
|
||||
|
|
@ -337,8 +453,9 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||
def move_to_end_of_document(self):
|
||||
self._js_call('moveToEndOfDocument')
|
||||
|
||||
def toggle_selection(self):
|
||||
self._js_call('toggleSelection', callback=self.selection_toggled.emit)
|
||||
def toggle_selection(self, line=False):
|
||||
self._js_call('toggleSelection', line,
|
||||
callback=self._toggle_sel_translate)
|
||||
|
||||
def drop_selection(self):
|
||||
self._js_call('dropSelection')
|
||||
|
|
@ -413,6 +530,13 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||
code = javascript.assemble('caret', command, *args)
|
||||
self._tab.run_js_async(code, callback)
|
||||
|
||||
def _toggle_sel_translate(self, state_str):
|
||||
if state_str is None:
|
||||
message.error("Error toggling caret selection")
|
||||
return
|
||||
state = browsertab.SelectionState[state_str]
|
||||
self.selection_toggled.emit(state)
|
||||
|
||||
|
||||
class WebEngineScroller(browsertab.AbstractScroller):
|
||||
|
||||
|
|
@ -558,6 +682,16 @@ class WebEngineHistoryPrivate(browsertab.AbstractHistoryPrivate):
|
|||
qtutils.deserialize(data, self._history)
|
||||
|
||||
def load_items(self, items):
|
||||
if qtutils.version_check('5.15', compiled=False):
|
||||
# WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/5359
|
||||
if items:
|
||||
url = items[-1].url
|
||||
if ((url.scheme(), url.host()) == ('qute', 'back') and
|
||||
len(items) >= 2):
|
||||
url = items[-2].url
|
||||
self._tab.load_url(url)
|
||||
return
|
||||
|
||||
if items:
|
||||
self._tab.before_load_started.emit(items[-1].url)
|
||||
|
||||
|
|
@ -620,6 +754,10 @@ class WebEngineElements(browsertab.AbstractElements):
|
|||
|
||||
"""QtWebEngine implemementations related to elements on the page."""
|
||||
|
||||
def __init__(self, tab: 'WebEngineTab') -> None:
|
||||
super().__init__()
|
||||
self._tab = tab
|
||||
|
||||
def _js_cb_multiple(self, callback, error_cb, js_elems):
|
||||
"""Handle found elements coming from JS and call the real callback.
|
||||
|
||||
|
|
@ -708,10 +846,15 @@ class WebEngineAudio(browsertab.AbstractAudio):
|
|||
config.instance.changed.connect(self._on_config_changed)
|
||||
|
||||
def set_muted(self, muted: bool, override: bool = False) -> None:
|
||||
was_muted = self.is_muted()
|
||||
self._overridden = override
|
||||
assert self._widget is not None
|
||||
page = self._widget.page()
|
||||
page.setAudioMuted(muted)
|
||||
if was_muted != muted and qtutils.version_check('5.15'):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-85118
|
||||
# so that the tab title at least updates the muted indicator
|
||||
self.muted_changed.emit(muted)
|
||||
|
||||
def is_muted(self):
|
||||
page = self._widget.page()
|
||||
|
|
@ -723,7 +866,7 @@ class WebEngineAudio(browsertab.AbstractAudio):
|
|||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_url_changed(self, url):
|
||||
if self._overridden:
|
||||
if self._overridden or not url.isValid():
|
||||
return
|
||||
mute = config.instance.get('content.mute', url=url)
|
||||
self.set_muted(mute)
|
||||
|
|
@ -814,9 +957,11 @@ class _WebEnginePermissions(QObject):
|
|||
self._tab.data.fullscreen = on
|
||||
self._tab.fullscreen_requested.emit(on)
|
||||
if on:
|
||||
notification = miscwidgets.FullscreenNotification(self._widget)
|
||||
notification.show()
|
||||
notification.set_timeout(3000)
|
||||
timeout = config.val.content.fullscreen.overlay_timeout
|
||||
if timeout != 0:
|
||||
notification = miscwidgets.FullscreenNotification(self._widget)
|
||||
notification.set_timeout(timeout)
|
||||
notification.show()
|
||||
|
||||
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
|
||||
def _on_feature_permission_requested(self, url, feature):
|
||||
|
|
@ -829,9 +974,18 @@ class _WebEnginePermissions(QObject):
|
|||
page.setFeaturePermission, url, feature,
|
||||
QWebEnginePage.PermissionDeniedByUser)
|
||||
|
||||
permission_str = debug.qenum_key(QWebEnginePage, feature)
|
||||
|
||||
if not url.isValid():
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-85116
|
||||
log.webview.warning("Ignoring feature permission {} for invalid "
|
||||
"URL {}".format(permission_str, url))
|
||||
deny_permission()
|
||||
return
|
||||
|
||||
if feature not in self._options:
|
||||
log.webview.error("Unhandled feature permission {}".format(
|
||||
debug.qenum_key(QWebEnginePage, feature)))
|
||||
permission_str))
|
||||
deny_permission()
|
||||
return
|
||||
|
||||
|
|
@ -1097,19 +1251,31 @@ class _WebEngineScripts(QObject):
|
|||
"""Add site-specific quirk scripts.
|
||||
|
||||
NOTE: This isn't implemented for Qt 5.7 because of different UserScript
|
||||
semantics there. We only have a quirk for WhatsApp Web right now. It
|
||||
looks like that quirk isn't needed for Qt < 5.13.
|
||||
semantics there. The WhatsApp Web quirk isn't needed for Qt < 5.13.
|
||||
The globalthis_quirk would be, but let's not keep such old QtWebEngine
|
||||
versions on life support.
|
||||
"""
|
||||
if not config.val.content.site_specific_quirks:
|
||||
return
|
||||
|
||||
page_scripts = self._widget.page().scripts()
|
||||
quirks = [
|
||||
(
|
||||
'whatsapp_web_quirk',
|
||||
QWebEngineScript.DocumentReady,
|
||||
QWebEngineScript.ApplicationWorld,
|
||||
),
|
||||
]
|
||||
if not qtutils.version_check('5.13'):
|
||||
quirks.append(('globalthis_quirk',
|
||||
QWebEngineScript.DocumentCreation,
|
||||
QWebEngineScript.MainWorld))
|
||||
|
||||
for filename in ['whatsapp_web_quirk']:
|
||||
for filename, injection_point, world in quirks:
|
||||
script = QWebEngineScript()
|
||||
script.setName(filename)
|
||||
script.setWorldId(QWebEngineScript.ApplicationWorld)
|
||||
script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
||||
script.setWorldId(world)
|
||||
script.setInjectionPoint(injection_point)
|
||||
src = utils.read_file("javascript/{}.user.js".format(filename))
|
||||
script.setSourceCode(src)
|
||||
page_scripts.insert(script)
|
||||
|
|
@ -1136,6 +1302,9 @@ class WebEngineTabPrivate(browsertab.AbstractTabPrivate):
|
|||
self._tab.action.exit_fullscreen()
|
||||
self._widget.shutdown()
|
||||
|
||||
def run_js_sync(self, code):
|
||||
raise browsertab.UnsupportedOperationError
|
||||
|
||||
|
||||
class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
|
|
@ -1149,7 +1318,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
abort_questions = pyqtSignal()
|
||||
|
||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||
super().__init__(win_id=win_id, private=private, parent=parent)
|
||||
super().__init__(win_id=win_id,
|
||||
mode_manager=mode_manager,
|
||||
private=private,
|
||||
parent=parent)
|
||||
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
|
||||
private=private)
|
||||
self.history = WebEngineHistory(tab=self)
|
||||
|
|
@ -1301,7 +1473,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
title_url = QUrl(url)
|
||||
title_url.setScheme('')
|
||||
title_url_str = title_url.toDisplayString(
|
||||
QUrl.RemoveScheme) # type: ignore
|
||||
QUrl.RemoveScheme) # type: ignore[arg-type]
|
||||
if title == title_url_str.strip('/'):
|
||||
title = ""
|
||||
|
||||
|
|
@ -1323,12 +1495,15 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
title="Proxy authentication required", text=msg,
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
abort_on=[self.abort_questions], url=urlstr)
|
||||
|
||||
if answer is not None:
|
||||
authenticator.setUser(answer.user)
|
||||
authenticator.setPassword(answer.password)
|
||||
else:
|
||||
try:
|
||||
sip.assign(authenticator, QAuthenticator()) # type: ignore
|
||||
sip.assign( # type: ignore[attr-defined]
|
||||
authenticator,
|
||||
QAuthenticator())
|
||||
except AttributeError:
|
||||
self._show_error_page(url, "Proxy authentication required")
|
||||
|
||||
|
|
@ -1349,7 +1524,8 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
if not netrc_success and answer is None:
|
||||
log.network.debug("Aborting auth")
|
||||
try:
|
||||
sip.assign(authenticator, QAuthenticator()) # type: ignore
|
||||
sip.assign( # type: ignore[attr-defined]
|
||||
authenticator, QAuthenticator())
|
||||
except AttributeError:
|
||||
# WORKAROUND for
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
|
||||
|
|
@ -1454,19 +1630,19 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
|
||||
@pyqtSlot(certificateerror.CertificateErrorWrapper)
|
||||
def _on_ssl_errors(self, error):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
url = error.url()
|
||||
log.webview.debug("Certificate error: {}".format(error))
|
||||
self._insecure_hosts.add(url.host())
|
||||
|
||||
log.network.debug("Certificate error: {}".format(error))
|
||||
|
||||
if error.is_overridable():
|
||||
error.ignore = shared.ignore_certificate_errors(
|
||||
url, [error], abort_on=[self.abort_questions])
|
||||
else:
|
||||
log.webview.error("Non-overridable certificate error: "
|
||||
log.network.error("Non-overridable certificate error: "
|
||||
"{}".format(error))
|
||||
|
||||
log.webview.debug("ignore {}, URL {}, requested {}".format(
|
||||
log.network.debug("ignore {}, URL {}, requested {}".format(
|
||||
error.ignore, url, self.url(requested=True)))
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207
|
||||
|
|
@ -1619,7 +1795,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
|
||||
try:
|
||||
# pylint: disable=unused-import
|
||||
from PyQt5.QtWebEngineWidgets import ( # type: ignore
|
||||
from PyQt5.QtWebEngineWidgets import (
|
||||
QWebEngineClientCertificateSelection)
|
||||
except ImportError:
|
||||
pass
|
||||
|
|
@ -1638,10 +1814,13 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
page.loadFinished.connect(self._on_load_finished)
|
||||
|
||||
self.before_load_started.connect(self._on_before_load_started)
|
||||
self.shutting_down.connect(self.abort_questions) # type: ignore
|
||||
self.load_started.connect(self.abort_questions) # type: ignore
|
||||
self.shutting_down.connect(
|
||||
self.abort_questions) # type: ignore[arg-type]
|
||||
self.load_started.connect(
|
||||
self.abort_questions) # type: ignore[arg-type]
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.audio._connect_signals()
|
||||
self.search.connect_signals()
|
||||
self._permissions.connect_signals()
|
||||
self._scripts.connect_signals()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
"""The main browser widget for QtWebEngine."""
|
||||
|
||||
import typing
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
|
@ -66,20 +68,26 @@ class WebEngineView(QWebEngineView):
|
|||
However, it sometimes isn't, so we use this as a WORKAROUND for
|
||||
https://bugreports.qt.io/browse/QTBUG-68727
|
||||
|
||||
This got introduced in Qt 5.11.0 and fixed in 5.12.0.
|
||||
The above bug got introduced in Qt 5.11.0 and fixed in 5.12.0.
|
||||
"""
|
||||
if 'lost-focusproxy' not in objects.debug_flags:
|
||||
proxy = self.focusProxy()
|
||||
if proxy is not None:
|
||||
return proxy
|
||||
proxy = self.focusProxy() # type: typing.Optional[QWidget]
|
||||
|
||||
if 'lost-focusproxy' in objects.debug_flags:
|
||||
proxy = None
|
||||
|
||||
if (proxy is not None or
|
||||
not qtutils.version_check('5.11', compiled=False) or
|
||||
qtutils.version_check('5.12', compiled=False)):
|
||||
return proxy
|
||||
|
||||
# We don't want e.g. a QMenu.
|
||||
rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget'
|
||||
children = [c for c in self.findChildren(QWidget)
|
||||
if c.isVisible() and c.inherits(rwhv_class)]
|
||||
|
||||
log.webview.debug("Found possibly lost focusProxy: {}"
|
||||
.format(children))
|
||||
if children:
|
||||
log.webview.debug("Found possibly lost focusProxy: {}"
|
||||
.format(children))
|
||||
|
||||
return children[-1] if children else None
|
||||
|
||||
|
|
@ -216,8 +224,7 @@ class WebEnginePage(QWebEnginePage):
|
|||
self.shutting_down],
|
||||
escape_msg=escape_msg)
|
||||
except shared.CallSuper:
|
||||
return super().javaScriptPrompt( # type: ignore
|
||||
url, js_msg, default)
|
||||
return super().javaScriptPrompt(url, js_msg, default)
|
||||
|
||||
def javaScriptAlert(self, url, js_msg):
|
||||
"""Override javaScriptAlert to use qutebrowser prompts."""
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ from PyQt5.QtNetwork import QNetworkCookie, QNetworkCookieJar
|
|||
from PyQt5.QtCore import pyqtSignal, QDateTime
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, standarddir, objreg
|
||||
from qutebrowser.misc import lineparser
|
||||
from qutebrowser.utils import utils, standarddir, objreg, log
|
||||
from qutebrowser.misc import lineparser, objects
|
||||
|
||||
|
||||
cookie_jar = None
|
||||
|
|
@ -56,7 +56,13 @@ class RAMCookieJar(QNetworkCookieJar):
|
|||
Return:
|
||||
True if one or more cookies are set for 'url', otherwise False.
|
||||
"""
|
||||
if config.val.content.cookies.accept == 'never':
|
||||
accept = config.instance.get('content.cookies.accept', url=url)
|
||||
|
||||
if 'log-cookies' in objects.debug_flags:
|
||||
log.network.debug('Cookie on {} -> applying setting {}'
|
||||
.format(url.toDisplayString(), accept))
|
||||
|
||||
if accept == 'never':
|
||||
return False
|
||||
else:
|
||||
self.changed.emit()
|
||||
|
|
@ -89,7 +95,8 @@ class CookieJar(RAMCookieJar):
|
|||
"""Parse cookies from lineparser and store them."""
|
||||
cookies = [] # type: typing.Sequence[QNetworkCookie]
|
||||
for line in self._lineparser:
|
||||
cookies += QNetworkCookie.parseCookies(line)
|
||||
line_cookies = QNetworkCookie.parseCookies(line)
|
||||
cookies += line_cookies # type: ignore[operator]
|
||||
self.setAllCookies(cookies)
|
||||
|
||||
def purge_old_cookies(self):
|
||||
|
|
@ -98,7 +105,8 @@ class CookieJar(RAMCookieJar):
|
|||
# http://doc.qt.io/qt-5/qtwebkitexamples-webkitwidgets-browser-cookiejar-cpp.html
|
||||
now = QDateTime.currentDateTime()
|
||||
cookies = [c for c in self.allCookies()
|
||||
if c.isSessionCookie() or c.expirationDate() >= now]
|
||||
if c.isSessionCookie() or
|
||||
c.expirationDate() >= now] # type: ignore[operator]
|
||||
self.setAllCookies(cookies)
|
||||
|
||||
def save(self):
|
||||
|
|
|
|||
|
|
@ -105,8 +105,9 @@ def _is_secure_cipher(cipher):
|
|||
def init():
|
||||
"""Disable insecure SSL ciphers on old Qt versions."""
|
||||
default_ciphers = QSslSocket.defaultCiphers()
|
||||
log.init.debug("Default Qt ciphers: {}".format(
|
||||
', '.join(c.name() for c in default_ciphers)))
|
||||
log.init.vdebug( # type: ignore[attr-defined]
|
||||
"Default Qt ciphers: {}".format(
|
||||
', '.join(c.name() for c in default_ciphers)))
|
||||
|
||||
good_ciphers = []
|
||||
bad_ciphers = []
|
||||
|
|
@ -116,9 +117,10 @@ def init():
|
|||
else:
|
||||
bad_ciphers.append(cipher)
|
||||
|
||||
log.init.debug("Disabling bad ciphers: {}".format(
|
||||
', '.join(c.name() for c in bad_ciphers)))
|
||||
QSslSocket.setDefaultCiphers(good_ciphers)
|
||||
if bad_ciphers:
|
||||
log.init.debug("Disabling bad ciphers: {}".format(
|
||||
', '.join(c.name() for c in bad_ciphers)))
|
||||
QSslSocket.setDefaultCiphers(good_ciphers)
|
||||
|
||||
|
||||
_SavedErrorsType = typing.MutableMapping[urlutils.HostTupleType,
|
||||
|
|
@ -169,14 +171,15 @@ class NetworkManager(QNetworkAccessManager):
|
|||
}
|
||||
self._set_cookiejar()
|
||||
self._set_cache()
|
||||
self.sslErrors.connect(self.on_ssl_errors) # type: ignore
|
||||
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.authenticationRequired.connect( # type: ignore
|
||||
self.authenticationRequired.connect( # type: ignore[attr-defined]
|
||||
self.on_authentication_required)
|
||||
self.proxyAuthenticationRequired.connect( # type: ignore
|
||||
self.proxyAuthenticationRequired.connect( # type: ignore[attr-defined]
|
||||
self.on_proxy_authentication_required)
|
||||
self.netrc_used = False
|
||||
|
||||
|
|
@ -235,7 +238,7 @@ class NetworkManager(QNetworkAccessManager):
|
|||
errors: A list of errors.
|
||||
"""
|
||||
errors = [certificateerror.CertificateErrorWrapper(e) for e in errors]
|
||||
log.webview.debug("Certificate errors: {!r}".format(
|
||||
log.network.debug("Certificate errors: {!r}".format(
|
||||
' / '.join(str(err) for err in errors)))
|
||||
try:
|
||||
host_tpl = urlutils.host_tuple(
|
||||
|
|
@ -251,7 +254,7 @@ class NetworkManager(QNetworkAccessManager):
|
|||
is_rejected = set(errors).issubset(
|
||||
self._rejected_ssl_errors[host_tpl])
|
||||
|
||||
log.webview.debug("Already accepted: {} / "
|
||||
log.network.debug("Already accepted: {} / "
|
||||
"rejected {}".format(is_accepted, is_rejected))
|
||||
|
||||
if is_rejected:
|
||||
|
|
@ -424,7 +427,7 @@ class NetworkManager(QNetworkAccessManager):
|
|||
if 'log-requests' in objects.debug_flags:
|
||||
operation = debug.qenum_key(QNetworkAccessManager, op)
|
||||
operation = operation.replace('Operation', '').upper()
|
||||
log.webview.debug("{} {}, first-party {}".format(
|
||||
log.network.debug("{} {}, first-party {}".format(
|
||||
operation,
|
||||
req.url().toDisplayString(),
|
||||
current_url.toDisplayString()))
|
||||
|
|
|
|||
|
|
@ -59,12 +59,15 @@ class FixedDataNetworkReply(QNetworkReply):
|
|||
# For some reason, a segfault will be triggered if these lambdas aren't
|
||||
# there.
|
||||
# pylint: disable=unnecessary-lambda
|
||||
QTimer.singleShot(0, lambda:
|
||||
self.metaDataChanged.emit()) # type: ignore
|
||||
QTimer.singleShot(0, lambda:
|
||||
self.readyRead.emit()) # type: ignore
|
||||
QTimer.singleShot(0, lambda:
|
||||
self.finished.emit()) # type: ignore
|
||||
QTimer.singleShot(
|
||||
0,
|
||||
lambda: self.metaDataChanged.emit()) # type: ignore[attr-defined]
|
||||
QTimer.singleShot(
|
||||
0,
|
||||
lambda: self.readyRead.emit()) # type: ignore[attr-defined]
|
||||
QTimer.singleShot(
|
||||
0,
|
||||
lambda: self.finished.emit()) # type: ignore[attr-defined]
|
||||
|
||||
@pyqtSlot()
|
||||
def abort(self):
|
||||
|
|
@ -120,9 +123,9 @@ class ErrorNetworkReply(QNetworkReply):
|
|||
self.setOpenMode(QIODevice.ReadOnly)
|
||||
self.setError(error, errorstring)
|
||||
QTimer.singleShot(0, lambda:
|
||||
self.error.emit(error)) # type: ignore
|
||||
self.error.emit(error)) # type: ignore[attr-defined]
|
||||
QTimer.singleShot(0, lambda:
|
||||
self.finished.emit()) # type: ignore
|
||||
self.finished.emit()) # type: ignore[attr-defined]
|
||||
|
||||
def abort(self):
|
||||
"""Do nothing since it's a fake reply."""
|
||||
|
|
@ -150,7 +153,7 @@ class RedirectNetworkReply(QNetworkReply):
|
|||
super().__init__(parent)
|
||||
self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url)
|
||||
QTimer.singleShot(0, lambda:
|
||||
self.finished.emit()) # type: ignore
|
||||
self.finished.emit()) # type: ignore[attr-defined]
|
||||
|
||||
def abort(self):
|
||||
"""Called when there's e.g. a redirection limit."""
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ def handler(request, operation, current_url):
|
|||
if ((url.scheme(), url.host(), url.path()) ==
|
||||
('qute', 'settings', '/set')):
|
||||
if current_url != QUrl('qute://settings/'):
|
||||
log.webview.warning("Blocking malicious request from {} to {}"
|
||||
log.network.warning("Blocking malicious request from {} to {}"
|
||||
.format(current_url.toDisplayString(),
|
||||
url.toDisplayString()))
|
||||
return networkreply.ErrorNetworkReply(
|
||||
|
|
|
|||
|
|
@ -175,6 +175,11 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
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]:
|
||||
|
|
@ -189,7 +194,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
return None
|
||||
|
||||
text = utils.compact_text(self._elem.toOuterXml(), 500)
|
||||
log.webelem.vdebug( # type: ignore
|
||||
log.webelem.vdebug( # type: ignore[attr-defined]
|
||||
"Client rectangles of element '{}': {}".format(text, rects))
|
||||
|
||||
for i in range(int(rects.get("length", 0))):
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import xml.etree.ElementTree
|
|||
|
||||
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.QtPrintSupport import QPrinter
|
||||
|
|
@ -33,6 +34,7 @@ from qutebrowser.browser import browsertab, shared
|
|||
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
|
||||
webkitsettings)
|
||||
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
|
|
@ -85,7 +87,10 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(tab, parent)
|
||||
self._flags = QWebPage.FindFlags(0) # type: ignore
|
||||
self._flags = self._empty_flags()
|
||||
|
||||
def _empty_flags(self):
|
||||
return QWebPage.FindFlags(0) # type: ignore[call-overload]
|
||||
|
||||
def _call_cb(self, callback, found, text, flags, caller):
|
||||
"""Call the given callback if it's non-None.
|
||||
|
|
@ -125,7 +130,7 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
self._widget.findText('', QWebPage.HighlightAllOccurrences)
|
||||
|
||||
def search(self, text, *, ignore_case=usertypes.IgnoreCase.never,
|
||||
reverse=False, result_cb=None):
|
||||
reverse=False, wrap=True, result_cb=None):
|
||||
# Don't go to next entry on duplicate search
|
||||
if self.text == text and self.search_displayed:
|
||||
log.webview.debug("Ignoring duplicate search request"
|
||||
|
|
@ -137,11 +142,13 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
|
||||
self.text = text
|
||||
self.search_displayed = True
|
||||
self._flags = QWebPage.FindWrapsAroundDocument
|
||||
self._flags = self._empty_flags()
|
||||
if self._is_case_sensitive(ignore_case):
|
||||
self._flags |= QWebPage.FindCaseSensitively
|
||||
if reverse:
|
||||
self._flags |= QWebPage.FindBackward
|
||||
if wrap:
|
||||
self._flags |= QWebPage.FindWrapsAroundDocument
|
||||
# We actually search *twice* - once to highlight everything, then again
|
||||
# to get a mark so we can navigate.
|
||||
found = self._widget.findText(text, self._flags)
|
||||
|
|
@ -157,7 +164,8 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||
def prev_result(self, *, result_cb=None):
|
||||
self.search_displayed = True
|
||||
# The int() here makes sure we get a copy of the flags.
|
||||
flags = QWebPage.FindFlags(int(self._flags)) # type: ignore
|
||||
flags = QWebPage.FindFlags(
|
||||
int(self._flags)) # type: ignore[call-overload]
|
||||
if flags & QWebPage.FindBackward:
|
||||
flags &= ~QWebPage.FindBackward
|
||||
else:
|
||||
|
|
@ -170,13 +178,24 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
|
||||
"""QtWebKit implementations related to moving the cursor/selection."""
|
||||
|
||||
def __init__(self,
|
||||
tab: 'WebKitTab',
|
||||
mode_manager: modeman.ModeManager,
|
||||
parent: QWidget = None) -> None:
|
||||
super().__init__(mode_manager, parent)
|
||||
self._tab = tab
|
||||
self._selection_state = browsertab.SelectionState.none
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_entered(self, mode):
|
||||
if mode != usertypes.KeyMode.caret:
|
||||
return
|
||||
|
||||
self.selection_enabled = self._widget.hasSelection()
|
||||
self.selection_toggled.emit(self.selection_enabled)
|
||||
if self._widget.hasSelection():
|
||||
self._selection_state = browsertab.SelectionState.normal
|
||||
else:
|
||||
self._selection_state = browsertab.SelectionState.none
|
||||
self.selection_toggled.emit(self._selection_state)
|
||||
settings = self._widget.settings()
|
||||
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
|
||||
|
||||
|
|
@ -191,7 +210,7 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
#
|
||||
# Note: We can't use hasSelection() here, as that's always
|
||||
# true in caret mode.
|
||||
if not self.selection_enabled:
|
||||
if self._selection_state is browsertab.SelectionState.none:
|
||||
self._widget.page().currentFrame().evaluateJavaScript(
|
||||
utils.read_file('javascript/position_caret.js'))
|
||||
|
||||
|
|
@ -199,151 +218,189 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
def _on_mode_left(self, _mode):
|
||||
settings = self._widget.settings()
|
||||
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
|
||||
if self.selection_enabled and self._widget.hasSelection():
|
||||
if (self._selection_state is not browsertab.SelectionState.none and
|
||||
self._widget.hasSelection()):
|
||||
# Remove selection if it exists
|
||||
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
|
||||
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False)
|
||||
self.selection_enabled = False
|
||||
self._selection_state = browsertab.SelectionState.none
|
||||
|
||||
def move_to_next_line(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = QWebPage.MoveToNextLine
|
||||
else:
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = QWebPage.SelectNextLine
|
||||
else:
|
||||
act = QWebPage.MoveToNextLine
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
self._select_line_to_end()
|
||||
|
||||
def move_to_prev_line(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = QWebPage.MoveToPreviousLine
|
||||
else:
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = QWebPage.SelectPreviousLine
|
||||
else:
|
||||
act = QWebPage.MoveToPreviousLine
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
self._select_line_to_start()
|
||||
|
||||
def move_to_next_char(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = QWebPage.MoveToNextChar
|
||||
else:
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectNextChar
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToNextChar
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_prev_char(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = QWebPage.MoveToPreviousChar
|
||||
else:
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectPreviousChar
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToPreviousChar
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_end_of_word(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
if utils.is_windows: # pragma: no cover
|
||||
act.append(QWebPage.MoveToPreviousChar)
|
||||
else:
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = [QWebPage.SelectNextWord]
|
||||
if utils.is_windows: # pragma: no cover
|
||||
act.append(QWebPage.SelectPreviousChar)
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
if utils.is_windows: # pragma: no cover
|
||||
act.append(QWebPage.MoveToPreviousChar)
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
|
||||
def move_to_next_word(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
if not utils.is_windows: # pragma: no branch
|
||||
act.append(QWebPage.MoveToNextChar)
|
||||
else:
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = [QWebPage.SelectNextWord]
|
||||
if not utils.is_windows: # pragma: no branch
|
||||
act.append(QWebPage.SelectNextChar)
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = [QWebPage.MoveToNextWord]
|
||||
if not utils.is_windows: # pragma: no branch
|
||||
act.append(QWebPage.MoveToNextChar)
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
|
||||
def move_to_prev_word(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = QWebPage.MoveToPreviousWord
|
||||
else:
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectPreviousWord
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToPreviousWord
|
||||
for _ in range(count):
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_start_of_line(self):
|
||||
if not self.selection_enabled:
|
||||
act = QWebPage.MoveToStartOfLine
|
||||
else:
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectStartOfLine
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToStartOfLine
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_end_of_line(self):
|
||||
if not self.selection_enabled:
|
||||
act = QWebPage.MoveToEndOfLine
|
||||
else:
|
||||
if self._selection_state is browsertab.SelectionState.normal:
|
||||
act = QWebPage.SelectEndOfLine
|
||||
elif self._selection_state is browsertab.SelectionState.line:
|
||||
return
|
||||
else:
|
||||
act = QWebPage.MoveToEndOfLine
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def move_to_start_of_next_block(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = [QWebPage.MoveToNextLine,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
else:
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = [QWebPage.SelectNextLine,
|
||||
QWebPage.SelectStartOfBlock]
|
||||
else:
|
||||
act = [QWebPage.MoveToNextLine,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
self._select_line_to_end()
|
||||
|
||||
def move_to_start_of_prev_block(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = [QWebPage.MoveToPreviousLine,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
else:
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = [QWebPage.SelectPreviousLine,
|
||||
QWebPage.SelectStartOfBlock]
|
||||
else:
|
||||
act = [QWebPage.MoveToPreviousLine,
|
||||
QWebPage.MoveToStartOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
self._select_line_to_start()
|
||||
|
||||
def move_to_end_of_next_block(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = [QWebPage.MoveToNextLine,
|
||||
QWebPage.MoveToEndOfBlock]
|
||||
else:
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = [QWebPage.SelectNextLine,
|
||||
QWebPage.SelectEndOfBlock]
|
||||
else:
|
||||
act = [QWebPage.MoveToNextLine,
|
||||
QWebPage.MoveToEndOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
self._select_line_to_end()
|
||||
|
||||
def move_to_end_of_prev_block(self, count=1):
|
||||
if not self.selection_enabled:
|
||||
act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
|
||||
else:
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = [QWebPage.SelectPreviousLine, QWebPage.SelectEndOfBlock]
|
||||
else:
|
||||
act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
|
||||
for _ in range(count):
|
||||
for a in act:
|
||||
self._widget.triggerPageAction(a)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
self._select_line_to_start()
|
||||
|
||||
def move_to_start_of_document(self):
|
||||
if not self.selection_enabled:
|
||||
act = QWebPage.MoveToStartOfDocument
|
||||
else:
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = QWebPage.SelectStartOfDocument
|
||||
else:
|
||||
act = QWebPage.MoveToStartOfDocument
|
||||
self._widget.triggerPageAction(act)
|
||||
if self._selection_state is browsertab.SelectionState.line:
|
||||
self._select_line()
|
||||
|
||||
def move_to_end_of_document(self):
|
||||
if not self.selection_enabled:
|
||||
act = QWebPage.MoveToEndOfDocument
|
||||
else:
|
||||
if self._selection_state is not browsertab.SelectionState.none:
|
||||
act = QWebPage.SelectEndOfDocument
|
||||
else:
|
||||
act = QWebPage.MoveToEndOfDocument
|
||||
self._widget.triggerPageAction(act)
|
||||
|
||||
def toggle_selection(self):
|
||||
self.selection_enabled = not self.selection_enabled
|
||||
self.selection_toggled.emit(self.selection_enabled)
|
||||
def toggle_selection(self, line=False):
|
||||
if line:
|
||||
self._selection_state = browsertab.SelectionState.line
|
||||
self._select_line()
|
||||
self.reverse_selection()
|
||||
self._select_line()
|
||||
self.reverse_selection()
|
||||
elif self._selection_state is not browsertab.SelectionState.normal:
|
||||
self._selection_state = browsertab.SelectionState.normal
|
||||
else:
|
||||
self._selection_state = browsertab.SelectionState.none
|
||||
self.selection_toggled.emit(self._selection_state)
|
||||
|
||||
def drop_selection(self):
|
||||
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
|
||||
|
|
@ -360,6 +417,32 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
);
|
||||
}""")
|
||||
|
||||
def _select_line(self):
|
||||
self._widget.triggerPageAction(QWebPage.SelectStartOfLine)
|
||||
self.reverse_selection()
|
||||
self._widget.triggerPageAction(QWebPage.SelectEndOfLine)
|
||||
self.reverse_selection()
|
||||
|
||||
def _select_line_to_end(self):
|
||||
# direction of selection (if anchor is to the left or right
|
||||
# of focus) has to be checked before moving selection
|
||||
# to the end of line
|
||||
if self._js_selection_left_to_right():
|
||||
self._widget.triggerPageAction(QWebPage.SelectEndOfLine)
|
||||
|
||||
def _select_line_to_start(self):
|
||||
if not self._js_selection_left_to_right():
|
||||
self._widget.triggerPageAction(QWebPage.SelectStartOfLine)
|
||||
|
||||
def _js_selection_left_to_right(self):
|
||||
"""Return True iff the selection's direction is left to right."""
|
||||
return self._tab.private_api.run_js_sync("""
|
||||
var sel = window.getSelection();
|
||||
var position = sel.anchorNode.compareDocumentPosition(sel.focusNode);
|
||||
(!position && sel.anchorOffset < sel.focusOffset ||
|
||||
position === Node.DOCUMENT_POSITION_FOLLOWING);
|
||||
""")
|
||||
|
||||
def _follow_selected(self, *, tab=False):
|
||||
if QWebSettings.globalSettings().testAttribute(
|
||||
QWebSettings.JavascriptEnabled):
|
||||
|
|
@ -391,11 +474,11 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
|
||||
if selected_element is not None:
|
||||
try:
|
||||
url = selected_element.attrib['href']
|
||||
href = selected_element.attrib['href']
|
||||
except KeyError:
|
||||
raise browsertab.WebTabError('Anchor element without '
|
||||
'href!')
|
||||
url = self._tab.url().resolved(QUrl(url))
|
||||
url = self._tab.url().resolved(QUrl(href))
|
||||
if tab:
|
||||
self._tab.new_tab_requested.emit(url)
|
||||
else:
|
||||
|
|
@ -580,6 +663,10 @@ class WebKitElements(browsertab.AbstractElements):
|
|||
|
||||
"""QtWebKit implemementations related to elements on the page."""
|
||||
|
||||
def __init__(self, tab: 'WebKitTab') -> None:
|
||||
super().__init__()
|
||||
self._tab = tab
|
||||
|
||||
def find_css(self, selector, callback, error_cb, *, only_visible=False):
|
||||
utils.unused(error_cb)
|
||||
mainframe = self._widget.page().mainFrame()
|
||||
|
|
@ -691,13 +778,21 @@ class WebKitTabPrivate(browsertab.AbstractTabPrivate):
|
|||
def shutdown(self):
|
||||
self._widget.shutdown()
|
||||
|
||||
def run_js_sync(self, code):
|
||||
document_element = self._widget.page().mainFrame().documentElement()
|
||||
result = document_element.evaluateJavaScript(code)
|
||||
return result
|
||||
|
||||
|
||||
class WebKitTab(browsertab.AbstractTab):
|
||||
|
||||
"""A QtWebKit tab in the browser."""
|
||||
|
||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||
super().__init__(win_id=win_id, private=private, parent=parent)
|
||||
super().__init__(win_id=win_id,
|
||||
mode_manager=mode_manager,
|
||||
private=private,
|
||||
parent=parent)
|
||||
widget = webview.WebView(win_id=win_id, tab_id=self.tab_id,
|
||||
private=private, tab=self)
|
||||
if private:
|
||||
|
|
@ -749,8 +844,7 @@ class WebKitTab(browsertab.AbstractTab):
|
|||
def run_js_async(self, code, callback=None, *, world=None):
|
||||
if world is not None and world != usertypes.JsWorld.jseval:
|
||||
log.webview.warning("Ignoring world ID {}".format(world))
|
||||
document_element = self._widget.page().mainFrame().documentElement()
|
||||
result = document_element.evaluateJavaScript(code)
|
||||
result = self.private_api.run_js_sync(code)
|
||||
if callback is not None:
|
||||
callback(result)
|
||||
|
||||
|
|
@ -847,9 +941,9 @@ class WebKitTab(browsertab.AbstractTab):
|
|||
if navigation.is_main_frame:
|
||||
self.settings.update_for_url(navigation.url)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_ssl_errors(self):
|
||||
self._has_ssl_errors = True
|
||||
@pyqtSlot('QNetworkReply*')
|
||||
def _on_ssl_errors(self, reply):
|
||||
self._insecure_hosts.add(reply.url().host())
|
||||
|
||||
def _connect_signals(self):
|
||||
view = self._widget
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
import html
|
||||
import functools
|
||||
import typing
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
|
|
@ -77,22 +78,24 @@ class BrowserPage(QWebPage):
|
|||
self.setNetworkAccessManager(self._networkmanager)
|
||||
self.setForwardUnsupportedContent(True)
|
||||
self.reloading.connect(self._networkmanager.clear_rejected_ssl_errors)
|
||||
self.printRequested.connect( # type: ignore
|
||||
self.printRequested.connect( # type: ignore[attr-defined]
|
||||
self.on_print_requested)
|
||||
self.downloadRequested.connect( # type: ignore
|
||||
self.downloadRequested.connect( # type: ignore[attr-defined]
|
||||
self.on_download_requested)
|
||||
self.unsupportedContent.connect( # type: ignore
|
||||
self.unsupportedContent.connect( # type: ignore[attr-defined]
|
||||
self.on_unsupported_content)
|
||||
self.loadStarted.connect(self.on_load_started) # type: ignore
|
||||
self.featurePermissionRequested.connect( # type: ignore
|
||||
self.loadStarted.connect( # type: ignore[attr-defined]
|
||||
self.on_load_started)
|
||||
self.featurePermissionRequested.connect( # type: ignore[attr-defined]
|
||||
self._on_feature_permission_requested)
|
||||
self.saveFrameStateRequested.connect( # type: ignore
|
||||
self.saveFrameStateRequested.connect( # type: ignore[attr-defined]
|
||||
self.on_save_frame_state_requested)
|
||||
self.restoreFrameStateRequested.connect( # type: ignore
|
||||
self.restoreFrameStateRequested.connect( # type: ignore[attr-defined]
|
||||
self.on_restore_frame_state_requested)
|
||||
self.loadFinished.connect( # type: ignore
|
||||
self.loadFinished.connect( # type: ignore[attr-defined]
|
||||
functools.partial(self._inject_userjs, self.mainFrame()))
|
||||
self.frameCreated.connect(self._connect_userjs_signals) # type: ignore
|
||||
self.frameCreated.connect( # type: ignore[attr-defined]
|
||||
self._connect_userjs_signals)
|
||||
|
||||
@pyqtSlot('QWebFrame*')
|
||||
def _connect_userjs_signals(self, frame):
|
||||
|
|
@ -205,8 +208,10 @@ class BrowserPage(QWebPage):
|
|||
suggested_file = ""
|
||||
if info.suggestedFileNames:
|
||||
suggested_file = info.suggestedFileNames[0]
|
||||
|
||||
files.fileNames, _ = QFileDialog.getOpenFileNames(
|
||||
None, None, suggested_file) # type: ignore
|
||||
None, None, suggested_file) # type: ignore[arg-type]
|
||||
|
||||
return True
|
||||
|
||||
def shutdown(self):
|
||||
|
|
@ -348,11 +353,11 @@ class BrowserPage(QWebPage):
|
|||
self.setFeaturePermission, frame, feature,
|
||||
QWebPage.PermissionDeniedByUser)
|
||||
|
||||
url = frame.url().adjusted(QUrl.RemoveUserInfo |
|
||||
QUrl.RemovePath |
|
||||
QUrl.RemoveQuery |
|
||||
QUrl.RemoveFragment)
|
||||
|
||||
url = frame.url().adjusted(typing.cast(QUrl.FormattingOptions,
|
||||
QUrl.RemoveUserInfo |
|
||||
QUrl.RemovePath |
|
||||
QUrl.RemoveQuery |
|
||||
QUrl.RemoveFragment))
|
||||
question = shared.feature_permission(
|
||||
url=url,
|
||||
option=options[feature], msg=messages[feature],
|
||||
|
|
@ -411,6 +416,8 @@ class BrowserPage(QWebPage):
|
|||
|
||||
def userAgentForUrl(self, url):
|
||||
"""Override QWebPage::userAgentForUrl to customize the user agent."""
|
||||
if not url.isValid():
|
||||
url = None
|
||||
return websettings.user_agent(url)
|
||||
|
||||
def supportsExtension(self, ext):
|
||||
|
|
|
|||
|
|
@ -87,7 +87,8 @@ class WebView(QWebView):
|
|||
stylesheet.set_register(self)
|
||||
|
||||
def __repr__(self):
|
||||
urlstr = self.url().toDisplayString(QUrl.EncodeUnicode) # type: ignore
|
||||
flags = QUrl.EncodeUnicode
|
||||
urlstr = self.url().toDisplayString(flags) # type: ignore[arg-type]
|
||||
url = utils.elide(urlstr, 100)
|
||||
return utils.get_repr(self, tab_id=self._tab_id, url=url)
|
||||
|
||||
|
|
@ -97,7 +98,7 @@ class WebView(QWebView):
|
|||
# Copied from:
|
||||
# https://code.google.com/p/webscraping/source/browse/webkit.py#325
|
||||
try:
|
||||
self.setPage(None) # type: ignore
|
||||
self.setPage(None) # type: ignore[arg-type]
|
||||
except RuntimeError:
|
||||
# It seems sometimes Qt has already deleted the QWebView and we
|
||||
# get: RuntimeError: wrapped C/C++ object of type WebView has been
|
||||
|
|
@ -180,9 +181,9 @@ class WebView(QWebView):
|
|||
This is not needed for QtWebEngine, so it's in here.
|
||||
"""
|
||||
menu = self.page().createStandardContextMenu()
|
||||
self.shutting_down.connect(menu.close) # type: ignore
|
||||
self.shutting_down.connect(menu.close) # type: ignore[arg-type]
|
||||
mm = modeman.instance(self.win_id)
|
||||
mm.entered.connect(menu.close) # type: ignore
|
||||
mm.entered.connect(menu.close) # type: ignore[arg-type]
|
||||
menu.exec_(e.globalPos())
|
||||
|
||||
def showEvent(self, e):
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ class Command:
|
|||
args = self._param_to_argparse_args(param, is_bool)
|
||||
callsig = debug_utils.format_call(self.parser.add_argument, args,
|
||||
kwargs, full=False)
|
||||
log.commands.vdebug( # type: ignore
|
||||
log.commands.vdebug( # type: ignore[attr-defined]
|
||||
'Adding arg {} of type {} -> {}'
|
||||
.format(param.name, typ, callsig))
|
||||
self.parser.add_argument(*args, **kwargs)
|
||||
|
|
@ -334,8 +334,8 @@ class Command:
|
|||
Args:
|
||||
param: The inspect.Parameter to look at.
|
||||
"""
|
||||
arginfo = self.get_arg_info(param)
|
||||
if arginfo.value:
|
||||
arg_info = self.get_arg_info(param)
|
||||
if arg_info.value:
|
||||
# Filled values are passed 1:1
|
||||
return None
|
||||
elif param.kind in [inspect.Parameter.VAR_POSITIONAL,
|
||||
|
|
@ -409,7 +409,8 @@ class Command:
|
|||
if hasattr(typing, 'UnionMeta'):
|
||||
# Python 3.5.2
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
is_union = isinstance(typ, typing.UnionMeta) # type: ignore
|
||||
is_union = isinstance(
|
||||
typ, typing.UnionMeta) # type: ignore[attr-defined]
|
||||
else:
|
||||
is_union = getattr(typ, '__origin__', None) is typing.Union
|
||||
|
||||
|
|
@ -575,7 +576,7 @@ class Command:
|
|||
|
||||
def register(self):
|
||||
"""Register this command in objects.commands."""
|
||||
log.commands.vdebug( # type: ignore
|
||||
log.commands.vdebug( # type: ignore[attr-defined]
|
||||
"Registering command {} (from {}:{})".format(
|
||||
self.name, self.handler.__module__, self.handler.__qualname__))
|
||||
if self.name in objects.commands:
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ def _init_variable_replacements() -> typing.Mapping[str, _ReplacementFunction]:
|
|||
modified_key = '{' + key + '}'
|
||||
# x = modified_key is to avoid binding x as a closure
|
||||
replacements[modified_key] = (
|
||||
lambda _, x=modified_key: x) # type: ignore
|
||||
lambda _, x=modified_key: x) # type: ignore[misc]
|
||||
return replacements
|
||||
|
||||
|
||||
|
|
@ -332,7 +332,7 @@ class CommandRunner(AbstractCommandRunner):
|
|||
self._win_id = win_id
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _handle_error(self, safely) -> typing.Iterator[None]:
|
||||
def _handle_error(self, safely: bool) -> typing.Iterator[None]:
|
||||
"""Show exceptions as errors if safely=True is given."""
|
||||
try:
|
||||
yield
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ from qutebrowser.commands import runners
|
|||
from qutebrowser.config import websettings
|
||||
from qutebrowser.misc import guiprocess
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
class _QtFIFOReader(QObject):
|
||||
|
|
@ -59,8 +60,10 @@ 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(fd, QSocketNotifier.Read, self)
|
||||
self._notifier.activated.connect(self.read_line) # type: ignore
|
||||
self._notifier = QSocketNotifier(typing.cast(sip.voidptr, fd),
|
||||
QSocketNotifier.Read, self)
|
||||
self._notifier.activated.connect( # type: ignore[attr-defined]
|
||||
self.read_line)
|
||||
|
||||
@pyqtSlot()
|
||||
def read_line(self):
|
||||
|
|
@ -148,7 +151,8 @@ class _BaseUserscriptRunner(QObject):
|
|||
log.procs.debug("Both text/HTML stored, kicking off userscript!")
|
||||
self._run_process(*self._args, **self._kwargs)
|
||||
|
||||
def _run_process(self, cmd, *args, env=None, verbose=False):
|
||||
def _run_process(self, cmd, *args, env=None, verbose=False,
|
||||
output_messages=False):
|
||||
"""Start the given command.
|
||||
|
||||
Args:
|
||||
|
|
@ -156,15 +160,16 @@ class _BaseUserscriptRunner(QObject):
|
|||
*args: The arguments to hand to the command
|
||||
env: A dictionary of environment variables to add.
|
||||
verbose: Show notifications when the command started/exited.
|
||||
output_messages: Show the output as messages.
|
||||
"""
|
||||
assert self._filepath is not None
|
||||
self._env['QUTE_FIFO'] = self._filepath
|
||||
if env is not None:
|
||||
self._env.update(env)
|
||||
|
||||
self._proc = guiprocess.GUIProcess('userscript',
|
||||
additional_env=self._env,
|
||||
verbose=verbose, parent=self)
|
||||
self._proc = guiprocess.GUIProcess(
|
||||
'userscript', additional_env=self._env,
|
||||
output_messages=output_messages, verbose=verbose, parent=self)
|
||||
self._proc.finished.connect(self.on_proc_finished)
|
||||
self._proc.error.connect(self.on_proc_error)
|
||||
self._proc.start(cmd, args)
|
||||
|
|
@ -254,14 +259,15 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
|
|||
self._filepath = tempfile.mktemp(prefix='qutebrowser-userscript-',
|
||||
dir=standarddir.runtime())
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
os.mkfifo(self._filepath)
|
||||
os.mkfifo(self._filepath, mode=0o600)
|
||||
# pylint: enable=no-member,useless-suppression
|
||||
except OSError as e:
|
||||
self._filepath = None # Make sure it's not used
|
||||
message.error("Error while creating FIFO: {}".format(e))
|
||||
return
|
||||
|
||||
self._reader = _QtFIFOReader(self._filepath)
|
||||
self._reader.got_line.connect(self.got_cmd) # type: ignore
|
||||
self._reader.got_line.connect(self.got_cmd) # type: ignore[arg-type]
|
||||
|
||||
@pyqtSlot()
|
||||
def on_proc_finished(self):
|
||||
|
|
@ -398,7 +404,8 @@ def _lookup_path(cmd):
|
|||
raise NotFoundError(cmd, directories)
|
||||
|
||||
|
||||
def run_async(tab, cmd, *args, win_id, env, verbose=False):
|
||||
def run_async(tab, cmd, *args, win_id, env, verbose=False,
|
||||
output_messages=False):
|
||||
"""Run a userscript after dumping page html/source.
|
||||
|
||||
Raises:
|
||||
|
|
@ -413,15 +420,15 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
|
|||
win_id: The window id the userscript is executed in.
|
||||
env: A dictionary of variables to add to the process environment.
|
||||
verbose: Show notifications when the command started/exited.
|
||||
output_messages: Show the output as messages.
|
||||
"""
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
commandrunner = runners.CommandRunner(win_id, parent=tabbed_browser)
|
||||
tb = objreg.get('tabbed-browser', scope='window', window=win_id)
|
||||
commandrunner = runners.CommandRunner(win_id, parent=tb)
|
||||
|
||||
if utils.is_posix:
|
||||
runner = _POSIXUserscriptRunner(tabbed_browser)
|
||||
runner = _POSIXUserscriptRunner(tb) # type: _BaseUserscriptRunner
|
||||
elif utils.is_windows: # pragma: no cover
|
||||
runner = _WindowsUserscriptRunner(tabbed_browser)
|
||||
runner = _WindowsUserscriptRunner(tb)
|
||||
else: # pragma: no cover
|
||||
raise UnsupportedError
|
||||
|
||||
|
|
@ -451,7 +458,8 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
|
|||
runner.finished.connect(commandrunner.deleteLater)
|
||||
runner.finished.connect(runner.deleteLater)
|
||||
|
||||
runner.prepare_run(cmd_path, *args, env=env, verbose=verbose)
|
||||
runner.prepare_run(cmd_path, *args, env=env, verbose=verbose,
|
||||
output_messages=output_messages)
|
||||
tab.dump_async(runner.store_html)
|
||||
tab.dump_async(runner.store_text, plain=True)
|
||||
return runner
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||
size = self._style.sizeFromContents(QStyle.CT_ItemViewItem, self._opt,
|
||||
docsize, self._opt.widget)
|
||||
qtutils.ensure_valid(size)
|
||||
return size + QSize(10, 3)
|
||||
return size + QSize(10, 3) # type: ignore[operator]
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
"""Override the QStyledItemDelegate paint function.
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ class CompletionView(QTreeView):
|
|||
|
||||
selmodel.setCurrentIndex(
|
||||
idx,
|
||||
QItemSelectionModel.ClearAndSelect | # type: ignore
|
||||
QItemSelectionModel.ClearAndSelect | # type: ignore[arg-type]
|
||||
QItemSelectionModel.Rows)
|
||||
|
||||
# if the last item is focused, try to fetch more
|
||||
|
|
@ -424,4 +424,8 @@ class CompletionView(QTreeView):
|
|||
if not index.isValid():
|
||||
raise cmdutils.CommandError("No item selected!")
|
||||
text = self.model().data(index)
|
||||
|
||||
if not utils.supports_selection():
|
||||
sel = False
|
||||
|
||||
utils.set_clipboard(text, selection=sel)
|
||||
|
|
|
|||
|
|
@ -183,11 +183,13 @@ class CompletionModel(QAbstractItemModel):
|
|||
# WORKAROUND:
|
||||
# layoutChanged is broken in PyQt 5.7.1, so we must use metaObject
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2017-January/038483.html
|
||||
self.metaObject().invokeMethod(self, # type: ignore
|
||||
"layoutAboutToBeChanged")
|
||||
meta = self.metaObject()
|
||||
meta.invokeMethod(self, # type: ignore[misc, call-overload]
|
||||
"layoutAboutToBeChanged")
|
||||
for cat in self._categories:
|
||||
cat.set_pattern(pattern)
|
||||
self.metaObject().invokeMethod(self, "layoutChanged") # type: ignore
|
||||
meta.invokeMethod(self, # type: ignore[misc, call-overload]
|
||||
"layoutChanged")
|
||||
|
||||
def first_item(self):
|
||||
"""Return the index of the first child (non-category) in the model."""
|
||||
|
|
|
|||
|
|
@ -97,11 +97,13 @@ def session(*, info=None): # pylint: disable=unused-argument
|
|||
return model
|
||||
|
||||
|
||||
def _buffer(skip_win_id=None):
|
||||
def _buffer(*, win_id_filter=lambda _win_id: True, add_win_id=True):
|
||||
"""Helper to get the completion model for buffer/other_buffer.
|
||||
|
||||
Args:
|
||||
skip_win_id: The id of the window to skip, or None to include all.
|
||||
win_id_filter: A filter function for window IDs to include.
|
||||
Should return True for all included windows.
|
||||
add_win_id: Whether to add the window ID to the completion items.
|
||||
"""
|
||||
def delete_buffer(data):
|
||||
"""Close the selected tab."""
|
||||
|
|
@ -117,8 +119,9 @@ def _buffer(skip_win_id=None):
|
|||
windows = [] # type: typing.List[typing.Tuple[str, str, str]]
|
||||
|
||||
for win_id in objreg.window_registry:
|
||||
if skip_win_id is not None and win_id == skip_win_id:
|
||||
if not win_id_filter(win_id):
|
||||
continue
|
||||
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
if tabbed_browser.shutting_down:
|
||||
|
|
@ -126,14 +129,18 @@ def _buffer(skip_win_id=None):
|
|||
tabs = [] # type: typing.List[typing.Tuple[str, str, str]]
|
||||
for idx in range(tabbed_browser.widget.count()):
|
||||
tab = tabbed_browser.widget.widget(idx)
|
||||
tabs.append(("{}/{}".format(win_id, idx + 1),
|
||||
tab_str = ("{}/{}".format(win_id, idx + 1) if add_win_id
|
||||
else str(idx + 1))
|
||||
tabs.append((tab_str,
|
||||
tab.url().toDisplayString(),
|
||||
tabbed_browser.widget.page_title(idx)))
|
||||
|
||||
if tabs_are_windows:
|
||||
windows += tabs
|
||||
else:
|
||||
title = str(win_id) if add_win_id else "Tabs"
|
||||
cat = listcategory.ListCategory(
|
||||
str(win_id), tabs, delete_func=delete_buffer, sort=False)
|
||||
title, tabs, delete_func=delete_buffer, sort=False)
|
||||
model.add_category(cat)
|
||||
|
||||
if tabs_are_windows:
|
||||
|
|
@ -157,7 +164,22 @@ def other_buffer(*, info):
|
|||
|
||||
Used for the tab-take command.
|
||||
"""
|
||||
return _buffer(skip_win_id=info.win_id)
|
||||
return _buffer(win_id_filter=lambda win_id: win_id != info.win_id)
|
||||
|
||||
|
||||
def tab_focus(*, info):
|
||||
"""A model to complete on open tabs in the current window."""
|
||||
model = _buffer(win_id_filter=lambda win_id: win_id == info.win_id,
|
||||
add_win_id=False)
|
||||
|
||||
special = [
|
||||
("last", "Focus the last-focused tab"),
|
||||
("stack-next", "Go forward through a stack of focused tabs"),
|
||||
("stack-prev", "Go backward through a stack of focused tabs"),
|
||||
]
|
||||
model.add_category(listcategory.ListCategory("Special", special))
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def window(*, info):
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@
|
|||
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from PyQt5.QtCore import QAbstractItemModel
|
||||
|
||||
from qutebrowser.completion.models import (completionmodel, listcategory,
|
||||
histcategory)
|
||||
from qutebrowser.browser import history
|
||||
|
|
@ -74,7 +77,7 @@ def url(*, info):
|
|||
if k != 'DEFAULT']
|
||||
# pylint: enable=bad-config-option
|
||||
categories = config.val.completion.open_categories
|
||||
models = {}
|
||||
models = {} # type: typing.Dict[str, QAbstractItemModel]
|
||||
|
||||
if searchengines and 'searchengines' in categories:
|
||||
models['searchengines'] = listcategory.ListCategory(
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ from qutebrowser.api import (cmdutils, hook, config, message, downloads,
|
|||
interceptor, apitypes, qtutils)
|
||||
|
||||
|
||||
logger = logging.getLogger('misc')
|
||||
logger = logging.getLogger('network')
|
||||
_host_blocker = typing.cast('HostBlocker', None)
|
||||
|
||||
|
||||
|
|
@ -139,8 +139,8 @@ class HostBlocker:
|
|||
"""Block the given request if necessary."""
|
||||
if self._is_blocked(request_url=info.request_url,
|
||||
first_party_url=info.first_party_url):
|
||||
logger.info("Request to {} blocked by host blocker."
|
||||
.format(info.request_url.host()))
|
||||
logger.debug("Request to {} blocked by host blocker."
|
||||
.format(info.request_url.host()))
|
||||
info.block()
|
||||
|
||||
def _read_hosts_line(self, raw_line: bytes) -> typing.Set[str]:
|
||||
|
|
|
|||
|
|
@ -185,9 +185,13 @@ def move_to_end_of_document(tab: apitypes.Tab) -> None:
|
|||
|
||||
@cmdutils.register(modes=[cmdutils.KeyMode.caret])
|
||||
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
|
||||
def toggle_selection(tab: apitypes.Tab) -> None:
|
||||
"""Toggle caret selection mode."""
|
||||
tab.caret.toggle_selection()
|
||||
def toggle_selection(tab: apitypes.Tab, line: bool = False) -> None:
|
||||
"""Toggle caret selection mode.
|
||||
|
||||
Args:
|
||||
line: Enables line-selection.
|
||||
"""
|
||||
tab.caret.toggle_selection(line)
|
||||
|
||||
|
||||
@cmdutils.register(modes=[cmdutils.KeyMode.caret])
|
||||
|
|
|
|||
|
|
@ -71,8 +71,10 @@ def _print_preview(tab: apitypes.Tab) -> None:
|
|||
tab.printing.check_preview_support()
|
||||
diag = QPrintPreviewDialog(tab)
|
||||
diag.setAttribute(Qt.WA_DeleteOnClose)
|
||||
diag.setWindowFlags(diag.windowFlags() | Qt.WindowMaximizeButtonHint |
|
||||
Qt.WindowMinimizeButtonHint)
|
||||
diag.setWindowFlags(
|
||||
diag.windowFlags() | # type: ignore[operator, arg-type]
|
||||
Qt.WindowMaximizeButtonHint |
|
||||
Qt.WindowMinimizeButtonHint)
|
||||
diag.paintRequested.connect(functools.partial(
|
||||
tab.printing.to_printer, callback=print_callback))
|
||||
diag.exec_()
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ class KeyConfig:
|
|||
'mode'.format(key, mode))
|
||||
|
||||
self._validate(key, mode)
|
||||
log.keyboard.vdebug( # type: ignore
|
||||
log.keyboard.vdebug( # type: ignore[attr-defined]
|
||||
"Adding binding {} -> {} in mode {}.".format(key, command, mode))
|
||||
|
||||
bindings = self._config.get_mutable_obj('bindings.commands')
|
||||
|
|
|
|||
|
|
@ -261,6 +261,23 @@ class ConfigCommands:
|
|||
with self._handle_config_error():
|
||||
self._config.unset(option, save_yaml=not temp)
|
||||
|
||||
@cmdutils.register(instance='config-commands')
|
||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||
def config_diff(self, win_id: int, old: bool = False) -> None:
|
||||
"""Show all customized options.
|
||||
|
||||
Args:
|
||||
old: Show difference for the pre-v1.0 files
|
||||
(qutebrowser.conf/keys.conf).
|
||||
"""
|
||||
url = QUrl('qute://configdiff')
|
||||
if old:
|
||||
url.setPath('/old')
|
||||
|
||||
tabbed_browser = objreg.get('tabbed-browser',
|
||||
scope='window', window=win_id)
|
||||
tabbed_browser.load_url(url, newtab=False)
|
||||
|
||||
@cmdutils.register(instance='config-commands')
|
||||
@cmdutils.argument('option', completion=configmodel.list_option)
|
||||
def config_list_add(self, option: str, value: str,
|
||||
|
|
@ -443,9 +460,9 @@ class ConfigCommands:
|
|||
if filename is None:
|
||||
filename = standarddir.config_py()
|
||||
else:
|
||||
filename = os.path.expanduser(filename)
|
||||
if not os.path.isabs(filename):
|
||||
filename = os.path.join(standarddir.config(), filename)
|
||||
filename = os.path.expanduser(filename)
|
||||
|
||||
if os.path.exists(filename) and not force:
|
||||
raise cmdutils.CommandError("{} already exists - use --force to "
|
||||
|
|
|
|||
|
|
@ -48,6 +48,16 @@ search.incremental:
|
|||
default: True
|
||||
desc: Find text on a page incrementally, renewing the search for each typed character.
|
||||
|
||||
search.wrap:
|
||||
type: Bool
|
||||
default: True
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: true
|
||||
desc: >-
|
||||
Wrap around at the top and bottom of the page when advancing through text matches
|
||||
using `:search-next` and `:search-prev`.
|
||||
|
||||
new_instance_open_target:
|
||||
type:
|
||||
name: String
|
||||
|
|
@ -341,6 +351,7 @@ content.cookies.accept:
|
|||
backend:
|
||||
QtWebKit: true
|
||||
QtWebEngine: Qt 5.11
|
||||
supports_pattern: true
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
|
|
@ -351,7 +362,16 @@ content.cookies.accept:
|
|||
a cookie is already set for the domain. On QtWebEngine, this is the
|
||||
same as no-3rdparty."
|
||||
- never: "Don't accept cookies at all."
|
||||
desc: Which cookies to accept.
|
||||
desc: >-
|
||||
Which cookies to accept.
|
||||
|
||||
With QtWebEngine, this setting also controls other features with tracking
|
||||
capabilities similar to those of cookies; including IndexedDB, DOM storage,
|
||||
filesystem API, service workers, and AppCache.
|
||||
|
||||
Note that with QtWebKit, only `all` and `never` are supported as per-domain
|
||||
values. Setting `no-3rdparty` or `no-unknown-3rdparty` per-domain on
|
||||
QtWebKit will have the same effect as `all`.
|
||||
|
||||
content.cookies.store:
|
||||
default: true
|
||||
|
|
@ -370,12 +390,46 @@ content.default_encoding:
|
|||
The encoding must be a string describing an encoding such as _utf-8_,
|
||||
_iso-8859-1_, etc.
|
||||
|
||||
content.unknown_url_scheme_policy:
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- disallow: "Disallows all navigation requests to URLs with unknown
|
||||
schemes."
|
||||
- allow-from-user-interaction: "Allows navigation requests to URLs with
|
||||
unknown schemes that are issued from user-interaction (like a
|
||||
mouse-click), whereas other navigation requests (for example from
|
||||
JavaScript) are suppressed."
|
||||
- allow-all: "Allows all navigation requests to URLs with unknown
|
||||
schemes."
|
||||
default: allow-from-user-interaction
|
||||
backend:
|
||||
QtWebEngine: Qt 5.11
|
||||
QtWebKit: false
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
How navigation requests to URLs with unknown schemes are handled.
|
||||
|
||||
content.windowed_fullscreen:
|
||||
renamed: content.fullscreen.window
|
||||
|
||||
content.fullscreen.window:
|
||||
type: Bool
|
||||
default: false
|
||||
desc: >-
|
||||
Limit fullscreen to the browser window (does not expand to fill the screen).
|
||||
|
||||
content.fullscreen.overlay_timeout:
|
||||
type:
|
||||
name: Int
|
||||
minval: 0
|
||||
maxval: maxint
|
||||
default: 3000
|
||||
desc: >-
|
||||
Set fullscreen notification overlay timeout in milliseconds.
|
||||
|
||||
If set to 0, no overlay will be displayed.
|
||||
|
||||
content.desktop_capture:
|
||||
type: BoolAsk
|
||||
default: ask
|
||||
|
|
@ -390,7 +444,7 @@ content.developer_extras:
|
|||
deleted: true
|
||||
|
||||
content.dns_prefetch:
|
||||
default: false
|
||||
default: true
|
||||
type: Bool
|
||||
backend:
|
||||
QtWebKit: true
|
||||
|
|
@ -508,11 +562,11 @@ content.headers.user_agent:
|
|||
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/79.0.3945.117 Safari/537.36"
|
||||
- Chrome 79 Win10
|
||||
like Gecko) Chrome/81.0.4044.129 Safari/537.36"
|
||||
- Chrome 80 Win10
|
||||
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
|
||||
Gecko) Chrome/79.0.3945.117 Safari/537.36"
|
||||
- Chrome 79 Linux
|
||||
Gecko) Chrome/81.0.4044.138 Safari/537.36"
|
||||
- Chrome 80 Linux
|
||||
supports_pattern: true
|
||||
desc: |
|
||||
User agent to send.
|
||||
|
|
@ -531,7 +585,9 @@ content.headers.user_agent:
|
|||
The default value is equal to the unchanged user agent of
|
||||
QtWebKit/QtWebEngine.
|
||||
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
Note that the value read from JavaScript is always the global value. With
|
||||
QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed
|
||||
to JavaScript requires a restart.
|
||||
|
||||
content.host_blocking.enabled:
|
||||
renamed: content.blocking.hosts.enabled
|
||||
|
|
@ -1120,6 +1176,22 @@ hints.border:
|
|||
type: String
|
||||
desc: CSS border value for hints.
|
||||
|
||||
hints.padding:
|
||||
default:
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 3
|
||||
right: 3
|
||||
type: Padding
|
||||
desc: Padding (in pixels) for hints.
|
||||
|
||||
hints.radius:
|
||||
default: 3
|
||||
type:
|
||||
name: Int
|
||||
minval: 0
|
||||
desc: Rounding radius (in pixels) for the edges of hints.
|
||||
|
||||
hints.chars:
|
||||
default: asdfghjkl
|
||||
type:
|
||||
|
|
@ -1338,6 +1410,19 @@ input.links_included_in_focus_chain:
|
|||
supports_pattern: true
|
||||
desc: Include hyperlinks in the keyboard focus chain when tabbing.
|
||||
|
||||
input.mouse.back_forward_buttons:
|
||||
default: true
|
||||
type: Bool
|
||||
desc: Enable back and forward buttons on the mouse.
|
||||
|
||||
input.mouse.rocker_gestures:
|
||||
default: false
|
||||
type: Bool
|
||||
desc: >-
|
||||
Enable Opera-like mouse rocker gestures.
|
||||
|
||||
This disables the context menu.
|
||||
|
||||
input.partial_timeout:
|
||||
default: 5000
|
||||
type:
|
||||
|
|
@ -1351,12 +1436,7 @@ input.partial_timeout:
|
|||
cleared after this time.
|
||||
|
||||
input.rocker_gestures:
|
||||
default: false
|
||||
type: Bool
|
||||
desc: >-
|
||||
Enable Opera-like mouse rocker gestures.
|
||||
|
||||
This disables the context menu.
|
||||
renamed: input.mouse.rocker_gestures
|
||||
|
||||
input.spatial_navigation:
|
||||
default: false
|
||||
|
|
@ -1423,7 +1503,7 @@ messages.unfocused:
|
|||
prompt.filebrowser:
|
||||
type: Bool
|
||||
default: true
|
||||
desc: Show a filebrowser in upload/download prompts.
|
||||
desc: Show a filebrowser in download prompts.
|
||||
|
||||
prompt.radius:
|
||||
type:
|
||||
|
|
@ -1442,8 +1522,12 @@ scrolling.bar:
|
|||
- never: Never show the scrollbar.
|
||||
- when-searching: Show the scrollbar when searching for text in the
|
||||
webpage. With the QtWebKit backend, this is equal to `never`.
|
||||
default: when-searching
|
||||
desc: When to show the scrollbar.
|
||||
- overlay: Show an overlay scrollbar. With Qt < 5.11 or on macOS, this is
|
||||
unavailable and equal to `when-searching`; with the QtWebKit
|
||||
backend, this is equal to `never`. Enabling/disabling overlay
|
||||
scrollbars requires a restart.
|
||||
default: overlay
|
||||
desc: When/how to show the scrollbar.
|
||||
|
||||
scrolling.smooth:
|
||||
type: Bool
|
||||
|
|
@ -1518,10 +1602,15 @@ spellcheck.languages:
|
|||
|
||||
## statusbar
|
||||
|
||||
statusbar.hide:
|
||||
type: Bool
|
||||
default: false
|
||||
desc: Hide the statusbar unless a message is shown.
|
||||
statusbar.show:
|
||||
default: always
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- always: Always show the statusbar.
|
||||
- never: Always hide the statusbar.
|
||||
- in-mode: Show the statusbar when in modes other than normal mode.
|
||||
desc: When to show the statusbar.
|
||||
|
||||
statusbar.padding:
|
||||
type: Padding
|
||||
|
|
@ -1912,17 +2001,29 @@ url.searchengines:
|
|||
name: String
|
||||
forbidden: ' '
|
||||
valtype: SearchEngineUrl
|
||||
desc: >-
|
||||
desc: |
|
||||
Search engines which can be used via the address bar.
|
||||
|
||||
Maps a search engine name (such as `DEFAULT`, or `ddg`) to a URL with a
|
||||
`{}` placeholder. The placeholder will be replaced by the search term, use
|
||||
`{{` and `}}` for literal `{`/`}` signs.
|
||||
`{{` and `}}` for literal `{`/`}` braces.
|
||||
|
||||
The following further placeholds are defined to configure how special
|
||||
characters in the search terms are replaced by safe characters (called
|
||||
'quoting'):
|
||||
|
||||
* `{}` and `{semiquoted}` quote everything except slashes; this is the most
|
||||
sensible choice for almost all search engines (for the search term
|
||||
`slash/and&` this placeholder expands to `slash/and%26amp`).
|
||||
* `{quoted}` quotes all characters (for `slash/and&` this placeholder
|
||||
expands to `slash%2Fand%26amp`).
|
||||
* `{unquoted}` quotes nothing (for `slash/and&` this placeholder
|
||||
expands to `slash/and&`).
|
||||
|
||||
The search engine named `DEFAULT` is used when `url.auto_search` is turned
|
||||
on and something else than a URL was entered to be opened. Other search
|
||||
engines can be used by prepending the search engine name to the search
|
||||
term, e.g. `:open google qutebrowser`.
|
||||
term, e.g. `:open google qutebrowser`.
|
||||
|
||||
url.start_pages:
|
||||
type:
|
||||
|
|
@ -2155,6 +2256,26 @@ colors.contextmenu.selected.fg:
|
|||
|
||||
If set to null, the Qt default is used.
|
||||
|
||||
colors.contextmenu.disabled.bg:
|
||||
type:
|
||||
name: QssColor
|
||||
none_ok: true
|
||||
default: null
|
||||
desc: >-
|
||||
Background color of disabled items in the context menu.
|
||||
|
||||
If set to null, the Qt default is used.
|
||||
|
||||
colors.contextmenu.disabled.fg:
|
||||
type:
|
||||
name: QssColor
|
||||
none_ok: true
|
||||
default: null
|
||||
desc: >-
|
||||
Foreground color of disabled items in the context menu.
|
||||
|
||||
If set to null, the Qt default is used.
|
||||
|
||||
colors.downloads.bar.bg:
|
||||
default: black
|
||||
type: QssColor
|
||||
|
|
@ -2539,6 +2660,169 @@ colors.webpage.prefers_color_scheme_dark:
|
|||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
## dark mode
|
||||
|
||||
colors.webpage.darkmode.enabled:
|
||||
default: false
|
||||
type: Bool
|
||||
desc: >-
|
||||
Render all web contents using a dark theme.
|
||||
|
||||
Example configurations from Chromium's `chrome://flags`:
|
||||
|
||||
|
||||
- "With simple HSL/CIELAB/RGB-based inversion": Set
|
||||
`colors.webpage.darkmode.algorithm` accordingly.
|
||||
|
||||
- "With selective image inversion": Set
|
||||
`colors.webpage.darkmode.policy.images` to `smart`.
|
||||
|
||||
- "With selective inversion of non-image elements": Set
|
||||
`colors.webpage.darkmode.threshold.text` to 150 and
|
||||
`colors.webpage.darkmode.threshold.background` to 205.
|
||||
|
||||
- "With selective inversion of everything": Combines the two variants
|
||||
above.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.algorithm:
|
||||
default: lightness-cielab
|
||||
desc: "Which algorithm to use for modifying how colors are rendered with
|
||||
darkmode."
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- lightness-cielab: Modify colors by converting them to CIELAB color
|
||||
space and inverting the L value.
|
||||
- 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.
|
||||
# kSimpleInvertForTesting is not exposed, as it's equivalent to
|
||||
# kInvertBrightness without gamma correction, and only available for
|
||||
# Chromium's automated tests
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.contrast:
|
||||
default: 0.0
|
||||
type:
|
||||
name: Float
|
||||
minval: -1.0
|
||||
maxval: 1.0
|
||||
desc: >-
|
||||
Contrast for dark mode.
|
||||
|
||||
This only has an effect when `colors.webpage.darkmode.algorithm` is set to
|
||||
`lightness-hsl` or `brightness-rgb`.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.policy.images:
|
||||
default: never
|
||||
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.
|
||||
desc: >-
|
||||
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].
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.policy.page:
|
||||
default: smart
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- always: Apply dark mode filter to all frames, regardless of content.
|
||||
- smart: Apply dark mode filter to frames based on background color.
|
||||
desc: Which pages to apply dark mode to.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.threshold.text:
|
||||
default: 256
|
||||
type:
|
||||
name: Int
|
||||
minval: 0
|
||||
maxval: 256
|
||||
desc: >-
|
||||
Threshold for inverting text with dark mode.
|
||||
|
||||
Text colors with brightness below this threshold will be inverted, and
|
||||
above it will be left as in the original, non-dark-mode page. Set to 256
|
||||
to always invert text color or to 0 to never invert text color.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.threshold.background:
|
||||
default: 0
|
||||
type:
|
||||
name: Int
|
||||
minval: 0
|
||||
maxval: 256
|
||||
desc: >-
|
||||
Threshold for inverting background elements with dark mode.
|
||||
|
||||
Background elements with brightness above this threshold will be inverted,
|
||||
and below it will be left as in the original, non-dark-mode page. Set to
|
||||
256 to never invert the color or to 0 to always invert it.
|
||||
|
||||
Note: This behavior is the opposite of
|
||||
`colors.webpage.darkmode.threshold.text`!
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.grayscale.all:
|
||||
default: false
|
||||
type: Bool
|
||||
desc: >-
|
||||
Render all colors as grayscale.
|
||||
|
||||
This only has an effect when `colors.webpage.darkmode.algorithm` is set to
|
||||
`lightness-hsl` or `brightness-rgb`.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
colors.webpage.darkmode.grayscale.images:
|
||||
default: 0.0
|
||||
type:
|
||||
name: Float
|
||||
minval: 0.0
|
||||
maxval: 1.0
|
||||
desc: >-
|
||||
Desaturation factor for images in dark mode.
|
||||
|
||||
If set to 0, images are left as-is. If set to 1, images are completely
|
||||
grayscale. Values between 0 and 1 desaturate the colors accordingly.
|
||||
restart: true
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
|
||||
# emacs: '
|
||||
|
||||
## fonts
|
||||
|
|
@ -2593,7 +2877,7 @@ fonts.contextmenu:
|
|||
|
||||
fonts.debug_console:
|
||||
default: default_size default_family
|
||||
type: QtFont
|
||||
type: Font
|
||||
desc: Font used for the debugging console.
|
||||
|
||||
fonts.downloads:
|
||||
|
|
@ -2636,10 +2920,15 @@ fonts.statusbar:
|
|||
type: Font
|
||||
desc: Font used in the statusbar.
|
||||
|
||||
fonts.tabs:
|
||||
fonts.tabs.selected:
|
||||
default: default_size default_family
|
||||
type: QtFont
|
||||
desc: Font used in the tab bar.
|
||||
type: Font
|
||||
desc: Font used for selected tabs.
|
||||
|
||||
fonts.tabs.unselected:
|
||||
default: default_size default_family
|
||||
type: Font
|
||||
desc: Font used for unselected tabs.
|
||||
|
||||
fonts.web.family.standard:
|
||||
default: ''
|
||||
|
|
@ -2730,6 +3019,7 @@ bindings.key_mappings:
|
|||
<Ctrl-6>: <Ctrl-^>
|
||||
<Ctrl-M>: <Return>
|
||||
<Ctrl-J>: <Return>
|
||||
<Ctrl-I>: <Tab>
|
||||
<Shift-Return>: <Return>
|
||||
<Enter>: <Return>
|
||||
<Shift-Enter>: <Return>
|
||||
|
|
@ -2827,6 +3117,7 @@ bindings.default:
|
|||
N: search-prev
|
||||
i: enter-mode insert
|
||||
v: enter-mode caret
|
||||
V: enter-mode caret ;; toggle-selection --line
|
||||
"`": enter-mode set_mark
|
||||
"'": enter-mode jump_mark
|
||||
yy: yank
|
||||
|
|
@ -2930,6 +3221,12 @@ bindings.default:
|
|||
tIH: config-cycle -p -u *://*.{url:host}/* content.images ;; reload
|
||||
tiu: config-cycle -p -t -u {url} content.images ;; reload
|
||||
tIu: config-cycle -p -u {url} content.images ;; reload
|
||||
tch: config-cycle -p -t -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
|
||||
tCh: config-cycle -p -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
|
||||
tcH: config-cycle -p -t -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
|
||||
tCH: config-cycle -p -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
|
||||
tcu: config-cycle -p -t -u {url} content.cookies.accept all no-3rdparty never ;; reload
|
||||
tCu: config-cycle -p -u {url} content.cookies.accept all no-3rdparty never ;; reload
|
||||
insert:
|
||||
<Ctrl-E>: open-editor
|
||||
<Shift-Ins>: insert-text -- {primary}
|
||||
|
|
@ -3008,6 +3305,7 @@ bindings.default:
|
|||
<Escape>: leave-mode
|
||||
caret:
|
||||
v: toggle-selection
|
||||
V: toggle-selection --line
|
||||
<Space>: toggle-selection
|
||||
<Ctrl-Space>: drop-selection
|
||||
c: enter-mode normal
|
||||
|
|
@ -3145,3 +3443,18 @@ bindings.commands:
|
|||
|
||||
* register: Entered when qutebrowser is waiting for a register name/key for
|
||||
commands like `:set-mark`.
|
||||
|
||||
## logging
|
||||
|
||||
logging.level.ram:
|
||||
default: debug
|
||||
type: LogLevel
|
||||
desc:
|
||||
Level for in-memory logs.
|
||||
|
||||
logging.level.console:
|
||||
default: info
|
||||
type: LogLevel
|
||||
desc: >-
|
||||
Level for console (stdout/stderr) logs.
|
||||
Ignored if the `--loglevel` or `--debug` CLI flags are used.
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue