Merge branch 'master' into more-sophisticated-adblock
This commit is contained in:
commit
317af23593
|
|
@ -13,7 +13,7 @@ jobs:
|
||||||
linters:
|
linters:
|
||||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -100,45 +100,34 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
### PyQt 5.9 (Python 3.6)
|
### PyQt 5.12 (Python 3.6)
|
||||||
- testenv: py36-pyqt59
|
- testenv: py36-pyqt512
|
||||||
os: ubuntu-18.04
|
|
||||||
python: 3.6
|
|
||||||
### PyQt 5.10 (Python 3.6)
|
|
||||||
- testenv: py36-pyqt510
|
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
python: 3.6
|
python: 3.6
|
||||||
### PyQt 5.11 (Python 3.7)
|
### PyQt 5.13 (Python 3.7)
|
||||||
- testenv: py37-pyqt511
|
- testenv: py37-pyqt513
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
python: 3.7
|
python: 3.7
|
||||||
### PyQt 5.12 (Python 3.8)
|
|
||||||
- testenv: py38-pyqt512
|
|
||||||
os: ubuntu-20.04
|
|
||||||
python: 3.8
|
|
||||||
### PyQt 5.13 (Python 3.8)
|
|
||||||
- testenv: py38-pyqt513
|
|
||||||
os: ubuntu-20.04
|
|
||||||
python: 3.8
|
|
||||||
### PyQt 5.14 (Python 3.8)
|
### PyQt 5.14 (Python 3.8)
|
||||||
- testenv: py38-pyqt514
|
- testenv: py38-pyqt514
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
python: 3.8
|
python: 3.8
|
||||||
### PyQt 5.15 (Python 3.9)
|
### PyQt 5.15 (Python 3.9, with coverage)
|
||||||
- testenv: py39-pyqt515
|
- testenv: py39-pyqt515-cov
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
python: 3.9
|
python: 3.9
|
||||||
### PyQt 5.15 (Python 3.8, with coverage)
|
### macOS: PyQt 5.15 (Python 3.7 to match PyInstaller env)
|
||||||
- testenv: py38-pyqt515-cov
|
- testenv: py37-pyqt515
|
||||||
os: ubuntu-20.04
|
|
||||||
python: 3.8
|
|
||||||
### macOS: PyQt 5.14 (Python 3.7)
|
|
||||||
- testenv: py37-pyqt514
|
|
||||||
os: macos-10.15
|
os: macos-10.15
|
||||||
python: 3.7
|
python: 3.7
|
||||||
args: "tests/unit" # Only run unit tests on macOS
|
args: "tests/unit" # Only run unit tests on macOS
|
||||||
### Windows: PyQt 5.14 (Python 3.7)
|
### macOS Big Sur
|
||||||
- testenv: py37-pyqt514
|
- testenv: py37-pyqt515
|
||||||
|
os: macos-11.0
|
||||||
|
python: 3.7
|
||||||
|
args: "tests/unit" # Only run unit tests on macOS
|
||||||
|
### Windows: PyQt 5.15 (Python 3.7 to match PyInstaller env)
|
||||||
|
- testenv: py37-pyqt515
|
||||||
os: windows-2019
|
os: windows-2019
|
||||||
python: 3.7
|
python: 3.7
|
||||||
runs-on: "${{ matrix.os }}"
|
runs-on: "${{ matrix.os }}"
|
||||||
|
|
@ -180,7 +169,7 @@ jobs:
|
||||||
codeql:
|
codeql:
|
||||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
@ -202,7 +191,7 @@ jobs:
|
||||||
irc:
|
irc:
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
needs: [linters, tests, tests-docker, codeql]
|
needs: [linters, tests, tests-docker, codeql]
|
||||||
if: "always() && github.repository_owner == 'qutebrowser'"
|
if: "always() && github.repository_owner == 'qutebrowser'"
|
||||||
steps:
|
steps:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
name: Rebuild Docker CI images
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "23 5 * * *" # daily at 5:23
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
image:
|
||||||
|
- archlinux-webkit
|
||||||
|
- archlinux-webengine
|
||||||
|
- archlinux-webengine-unstable
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- run: pip install jinja2
|
||||||
|
- name: Generate Dockerfile
|
||||||
|
run: python3 generate.py ${{ matrix.image }}
|
||||||
|
working-directory: scripts/dev/ci/docker/
|
||||||
|
- uses: docker/setup-buildx-action@v1
|
||||||
|
- uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: qutebrowser
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
- uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
file: scripts/dev/ci/docker/Dockerfile
|
||||||
|
context: .
|
||||||
|
tags: "qutebrowser/ci:${{ matrix.image }}"
|
||||||
|
push: ${{ github.ref == 'refs/heads/master' }}
|
||||||
|
|
||||||
|
irc:
|
||||||
|
timeout-minutes: 2
|
||||||
|
continue-on-error: true
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: [docker]
|
||||||
|
if: "always() && github.repository == 'qutebrowser/qutebrowser'"
|
||||||
|
steps:
|
||||||
|
- name: Send success IRC notification
|
||||||
|
uses: Gottox/irc-message-action@v1.1
|
||||||
|
if: "needs.docker.result == 'success'"
|
||||||
|
with:
|
||||||
|
server: chat.freenode.net
|
||||||
|
channel: '#qutebrowser-dev'
|
||||||
|
nickname: qutebrowser-bot
|
||||||
|
message: "[${{ github.workflow }}] \u00033Success:\u0003 ${{ github.ref }} https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} (@${{ github.actor }})"
|
||||||
|
- name: Send non-success IRC notification
|
||||||
|
uses: Gottox/irc-message-action@v1.1
|
||||||
|
if: "needs.docker.result != 'success'"
|
||||||
|
with:
|
||||||
|
server: chat.freenode.net
|
||||||
|
channel: '#qutebrowser-dev'
|
||||||
|
nickname: qutebrowser-bot
|
||||||
|
message: "[${{ github.workflow }}] \u00034FAIL:\u0003 ${{ github.ref }} https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} (@${{ github.actor }})\n
|
||||||
|
linters: ${{ needs.linters.result }}, tests: ${{ needs.tests.result }}, tests-docker: ${{ needs.tests-docker.result }}, codeql: ${{ needs.codeql.result }}"
|
||||||
|
|
@ -31,7 +31,7 @@ jobs:
|
||||||
run: "python3 scripts/dev/recompile_requirements.py ${{ github.events.input.environments }}"
|
run: "python3 scripts/dev/recompile_requirements.py ${{ github.events.input.environments }}"
|
||||||
id: requirements
|
id: requirements
|
||||||
- name: Create pull request
|
- name: Create pull request
|
||||||
uses: peter-evans/create-pull-request@v2
|
uses: peter-evans/create-pull-request@v3
|
||||||
with:
|
with:
|
||||||
committer: qutebrowser bot <bot@qutebrowser.org>
|
committer: qutebrowser bot <bot@qutebrowser.org>
|
||||||
author: qutebrowser bot <bot@qutebrowser.org>
|
author: qutebrowser bot <bot@qutebrowser.org>
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,6 @@ ignore_missing_imports = True
|
||||||
# https://bitbucket.org/birkenfeld/pygments-main/issues/1485/type-hints
|
# https://bitbucket.org/birkenfeld/pygments-main/issues/1485/type-hints
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
[mypy-cssutils]
|
|
||||||
# Pretty much inactive currently
|
|
||||||
ignore_missing_imports = True
|
|
||||||
|
|
||||||
[mypy-pypeg2]
|
[mypy-pypeg2]
|
||||||
# Pretty much inactive currently
|
# Pretty much inactive currently
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ valid-metaclass-classmethod-first-arg=cls
|
||||||
|
|
||||||
[TYPECHECK]
|
[TYPECHECK]
|
||||||
ignored-modules=PyQt5,PyQt5.QtWebKit
|
ignored-modules=PyQt5,PyQt5.QtWebKit
|
||||||
ignored-classes=DummyBox
|
ignored-classes=DummyBox,__cause__
|
||||||
|
|
||||||
[IMPORTS]
|
[IMPORTS]
|
||||||
known-third-party=sip
|
known-third-party=sip
|
||||||
|
|
|
||||||
16
.travis.yml
16
.travis.yml
|
|
@ -1,16 +0,0 @@
|
||||||
dist: xenial
|
|
||||||
language: python
|
|
||||||
python: 3.6
|
|
||||||
os: linux
|
|
||||||
env: TESTENV=py36-pyqt57
|
|
||||||
|
|
||||||
install:
|
|
||||||
- python -m pip install -U pip
|
|
||||||
- python -m pip install -U -r misc/requirements/requirements-tox.txt
|
|
||||||
- ulimit -c unlimited
|
|
||||||
|
|
||||||
script:
|
|
||||||
- tox -e "$TESTENV"
|
|
||||||
|
|
||||||
after_failure:
|
|
||||||
- bash scripts/dev/ci/backtrace.sh
|
|
||||||
|
|
@ -9,6 +9,7 @@ ignore: |
|
||||||
rules:
|
rules:
|
||||||
document-start: disable
|
document-start: disable
|
||||||
line-length:
|
line-length:
|
||||||
|
max: 88
|
||||||
ignore: |
|
ignore: |
|
||||||
/.github/*.yml
|
/.github/*.yml
|
||||||
/.github/workflows/*.yml
|
/.github/workflows/*.yml
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,8 @@ Requirements
|
||||||
The following software and libraries are required to run qutebrowser:
|
The following software and libraries are required to run qutebrowser:
|
||||||
|
|
||||||
* https://www.python.org/[Python] 3.6 or newer
|
* https://www.python.org/[Python] 3.6 or newer
|
||||||
* https://www.qt.io/[Qt] 5.7.1 or newer (5.14 recommended; support for < 5.11
|
* https://www.qt.io/[Qt] 5.12.0 or newer (5.12 LTS or 5.15 recommended)
|
||||||
will be dropped with qutebrowser v2.0.0) with the following modules:
|
with the following modules:
|
||||||
- QtCore / qtbase
|
- QtCore / qtbase
|
||||||
- QtQuick (part of qtbase in some distributions)
|
- QtQuick (part of qtbase in some distributions)
|
||||||
- QtSQL (part of qtbase in some distributions)
|
- QtSQL (part of qtbase in some distributions)
|
||||||
|
|
@ -123,8 +123,8 @@ The following software and libraries are required to run qutebrowser:
|
||||||
revision with known unpatched vulnerabilities. Please use it carefully and
|
revision with known unpatched vulnerabilities. Please use it carefully and
|
||||||
avoid visiting untrusted websites and using it for transmission of
|
avoid visiting untrusted websites and using it for transmission of
|
||||||
sensitive data.**
|
sensitive data.**
|
||||||
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.12.0 or newer
|
||||||
(5.14 recommended, support for < 5.11 will be dropped soon) for Python 3
|
for Python 3
|
||||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||||
* https://fdik.org/pyPEG/[pyPEG2]
|
* https://fdik.org/pyPEG/[pyPEG2]
|
||||||
* http://jinja.pocoo.org/[jinja2]
|
* http://jinja.pocoo.org/[jinja2]
|
||||||
|
|
@ -135,8 +135,6 @@ The following software and libraries are required to run qutebrowser:
|
||||||
The following libraries are optional:
|
The following libraries are optional:
|
||||||
|
|
||||||
* https://pypi.org/project/adblock/[adblock] (for improved adblocking using ABP syntax)
|
* https://pypi.org/project/adblock/[adblock] (for improved adblocking using ABP syntax)
|
||||||
* http://cthedot.de/cssutils/[cssutils] (for an improved `:download --mhtml`
|
|
||||||
with QtWebKit).
|
|
||||||
* On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log
|
* On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log
|
||||||
output.
|
output.
|
||||||
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
|
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,45 @@ Major changes
|
||||||
3.5 is dropped. Note that Python 3.5 is
|
3.5 is dropped. Note that Python 3.5 is
|
||||||
https://www.python.org/downloads/release/python-3510/[no longer supported
|
https://www.python.org/downloads/release/python-3510/[no longer supported
|
||||||
upstream] since September 2020.
|
upstream] since September 2020.
|
||||||
|
- At least Qt/PyQt 5.12 is now required to run qutebrowser, support for 5.7 to
|
||||||
|
5.11 (inclusive) is dropped. While Debian Buster ships Qt 5.11, it's based on a
|
||||||
|
Chromium version from 2018 with
|
||||||
|
https://www.debian.org/releases/buster/amd64/release-notes/ch-information.en.html#browser-security[no Debian security support]
|
||||||
|
and unsupported upstream since May 2019.
|
||||||
|
It also has compatibility issues with various websites (GitHub, Twitch, Android
|
||||||
|
Developer documentation, YouTube, ...). Since no newer Debian Stable is released
|
||||||
|
at the time of writing, it's recommended to
|
||||||
|
https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc#installing-qutebrowser-with-virtualenv[install qutebrowser in a virtualenv]
|
||||||
|
with a newer version of Qt/PyQt.
|
||||||
|
- Windows 7 is not supported anymore by the Windows binaries.
|
||||||
|
- The (formerly optional) `cssutils` dependency is now removed. It was only
|
||||||
|
needed for improved behavior in corner cases when using `:download --mhtml`
|
||||||
|
with the (non-default) QtWebKit backend, and as such it's unlikely anyone is
|
||||||
|
still relying on it. The `cssutils` project is also dead upstream, with its
|
||||||
|
repository being gone after Bitbucket
|
||||||
|
https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket[removed Mercurial support].
|
||||||
|
|
||||||
|
Removed
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
- The `--enable-webengine-inspector` flag (which was only needed for Qt 5.10 and
|
||||||
|
below) is now dropped. With Qt 5.11 and newer, the inspector/devtools are
|
||||||
|
enabled unconditionally.
|
||||||
|
- Support for moving qutebrowser data from versions before v1.0.0 has been
|
||||||
|
removed.
|
||||||
|
- The `--old` flag for `:config-diff` has been removed. It used to show
|
||||||
|
customized options for the old pre-v1.0 config files (in order to aid
|
||||||
|
migration to v1.0).
|
||||||
|
- The `:inspector` command which was deprecated in v1.13.0 (in favor of
|
||||||
|
`:devtools`) is now removed.
|
||||||
|
|
||||||
|
Added
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- When QtWebEngine has been updated but PyQtWebEngine hasn't yet, the dark mode
|
||||||
|
settings might stop working. As a (currently undocumented) escape hatch, this
|
||||||
|
version adds a `QUTE_DARKMODE_VARIANT=qt_515_2` environment variable which can
|
||||||
|
be set to get the correct behavior in (transitive) situations like this.
|
||||||
|
|
||||||
Changed
|
Changed
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
@ -32,13 +71,120 @@ Changed
|
||||||
- `config.py` files now are required to have either
|
- `config.py` files now are required to have either
|
||||||
`config.load_autoconfig(False)` (don't load `autoconfig.yml`) or
|
`config.load_autoconfig(False)` (don't load `autoconfig.yml`) or
|
||||||
`config.load_autoconfig()` (do load `autoconfig.yml`) in them.
|
`config.load_autoconfig()` (do load `autoconfig.yml`) in them.
|
||||||
|
- (TODO) Windows and macOS releases now ship Python 3.9 rather than 3.7.
|
||||||
- The `colors.webpage.darkmode.*` settings are now also supported with older Qt
|
- The `colors.webpage.darkmode.*` settings are now also supported with older Qt
|
||||||
versions (Qt 5.10 to 5.13) rather than just with Qt 5.14 and above.
|
versions (Qt 5.12 and 5.13) rather than just with Qt 5.14 and above.
|
||||||
- For regexes in the config (`hints.{prev,next}_regexes`), certain patterns
|
- For regexes in the config (`hints.{prev,next}_regexes`), certain patterns
|
||||||
which will change meanings in future Python versions are now disallowed. This is
|
which will change meanings in future Python versions are now disallowed. This is
|
||||||
the case for character sets starting with a literal `[` or containing literal
|
the case for character sets starting with a literal `[` or containing literal
|
||||||
character sequences `--`, `&&`, `~~`, or `||`. To avoid a warning, remove the
|
character sequences `--`, `&&`, `~~`, or `||`. To avoid a warning, remove the
|
||||||
duplicate characters or escape them with a backslash.
|
duplicate characters or escape them with a backslash.
|
||||||
|
- If `prompt(..., "default")` is used via JS, the default text is now
|
||||||
|
pre-selected in the prompt shown by qutebrowser.
|
||||||
|
- URLs such as `::1/foo` are now handled as a search term or local file rather
|
||||||
|
than IPv6. Use `[::1]/foo` to force parsing as IPv6 instead.
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- With interpolated color settings (`colors.tabs.indicator.*` and
|
||||||
|
`colors.downloads.*`), the alpha channel is now handled correctly.
|
||||||
|
|
||||||
|
v1.14.1 (unreleased)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Added
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- With v1.14.0, qutebrowser configures the main window to be transparent, so
|
||||||
|
that it's possible to configure a translucent tab- or statusbar. However, that
|
||||||
|
change introduced various issues, such as performance degradation on some
|
||||||
|
systems or breaking dmenu window embedding with its `-w` option. To avoid those
|
||||||
|
issues for people who are not using transparency, the default behavior is
|
||||||
|
reverted to versions before v1.14.0 in this release. A new `window.transparent`
|
||||||
|
setting can be set to `true` to restore the behavior of v1.14.0.
|
||||||
|
|
||||||
|
Changed
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
- Windows and macOS releases now ship Qt 5.15.2, which is based on
|
||||||
|
Chromium 83.0.4103.122 with security fixes up to 86.0.4240.183. This includes
|
||||||
|
CVE-2020-15999 in the bundled freetype library, which is known to be exploited
|
||||||
|
in the wild. It also includes various other bugfixes/features compared to
|
||||||
|
Qt 5.15.0 included in qutebrowser v1.14.0, such as:
|
||||||
|
* Correct handling of AltGr on Windows
|
||||||
|
* Fix for `content.cookies.accept` not working properly
|
||||||
|
* Fixes for screen sharing (some websites are still broken until an upcoming Qt
|
||||||
|
5.15.3)
|
||||||
|
* Support for FIDO U2F / WebAuth
|
||||||
|
* Fix for the unwanted creation of directories such as `databases-incognito` in
|
||||||
|
the home directory
|
||||||
|
* Proper autocompletion in the devtools console
|
||||||
|
* Proper signalisation of a tab's audible status (`[A]`)
|
||||||
|
* Fix for a hang when opening the context menu on macOS Big Sur (11.0)
|
||||||
|
* Hardware accelerated graphics on macOS
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Setting the `content.headers.referer` setting to `same-domain` (the default)
|
||||||
|
was supposed to truncate referers to only the host with QtWebEngine.
|
||||||
|
Unfortunately, this functionality broke in Qt 5.14. It works properly again
|
||||||
|
with this release, including a test so this won't happen again.
|
||||||
|
- With QtWebEngine 5.15, setting the `content.headers.referer` setting to
|
||||||
|
`never` did still send referers. This is now fixed as well.
|
||||||
|
- In v1.14.0, a regression was introduced, causing a crash when qutebrowser was
|
||||||
|
closed after opening a download with PDF.js. This is now fixed.
|
||||||
|
- With Qt 5.12, the `Object.fromEntries` JavaScript API is unavailable (it was
|
||||||
|
introduced in Chromium 73, while Qt 5.12 is based on 69). This caused
|
||||||
|
https://www.vr.fi/en and possibly other websites to break when accessed with Qt
|
||||||
|
5.12. A suitable polyfill is now included with qutebrowser if
|
||||||
|
`content.site_specific_quirks` is enabled (which is the default).
|
||||||
|
- While XDG startup notifications (e.g. launch feedback via the bouncy cursor
|
||||||
|
in KDE Plasma) were supported ever since Qt 5.1, qutebrowser's desktop file
|
||||||
|
accidentally declared that it wasn't supported. This is now fixed.
|
||||||
|
- The `dmenu_qutebrowser` and `qutedmenu` userscripts now correctly read the
|
||||||
|
qutebrowser sqlite history which has been in use since v1.0.0.
|
||||||
|
- With Python 3.8+ and vertical tabs, a deprecation warning for an implicit int
|
||||||
|
conversion was shown. This is now fixed.
|
||||||
|
- Ever since Qt 5.11, fetching more completion data when that data is loaded
|
||||||
|
lazily (such as with history) and the last visible item is selected was broken.
|
||||||
|
The exact reason is currently unknown, but this release adds a tenative fix.
|
||||||
|
- When PgUp/PgDown were used to go beyond the last visible item, the above issue
|
||||||
|
caused a crash, which is now also fixed.
|
||||||
|
- As a workaround for an overzealous Microsoft Defender false-positive detecting
|
||||||
|
a "trojan" in the (unprocessed) adblock list, `:adblock-update` now doesn't
|
||||||
|
cache the HTTP response anymore.
|
||||||
|
- With the QtWebKit backend and `content.headers` set to `same-domain` (the
|
||||||
|
default), origins with the same domain but different schemes or ports were
|
||||||
|
treated as the same domain. They now are correctly treated as different domains.
|
||||||
|
- When a URL path uses percent escapes (such as
|
||||||
|
`https://example.com/embedded%2Fpath`), using `:navigate up` would treat the
|
||||||
|
`%2F` as a path separator and replace any remaining percent escapes by their
|
||||||
|
unescaped equivalents. Those are now handled correctly.
|
||||||
|
- On macOS 11.0 (Big Sur), the default monospace font name caused a parsing error, thus
|
||||||
|
resulting in broken styling for the completion, hints, and other UI components.
|
||||||
|
They now look properly again.
|
||||||
|
- Due to a Qt bug, installing Qt/PyQt from prebuilt binaries on systems with a
|
||||||
|
very old `libxcb-utils` version (notably, Debian Stable, but not Ubuntu since
|
||||||
|
16.04 LTS) results in a setup which fails to start. This also affects the
|
||||||
|
`mkvenv.py` script, which now includes a workaround for this case.
|
||||||
|
- The `open_url_instance.sh` userscript now complains when `socat` is not
|
||||||
|
installed, rather than silencing the error.
|
||||||
|
- The example AppArmor profile in `misc/` was outdated and written for the
|
||||||
|
older QtWebKit backend. It is now updated to serve as an useful starting
|
||||||
|
point with QtWebEngine.
|
||||||
|
- When running `:devtools` on Fedora without the needed (optional) dependency
|
||||||
|
installed, it was suggested to install `qt5-webengine-devtools`, which does
|
||||||
|
not, in fact, exist. It's now correctly suggested to install
|
||||||
|
`qt5-qtwebengine-devtools` instead.
|
||||||
|
- Minor performance improvements.
|
||||||
|
- (TODO) Fix for various functionality breaking in private windows with v1.14.0,
|
||||||
|
after the last private window is closed. This includes:
|
||||||
|
* Ad blocking
|
||||||
|
* Downloads
|
||||||
|
* Site-specific quirks (e.g. for Google login)
|
||||||
|
* Certain settings such as `content.javascript.enabled`
|
||||||
|
|
||||||
v1.14.0 (2020-10-15)
|
v1.14.0 (2020-10-15)
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,8 @@ Currently, the following tox environments are available:
|
||||||
|
|
||||||
* Tests using https://www.pytest.org[pytest]:
|
* Tests using https://www.pytest.org[pytest]:
|
||||||
- `py36`, `py37`, ...: Run pytest for python 3.6/3.7/... with the system-wide PyQt.
|
- `py36`, `py37`, ...: Run pytest for python 3.6/3.7/... with the system-wide PyQt.
|
||||||
- `py36-pyqt57`, ..., `py36-pyqt59`: Run pytest with the given PyQt version (`py35-*` also works).
|
- `py36-pyqt512`, ..., `py36-pyqt515`: Run pytest with the given PyQt version (`py35-*` also works).
|
||||||
- `py36-pyqt59-cov`: Run with coverage support (other Python/PyQt versions work too).
|
- `py36-pyqt515-cov`: Run with coverage support (other Python/PyQt versions work too).
|
||||||
* `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8].
|
* `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8].
|
||||||
* `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find
|
* `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find
|
||||||
unused code portions.
|
unused code portions.
|
||||||
|
|
@ -586,9 +586,9 @@ can be useful for debugging:
|
||||||
- chrome://gpuclean/ (crashes the current renderer process!)
|
- chrome://gpuclean/ (crashes the current renderer process!)
|
||||||
- chrome://ppapiflashcrash/
|
- chrome://ppapiflashcrash/
|
||||||
- chrome://ppapiflashhang/
|
- chrome://ppapiflashhang/
|
||||||
- chrome://quota-internals/ (Qt 5.11)
|
- chrome://quota-internals/
|
||||||
- chrome://taskscheduler-internals/ (Qt 5.11)
|
- chrome://taskscheduler-internals/
|
||||||
- chrome://sandbox/ (Qt 5.11, Linux only)
|
- chrome://sandbox/ (Linux only)
|
||||||
|
|
||||||
QtWebEngine internals
|
QtWebEngine internals
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,6 @@ For QtWebKit:
|
||||||
+
|
+
|
||||||
For QtWebEngine:
|
For QtWebEngine:
|
||||||
|
|
||||||
. Make sure your versions of PyQt and Qt are 5.8 or higher.
|
|
||||||
. Use `dictcli.py` script to install dictionaries.
|
. Use `dictcli.py` script to install dictionaries.
|
||||||
Run the script with `-h` for the parameter description.
|
Run the script with `-h` for the parameter description.
|
||||||
. Set `spellcheck.languages` to the desired list of languages, e.g.:
|
. Set `spellcheck.languages` to the desired list of languages, e.g.:
|
||||||
|
|
@ -253,11 +252,6 @@ Note that there are some missing features which you may run into:
|
||||||
. Some scripts expect `GM_xmlhttpRequest` to ignore Cross Origin Resource
|
. Some scripts expect `GM_xmlhttpRequest` to ignore Cross Origin Resource
|
||||||
Sharing restrictions, this is currently not supported, so scripts making
|
Sharing restrictions, this is currently not supported, so scripts making
|
||||||
requests to third party sites will often fail to function correctly.
|
requests to third party sites will often fail to function correctly.
|
||||||
. If your backend is a QtWebEngine version 5.8, 5.9 or 5.10 then regular
|
|
||||||
expressions are not supported in `@include` or `@exclude` rules. If your
|
|
||||||
script uses them you can re-write them to use glob expressions or convert
|
|
||||||
them to `@match` rules.
|
|
||||||
See https://wiki.greasespot.net/Metadata_Block[the wiki] for more info.
|
|
||||||
. Any greasemonkey API function to do with adding UI elements is not currently
|
. Any greasemonkey API function to do with adding UI elements is not currently
|
||||||
supported. That means context menu extentensions and background pages.
|
supported. That means context menu extentensions and background pages.
|
||||||
|
|
||||||
|
|
@ -330,8 +324,14 @@ There is a total of four possible approaches to get dark websites:
|
||||||
The setting requires a restart and QtWebEngine with at least Qt 5.14.
|
The setting requires a restart and QtWebEngine with at least Qt 5.14.
|
||||||
- The `colors.webpage.darkmode.*` settings enable the dark mode of the underlying
|
- The `colors.webpage.darkmode.*` settings enable the dark mode of the underlying
|
||||||
Chromium. Those setting require a restart and QtWebEngine with at least Qt 5.14. It's
|
Chromium. Those setting require a restart and QtWebEngine with at least Qt 5.14. It's
|
||||||
unfortunately not possible (due to limitations in Chromium and/or QtWebEngine) to
|
unfortunately not possible (due to limitations
|
||||||
|
https://bugs.chromium.org/p/chromium/issues/detail?id=952419[in Chromium] and/or
|
||||||
|
https://bugreports.qt.io/browse/QTBUG-84484[QtWebEngine]) to
|
||||||
change them dynamically or to specify a list of excluded websites.
|
change them dynamically or to specify a list of excluded websites.
|
||||||
|
There is some remaining hope to
|
||||||
|
https://github.com/qutebrowser/qutebrowser/issues/5542[allow for this]
|
||||||
|
using HTML/CSS features, but so far nobody has been able to get things to
|
||||||
|
work (even with Chromium) - help welcome!
|
||||||
- The `content.user_stylesheets` setting allows specifying a custom CSS such as
|
- The `content.user_stylesheets` setting allows specifying a custom CSS such as
|
||||||
https://github.com/alphapapa/solarized-everything-css/[Solarized Everything]. Despite
|
https://github.com/alphapapa/solarized-everything-css/[Solarized Everything]. Despite
|
||||||
the name, the repository also offers themes other than just Solarized. This approach
|
the name, the repository also offers themes other than just Solarized. This approach
|
||||||
|
|
|
||||||
|
|
@ -332,14 +332,8 @@ Remove a key from a dict.
|
||||||
|
|
||||||
[[config-diff]]
|
[[config-diff]]
|
||||||
=== config-diff
|
=== config-diff
|
||||||
Syntax: +:config-diff [*--old*]+
|
|
||||||
|
|
||||||
Show all customized options.
|
Show all customized options.
|
||||||
|
|
||||||
==== optional arguments
|
|
||||||
* +*-o*+, +*--old*+: Show difference for the pre-v1.0 files (qutebrowser.conf/keys.conf).
|
|
||||||
|
|
||||||
|
|
||||||
[[config-edit]]
|
[[config-edit]]
|
||||||
=== config-edit
|
=== config-edit
|
||||||
Syntax: +:config-edit [*--no-source*]+
|
Syntax: +:config-edit [*--no-source*]+
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ qutebrowser's config files
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
qutebrowser releases before v1.0.0 had a `qutebrowser.conf` and `keys.conf`
|
qutebrowser releases before v1.0.0 had a `qutebrowser.conf` and `keys.conf`
|
||||||
file. Those are not used anymore since that release - see
|
file. Those are not used anymore since v1.0.0.
|
||||||
<<migrating,"Migrating older configurations">> for information on how to
|
|
||||||
migrate to the new config.
|
|
||||||
|
|
||||||
When using `:set` and `:bind`, changes are saved to an `autoconfig.yml` file
|
When using `:set` and `:bind`, changes are saved to an `autoconfig.yml` file
|
||||||
automatically. If you don't want to have a config file which is curated by
|
automatically. If you don't want to have a config file which is curated by
|
||||||
|
|
@ -422,8 +420,8 @@ stable across qutebrowser versions):
|
||||||
# pylint: disable=C0111
|
# pylint: disable=C0111
|
||||||
from qutebrowser.config.configfiles import ConfigAPI # noqa: F401
|
from qutebrowser.config.configfiles import ConfigAPI # noqa: F401
|
||||||
from qutebrowser.config.config import ConfigContainer # noqa: F401
|
from qutebrowser.config.config import ConfigContainer # noqa: F401
|
||||||
config = config # type: ConfigAPI # noqa: F821 pylint: disable=E0602,C0103
|
config: ConfigAPI = config # noqa: F821 pylint: disable=E0602,C0103
|
||||||
c = c # type: ConfigContainer # noqa: F821 pylint: disable=E0602,C0103
|
c: ConfigContainer = c # noqa: F821 pylint: disable=E0602,C0103
|
||||||
----
|
----
|
||||||
|
|
||||||
emacs-like config
|
emacs-like config
|
||||||
|
|
@ -439,38 +437,3 @@ Various emacs/conkeror-like keybinding configs exist:
|
||||||
It's also mostly possible to get rid of modal keybindings by setting
|
It's also mostly possible to get rid of modal keybindings by setting
|
||||||
`input.insert_mode.auto_enter` to `false`, and `input.forward_unbound_keys` to
|
`input.insert_mode.auto_enter` to `false`, and `input.forward_unbound_keys` to
|
||||||
`all`.
|
`all`.
|
||||||
|
|
||||||
[[migrating]]
|
|
||||||
Migrating older configurations
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
qutebrowser does no automatic migration for the new configuration. However,
|
|
||||||
there's a special link:qute://configdiff/old[configdiff] page
|
|
||||||
(`qute://configdiff/old`) in qutebrowser, which will show you the changes you
|
|
||||||
did in your old configuration, compared to the old defaults.
|
|
||||||
|
|
||||||
Other changes in default settings:
|
|
||||||
|
|
||||||
- In v1.1.x and newer, `<Up>` and `<Down>` navigate through command history
|
|
||||||
if no text was entered yet.
|
|
||||||
With v1.0.x, they always navigate through command history instead of selecting
|
|
||||||
completion items. Use `<Tab>`/`<Shift-Tab>` to cycle through the completion
|
|
||||||
instead.
|
|
||||||
You can get back the old behavior by doing:
|
|
||||||
+
|
|
||||||
----
|
|
||||||
:bind -m command <Up> completion-item-focus prev
|
|
||||||
:bind -m command <Down> completion-item-focus next
|
|
||||||
----
|
|
||||||
+
|
|
||||||
or always navigate through command history with
|
|
||||||
+
|
|
||||||
----
|
|
||||||
:bind -m command <Up> command-history-prev
|
|
||||||
:bind -m command <Down> command-history-next
|
|
||||||
----
|
|
||||||
|
|
||||||
- The default for `completion.web_history.max_items` is now set to `-1`, showing
|
|
||||||
an unlimited number of items in the completion for `:open` as the new
|
|
||||||
sqlite-based completion is much faster. If the `:open` completion is too slow
|
|
||||||
on your machine, set an appropriate limit again.
|
|
||||||
|
|
|
||||||
|
|
@ -329,6 +329,7 @@
|
||||||
|<<url.yank_ignored_parameters,url.yank_ignored_parameters>>|URL parameters to strip with `:yank url`.
|
|<<url.yank_ignored_parameters,url.yank_ignored_parameters>>|URL parameters to strip with `:yank url`.
|
||||||
|<<window.hide_decoration,window.hide_decoration>>|Hide the window decoration.
|
|<<window.hide_decoration,window.hide_decoration>>|Hide the window decoration.
|
||||||
|<<window.title_format,window.title_format>>|Format to use for the window title. The same placeholders like for
|
|<<window.title_format,window.title_format>>|Format to use for the window title. The same placeholders like for
|
||||||
|
|<<window.transparent,window.transparent>>|Set the main window background to transparent.
|
||||||
|<<zoom.default,zoom.default>>|Default zoom level.
|
|<<zoom.default,zoom.default>>|Default zoom level.
|
||||||
|<<zoom.levels,zoom.levels>>|Available zoom levels.
|
|<<zoom.levels,zoom.levels>>|Available zoom levels.
|
||||||
|<<zoom.mouse_divider,zoom.mouse_divider>>|Number of zoom increments to divide the mouse wheel movements to.
|
|<<zoom.mouse_divider,zoom.mouse_divider>>|Number of zoom increments to divide the mouse wheel movements to.
|
||||||
|
|
@ -1586,9 +1587,7 @@ Valid values:
|
||||||
|
|
||||||
Default: +pass:[lightness-cielab]+
|
Default: +pass:[lightness-cielab]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[colors.webpage.darkmode.contrast]]
|
[[colors.webpage.darkmode.contrast]]
|
||||||
=== colors.webpage.darkmode.contrast
|
=== colors.webpage.darkmode.contrast
|
||||||
|
|
@ -1601,9 +1600,7 @@ Type: <<types,Float>>
|
||||||
|
|
||||||
Default: +pass:[0.0]+
|
Default: +pass:[0.0]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[colors.webpage.darkmode.enabled]]
|
[[colors.webpage.darkmode.enabled]]
|
||||||
=== colors.webpage.darkmode.enabled
|
=== colors.webpage.darkmode.enabled
|
||||||
|
|
@ -1629,9 +1626,7 @@ Type: <<types,Bool>>
|
||||||
|
|
||||||
Default: +pass:[false]+
|
Default: +pass:[false]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[colors.webpage.darkmode.grayscale.all]]
|
[[colors.webpage.darkmode.grayscale.all]]
|
||||||
=== colors.webpage.darkmode.grayscale.all
|
=== colors.webpage.darkmode.grayscale.all
|
||||||
|
|
@ -1644,9 +1639,7 @@ Type: <<types,Bool>>
|
||||||
|
|
||||||
Default: +pass:[false]+
|
Default: +pass:[false]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[colors.webpage.darkmode.grayscale.images]]
|
[[colors.webpage.darkmode.grayscale.images]]
|
||||||
=== colors.webpage.darkmode.grayscale.images
|
=== colors.webpage.darkmode.grayscale.images
|
||||||
|
|
@ -1666,7 +1659,7 @@ On QtWebKit, this setting is unavailable.
|
||||||
[[colors.webpage.darkmode.policy.images]]
|
[[colors.webpage.darkmode.policy.images]]
|
||||||
=== colors.webpage.darkmode.policy.images
|
=== colors.webpage.darkmode.policy.images
|
||||||
Which images to apply dark mode to.
|
Which images to apply dark mode to.
|
||||||
With QtWebEngine 5.15.0, this setting can cause frequent renderer process crashes due to a https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug in Qt]. With QtWebEngine 5.10, this is not available at all. In those cases, the 'smart' setting is ignored and treated like 'never'.
|
With QtWebEngine 5.15.0, this setting can cause frequent renderer process crashes due to a https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug in Qt].
|
||||||
|
|
||||||
This setting requires a restart.
|
This setting requires a restart.
|
||||||
|
|
||||||
|
|
@ -1676,13 +1669,11 @@ Valid values:
|
||||||
|
|
||||||
* +always+: Apply dark mode filter to all images.
|
* +always+: Apply dark mode filter to all images.
|
||||||
* +never+: Never apply dark mode filter to any images.
|
* +never+: Never apply dark mode filter to any images.
|
||||||
* +smart+: Apply dark mode based on image content. Not available with Qt 5.10 / 5.15.0.
|
* +smart+: Apply dark mode based on image content. Not available with Qt 5.15.0.
|
||||||
|
|
||||||
Default: +pass:[smart]+
|
Default: +pass:[smart]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[colors.webpage.darkmode.policy.page]]
|
[[colors.webpage.darkmode.policy.page]]
|
||||||
=== colors.webpage.darkmode.policy.page
|
=== colors.webpage.darkmode.policy.page
|
||||||
|
|
@ -1905,7 +1896,6 @@ Default:
|
||||||
[[content.autoplay]]
|
[[content.autoplay]]
|
||||||
=== content.autoplay
|
=== content.autoplay
|
||||||
Automatically start playing `<video>` elements.
|
Automatically start playing `<video>` elements.
|
||||||
Note: On Qt < 5.11, this option needs a restart and does not support URL patterns.
|
|
||||||
|
|
||||||
This setting supports URL patterns.
|
This setting supports URL patterns.
|
||||||
|
|
||||||
|
|
@ -1913,9 +1903,7 @@ Type: <<types,Bool>>
|
||||||
|
|
||||||
Default: +pass:[true]+
|
Default: +pass:[true]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[content.blocking.adblock.lists]]
|
[[content.blocking.adblock.lists]]
|
||||||
=== content.blocking.adblock.lists
|
=== content.blocking.adblock.lists
|
||||||
|
|
@ -2058,12 +2046,9 @@ Valid values:
|
||||||
|
|
||||||
Default: +pass:[all]+
|
Default: +pass:[all]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.11 or newer.
|
|
||||||
|
|
||||||
[[content.cookies.store]]
|
[[content.cookies.store]]
|
||||||
=== content.cookies.store
|
=== content.cookies.store
|
||||||
Store cookies.
|
Store cookies.
|
||||||
Note this option needs a restart with QtWebEngine on Qt < 5.9.
|
|
||||||
|
|
||||||
Type: <<types,Bool>>
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
|
|
@ -2081,7 +2066,6 @@ Default: +pass:[iso-8859-1]+
|
||||||
[[content.desktop_capture]]
|
[[content.desktop_capture]]
|
||||||
=== content.desktop_capture
|
=== content.desktop_capture
|
||||||
Allow websites to share screen content.
|
Allow websites to share screen content.
|
||||||
On Qt < 5.10, a dialog box is always displayed, even if this is set to "true".
|
|
||||||
|
|
||||||
This setting supports URL patterns.
|
This setting supports URL patterns.
|
||||||
|
|
||||||
|
|
@ -2105,7 +2089,7 @@ Type: <<types,Bool>>
|
||||||
|
|
||||||
Default: +pass:[true]+
|
Default: +pass:[true]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.12 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
[[content.frame_flattening]]
|
[[content.frame_flattening]]
|
||||||
=== content.frame_flattening
|
=== content.frame_flattening
|
||||||
|
|
@ -2436,9 +2420,7 @@ Valid values:
|
||||||
|
|
||||||
Default: +pass:[ask]+
|
Default: +pass:[ask]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.8 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[content.mute]]
|
[[content.mute]]
|
||||||
=== content.mute
|
=== content.mute
|
||||||
|
|
@ -2505,9 +2487,7 @@ Valid values:
|
||||||
|
|
||||||
Default: +pass:[ask]+
|
Default: +pass:[ask]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.11 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[content.plugins]]
|
[[content.plugins]]
|
||||||
=== content.plugins
|
=== content.plugins
|
||||||
|
|
@ -2529,7 +2509,7 @@ Type: <<types,Bool>>
|
||||||
|
|
||||||
Default: +pass:[true]+
|
Default: +pass:[true]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.8 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
[[content.private_browsing]]
|
[[content.private_browsing]]
|
||||||
=== content.private_browsing
|
=== content.private_browsing
|
||||||
|
|
@ -2580,9 +2560,7 @@ Valid values:
|
||||||
|
|
||||||
Default: +pass:[ask]+
|
Default: +pass:[ask]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.11 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[content.site_specific_quirks]]
|
[[content.site_specific_quirks]]
|
||||||
=== content.site_specific_quirks
|
=== content.site_specific_quirks
|
||||||
|
|
@ -2626,9 +2604,7 @@ Valid values:
|
||||||
|
|
||||||
Default: +pass:[allow-from-user-interaction]+
|
Default: +pass:[allow-from-user-interaction]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.11 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[content.user_stylesheets]]
|
[[content.user_stylesheets]]
|
||||||
=== content.user_stylesheets
|
=== content.user_stylesheets
|
||||||
|
|
@ -2651,7 +2627,6 @@ Default: +pass:[true]+
|
||||||
[[content.webrtc_ip_handling_policy]]
|
[[content.webrtc_ip_handling_policy]]
|
||||||
=== content.webrtc_ip_handling_policy
|
=== content.webrtc_ip_handling_policy
|
||||||
Which interfaces to expose via WebRTC.
|
Which interfaces to expose via WebRTC.
|
||||||
On Qt 5.10, this option doesn't work because of a Qt bug.
|
|
||||||
|
|
||||||
This setting requires a restart.
|
This setting requires a restart.
|
||||||
|
|
||||||
|
|
@ -2666,9 +2641,7 @@ Valid values:
|
||||||
|
|
||||||
Default: +pass:[all-interfaces]+
|
Default: +pass:[all-interfaces]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.9.2 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[content.xss_auditing]]
|
[[content.xss_auditing]]
|
||||||
=== content.xss_auditing
|
=== content.xss_auditing
|
||||||
|
|
@ -3594,7 +3567,7 @@ Valid values:
|
||||||
* +always+: Always show the scrollbar.
|
* +always+: Always show the scrollbar.
|
||||||
* +never+: Never 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`.
|
* +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 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.
|
* +overlay+: Show an overlay scrollbar. 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: +pass:[overlay]+
|
Default: +pass:[overlay]+
|
||||||
|
|
||||||
|
|
@ -3713,9 +3686,7 @@ Valid values:
|
||||||
|
|
||||||
Default: empty
|
Default: empty
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.8 or newer.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
On QtWebKit, this setting is unavailable.
|
|
||||||
|
|
||||||
[[statusbar.padding]]
|
[[statusbar.padding]]
|
||||||
=== statusbar.padding
|
=== statusbar.padding
|
||||||
|
|
@ -4211,6 +4182,7 @@ characters in the search terms are replaced by safe characters (called
|
||||||
expands to `slash%2Fand%26amp`).
|
expands to `slash%2Fand%26amp`).
|
||||||
* `{unquoted}` quotes nothing (for `slash/and&` this placeholder
|
* `{unquoted}` quotes nothing (for `slash/and&` this placeholder
|
||||||
expands to `slash/and&`).
|
expands to `slash/and&`).
|
||||||
|
* `{0}` means the same as `{}`, but can be used multiple times.
|
||||||
|
|
||||||
The search engine named `DEFAULT` is used when `url.auto_search` is turned
|
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
|
on and something else than a URL was entered to be opened. Other search
|
||||||
|
|
@ -4268,6 +4240,22 @@ Type: <<types,FormatString>>
|
||||||
|
|
||||||
Default: +pass:[{perc}{current_title}{title_sep}qutebrowser]+
|
Default: +pass:[{perc}{current_title}{title_sep}qutebrowser]+
|
||||||
|
|
||||||
|
[[window.transparent]]
|
||||||
|
=== window.transparent
|
||||||
|
Set the main window background to transparent.
|
||||||
|
|
||||||
|
This allows having a transparent tab- or statusbar (might require a compositor such
|
||||||
|
as picom). However, it breaks some functionality such as dmenu embedding via its
|
||||||
|
`-w` option. On some systems, it was additionally reported that main window
|
||||||
|
transparency negatively affects performance.
|
||||||
|
|
||||||
|
Note this setting only affects windows opened after setting it.
|
||||||
|
|
||||||
|
|
||||||
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
|
Default: +pass:[false]+
|
||||||
|
|
||||||
[[zoom.default]]
|
[[zoom.default]]
|
||||||
=== zoom.default
|
=== zoom.default
|
||||||
Default zoom level.
|
Default zoom level.
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,15 @@ How to install qutebrowser depends a lot on the version of Debian/Ubuntu you're
|
||||||
running.
|
running.
|
||||||
|
|
||||||
[[ubuntu1604]]
|
[[ubuntu1604]]
|
||||||
Ubuntu 16.04 LTS / Linux Mint 18
|
Debian Stretch / Ubuntu 16.04 LTS / Linux Mint 18
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Debian Stretch does have QtWebEngine packaged, but only in a very old and insecure
|
||||||
|
version (Qt 5.7, based on a Chromium from March 2016). Furthermore, it packages Python
|
||||||
|
3.5 which is not supported anymore since qutebrowser v2.0.0.
|
||||||
|
|
||||||
Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
|
Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
|
||||||
QtWebEngine). It also comes with Python 3.5 which is not supported anymore since
|
QtWebEngine) and also comes with Python 3.5.
|
||||||
qutebrowser v2.0.0.
|
|
||||||
|
|
||||||
You should be able to install a newer Python (3.6+) using the
|
You should be able to install a newer Python (3.6+) using the
|
||||||
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or
|
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or
|
||||||
|
|
@ -51,51 +54,23 @@ Note you'll need some basic libraries to use the virtualenv-installed PyQt:
|
||||||
// FIXME not needed anymore?
|
// FIXME not needed anymore?
|
||||||
// libxi6 libxrender1 libegl1-mesa
|
// libxi6 libxrender1 libegl1-mesa
|
||||||
|
|
||||||
Debian Stretch
|
Debian Buster / Ubuntu 18.04 LTS / Linux Mint 19
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
WARNING: Debian Stretch packages Qt 5.7 which is very old (based on a Chromium
|
Debian Buster packages qutebrowser, but ships a very old version (v1.6.1 from March
|
||||||
from March 2016 with security fixes from November 2016) and insecure. It is also
|
2019). The QtWebEngine library used for rendering web contents is also very old (Qt
|
||||||
https://www.debian.org/releases/stretch/amd64/release-notes/ch-information.en.html#browser-security[not covered]
|
5.11, based on a Chromium from March 2018) and insecure. It is
|
||||||
by Debian's security patches. Support for it will be dropped in qutebrowser
|
|
||||||
v2.0.0, preliminarily planned for December 2020. It is recommended to
|
|
||||||
<<tox,install qutebrowser in a virtualenv>> with a newer PyQt/Qt binary
|
|
||||||
instead.
|
|
||||||
|
|
||||||
Debian Stretch comes with QtWebEngine in the repositories. This makes it possible
|
|
||||||
to install qutebrowser via the Debian package.
|
|
||||||
|
|
||||||
You'll need to download three packages:
|
|
||||||
|
|
||||||
- https://packages.debian.org/sid/all/python3-pypeg2/download[PyPEG2] (a library
|
|
||||||
used by qutebrowser which is not in the earlier repositories)
|
|
||||||
- https://packages.debian.org/sid/all/qutebrowser/download[qutebrowser] itself
|
|
||||||
- Either https://packages.debian.org/sid/all/qutebrowser-qtwebengine/download[qutebrowser-qtwebengine]
|
|
||||||
or https://packages.debian.org/sid/all/qutebrowser-qtwebkit/download[qutebrowser-qtwebkit]
|
|
||||||
(or both) depending on the backend you want to use. QtWebEngine is the
|
|
||||||
default/recommended choice.
|
|
||||||
|
|
||||||
After downloading, install the packages (make sure to install all the
|
|
||||||
downloaded qutebrowser deb files in one apt command):
|
|
||||||
|
|
||||||
----
|
|
||||||
# apt install ./python3-pypeg2_*_all.deb
|
|
||||||
# apt install ./qutebrowser*.deb
|
|
||||||
----
|
|
||||||
|
|
||||||
For an update after the initial install, you only need to download/install the
|
|
||||||
qutebrowser package.
|
|
||||||
|
|
||||||
Debian Buster / Ubuntu 18.04 LTS / Linux Mint 19 (or newer)
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
WARNING: Debian Buster packages Qt 5.11 which is very old (based on a Chromium
|
|
||||||
from March 2018 with security fixes from November 2018) and insecure. It is also
|
|
||||||
https://www.debian.org/releases/buster/amd64/release-notes/ch-information.en.html#browser-security[not covered]
|
https://www.debian.org/releases/buster/amd64/release-notes/ch-information.en.html#browser-security[not covered]
|
||||||
by Debian's security patches. Support for it will be dropped in qutebrowser
|
by Debian's security patches. It's recommended to <<tox,install qutebrowser in a
|
||||||
v2.0.0, preliminarily planned for December 2020. It is recommended to
|
virtualenv>> with a newer PyQt/Qt binary instead.
|
||||||
<<tox,install qutebrowser in a virtualenv>> with a newer PyQt/Qt binary
|
|
||||||
instead.
|
With Ubuntu 18.04, the situation looks similar (but worse): There, qutebrowser v1.1.1
|
||||||
|
from January 2018 is packaged, with QtWebEngine 5.9 based on a Chromium from January
|
||||||
|
2017. It's recommended to either upgrade to Ubuntu 20.04 LTS or <<tox,install
|
||||||
|
qutebrowser in a virtualenv>> with a newer PyQt/Qt binary instead.
|
||||||
|
|
||||||
|
Ubuntu 20.04 LTS / Linux Mint 20 (or newer)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
With those distributions, qutebrowser is in the official repositories, and you
|
With those distributions, qutebrowser is in the official repositories, and you
|
||||||
can install it with apt:
|
can install it with apt:
|
||||||
|
|
@ -108,7 +83,8 @@ Additional hints
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
- If running from git, run the following to generate the documentation for the
|
- If running from git, run the following to generate the documentation for the
|
||||||
`:help` command:
|
`:help` command (the `mkvenv.py` script used with a virtualenv install already does
|
||||||
|
this for you):
|
||||||
+
|
+
|
||||||
----
|
----
|
||||||
# apt install --no-install-recommends asciidoc
|
# apt install --no-install-recommends asciidoc
|
||||||
|
|
@ -116,14 +92,16 @@ $ python3 scripts/asciidoc2html.py
|
||||||
----
|
----
|
||||||
|
|
||||||
- If you prefer using QtWebKit, there's QtWebKit 5.212 available in
|
- If you prefer using QtWebKit, there's QtWebKit 5.212 available in
|
||||||
https://packages.debian.org/buster/libqt5webkit5[Debian Testing]. Note
|
Ubuntu 18.04 / Debian Buster or newer. Note however that it is based on an upstream
|
||||||
however that it is based on an upstream WebKit from September 2016 with known
|
WebKit from September 2016 with known security issues and no sandboxing or process
|
||||||
security issues and no sandboxing or process isolation.
|
isolation.
|
||||||
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||||
+
|
+
|
||||||
----
|
----
|
||||||
# apt install gstreamer1.0-plugins-{bad,base,good,ugly}
|
# apt install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||||
----
|
----
|
||||||
|
+
|
||||||
|
Note those are only needed with QtWebKit, not with the (default) QtWebEngine backend.
|
||||||
|
|
||||||
On Fedora
|
On Fedora
|
||||||
---------
|
---------
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,6 @@ show it.
|
||||||
*--backend* '{webkit,webengine}'::
|
*--backend* '{webkit,webengine}'::
|
||||||
Which backend to use.
|
Which backend to use.
|
||||||
|
|
||||||
*--enable-webengine-inspector*::
|
|
||||||
Enable the web inspector / devtools for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details. This is not needed anymore since Qt 5.11 where the inspector is always enabled and secure.
|
|
||||||
|
|
||||||
=== debug arguments
|
=== debug arguments
|
||||||
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
|
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
|
||||||
Override the configured console loglevel
|
Override the configured console loglevel
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ Type=Application
|
||||||
Categories=Network;WebBrowser;
|
Categories=Network;WebBrowser;
|
||||||
Exec=qutebrowser %u
|
Exec=qutebrowser %u
|
||||||
Terminal=false
|
Terminal=false
|
||||||
StartupNotify=false
|
StartupNotify=true
|
||||||
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute;
|
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute;
|
||||||
Keywords=Browser
|
Keywords=Browser
|
||||||
Actions=new-window;preferences;
|
Actions=new-window;preferences;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
check-manifest==0.44
|
build==0.1.0
|
||||||
|
check-manifest==0.45
|
||||||
|
packaging==20.4
|
||||||
pep517==0.9.1
|
pep517==0.9.1
|
||||||
toml==0.10.1
|
pyparsing==2.4.7
|
||||||
|
six==1.15.0
|
||||||
|
toml==0.10.2
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
bump2version==1.0.1
|
bump2version==1.0.1
|
||||||
certifi==2020.6.20
|
certifi==2020.11.8
|
||||||
cffi==1.14.3
|
cffi==1.14.4
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
colorama==0.4.4
|
colorama==0.4.4
|
||||||
cryptography==3.2
|
cryptography==3.2.1
|
||||||
cssutils==1.0.2
|
|
||||||
github3.py==1.3.0
|
github3.py==1.3.0
|
||||||
hunter==3.3.1
|
hunter==3.3.1
|
||||||
idna==2.10
|
idna==2.10
|
||||||
|
|
@ -16,11 +15,11 @@ packaging==20.4
|
||||||
pycparser==2.20
|
pycparser==2.20
|
||||||
Pympler==0.9
|
Pympler==0.9
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
PyQt-builder==1.5.0
|
PyQt-builder==1.6.0
|
||||||
python-dateutil==2.8.1
|
python-dateutil==2.8.1
|
||||||
requests==2.24.0
|
requests==2.25.0
|
||||||
sip==5.4.0
|
sip==5.5.0
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
toml==0.10.1
|
toml==0.10.2
|
||||||
uritemplate==3.0.1
|
uritemplate==3.0.1
|
||||||
# urllib3==1.25.11
|
# urllib3==1.26.2
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
hunter
|
hunter
|
||||||
cssutils
|
|
||||||
pympler
|
pympler
|
||||||
github3.py
|
github3.py
|
||||||
bump2version
|
bump2version
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
attrs==20.2.0
|
attrs==20.3.0
|
||||||
flake8==3.8.4
|
flake8==3.8.4
|
||||||
flake8-bugbear==20.1.4
|
flake8-bugbear==20.11.1
|
||||||
flake8-builtins==1.5.3
|
flake8-builtins==1.5.3
|
||||||
flake8-comprehensions==3.3.0
|
flake8-comprehensions==3.3.0
|
||||||
flake8-copyright==0.2.2
|
flake8-copyright==0.2.2
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
diff-cover==4.0.1
|
diff-cover==4.0.1
|
||||||
inflect==4.1.0
|
inflect==5.0.2
|
||||||
Jinja2==2.11.2
|
Jinja2==2.11.2
|
||||||
jinja2-pluralize==0.3.0
|
jinja2-pluralize==0.3.0
|
||||||
lxml==4.6.1
|
lxml==4.6.1
|
||||||
|
|
@ -10,6 +10,6 @@ mypy==0.790
|
||||||
mypy-extensions==0.4.3
|
mypy-extensions==0.4.3
|
||||||
pluggy==0.13.1
|
pluggy==0.13.1
|
||||||
Pygments==2.7.2
|
Pygments==2.7.2
|
||||||
-e git+https://github.com/stlehmann/PyQt5-stubs.git@811462b34ee151b898289ae8f1de8af30c690c55#egg=PyQt5_stubs
|
-e git+https://github.com/stlehmann/PyQt5-stubs.git@704207e90bee7b36ec9861dfa6b39f06a27c6718#egg=PyQt5_stubs
|
||||||
typed-ast==1.4.1
|
typed-ast==1.4.1
|
||||||
typing-extensions==3.7.4.3
|
typing-extensions==3.7.4.3
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
altgraph==0.17
|
altgraph==0.17
|
||||||
pyinstaller==4.0
|
pyinstaller==4.1
|
||||||
pyinstaller-hooks-contrib==2020.9
|
pyinstaller-hooks-contrib==2020.10
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
astroid==2.3.3 # rq.filter: < 2.4
|
astroid==2.3.3 # rq.filter: < 2.4
|
||||||
certifi==2020.6.20
|
certifi==2020.11.8
|
||||||
cffi==1.14.3
|
cffi==1.14.4
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
cryptography==3.2
|
cryptography==3.2.1
|
||||||
github3.py==1.3.0
|
github3.py==1.3.0
|
||||||
idna==2.10
|
idna==2.10
|
||||||
isort==4.3.21
|
isort==4.3.21
|
||||||
|
|
@ -15,9 +15,9 @@ pycparser==2.20
|
||||||
pylint==2.4.4 # rq.filter: < 2.5
|
pylint==2.4.4 # rq.filter: < 2.5
|
||||||
python-dateutil==2.8.1
|
python-dateutil==2.8.1
|
||||||
./scripts/dev/pylint_checkers
|
./scripts/dev/pylint_checkers
|
||||||
requests==2.24.0
|
requests==2.25.0
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
typed-ast==1.4.1 ; python_version<"3.8"
|
typed-ast==1.4.1 ; python_version<"3.8"
|
||||||
uritemplate==3.0.1
|
uritemplate==3.0.1
|
||||||
# urllib3==1.25.11
|
# urllib3==1.26.2
|
||||||
wrapt==1.11.2
|
wrapt==1.11.2
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
|
||||||
|
|
||||||
PyQt5==5.10.1 # rq.filter: < 5.11
|
|
||||||
sip==4.19.8 # rq.filter: < 5
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
#@ filter: PyQt5 < 5.11
|
|
||||||
PyQt5 >= 5.10, < 5.11
|
|
||||||
#@ filter: sip < 5
|
|
||||||
sip < 5
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
|
||||||
|
|
||||||
PyQt5==5.11.3 # rq.filter: < 5.12
|
|
||||||
PyQt5-sip==4.19.19 # rq.filter: < 4.20
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
#@ filter: PyQt5 < 5.12
|
|
||||||
PyQt5 >= 5.11, < 5.12
|
|
||||||
|
|
||||||
#@ filter: PyQt5-sip < 4.20
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
PyQt5==5.15.1 # rq.filter: < 6
|
PyQt5==5.15.2 # rq.filter: < 6
|
||||||
PyQt5-sip==12.8.1
|
PyQt5-sip==12.8.1
|
||||||
PyQtWebEngine==5.15.1 # rq.filter: < 6
|
PyQtWebEngine==5.15.2 # rq.filter: < 6
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
|
||||||
|
|
||||||
PyQt5==5.7.1 # rq.filter: < 5.8
|
|
||||||
sip==4.19.8 # rq.filter: < 5
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
#@ filter: PyQt5 < 5.8
|
|
||||||
#@ filter: sip < 5
|
|
||||||
PyQt5 >= 5.7, < 5.8
|
|
||||||
sip < 5
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
|
||||||
|
|
||||||
PyQt5==5.9.2 # rq.filter: < 5.10
|
|
||||||
sip==4.19.8 # rq.filter: < 5
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
#@ filter: PyQt5 < 5.10
|
|
||||||
PyQt5 >= 5.9, < 5.10
|
|
||||||
#@ filter: sip < 5
|
|
||||||
sip < 5
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
PyQt5==5.15.1
|
PyQt5==5.15.2
|
||||||
PyQt5-sip==12.8.1
|
PyQt5-sip==12.8.1
|
||||||
PyQtWebEngine==5.15.0
|
PyQtWebEngine==5.15.2
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
PyQt5
|
PyQt5
|
||||||
PyQtWebEngine!=5.15.1
|
PyQtWebEngine
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,5 @@ Pygments
|
||||||
pyPEG2
|
pyPEG2
|
||||||
PyYAML
|
PyYAML
|
||||||
colorama
|
colorama
|
||||||
cssutils
|
|
||||||
attrs
|
attrs
|
||||||
adblock # Optional, for improved adblocking
|
adblock # Optional, for improved adblocking
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
alabaster==0.7.12
|
alabaster==0.7.12
|
||||||
Babel==2.8.0
|
Babel==2.9.0
|
||||||
certifi==2020.6.20
|
certifi==2020.11.8
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
docutils==0.16
|
docutils==0.16
|
||||||
idna==2.10
|
idna==2.10
|
||||||
|
|
@ -12,15 +12,15 @@ MarkupSafe==1.1.1
|
||||||
packaging==20.4
|
packaging==20.4
|
||||||
Pygments==2.7.2
|
Pygments==2.7.2
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
pytz==2020.1
|
pytz==2020.4
|
||||||
requests==2.24.0
|
requests==2.25.0
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
snowballstemmer==2.0.0
|
snowballstemmer==2.0.0
|
||||||
Sphinx==3.2.1
|
Sphinx==3.3.1
|
||||||
sphinxcontrib-applehelp==1.0.2
|
sphinxcontrib-applehelp==1.0.2
|
||||||
sphinxcontrib-devhelp==1.0.2
|
sphinxcontrib-devhelp==1.0.2
|
||||||
sphinxcontrib-htmlhelp==1.0.3
|
sphinxcontrib-htmlhelp==1.0.3
|
||||||
sphinxcontrib-jsmath==1.0.1
|
sphinxcontrib-jsmath==1.0.1
|
||||||
sphinxcontrib-qthelp==1.0.3
|
sphinxcontrib-qthelp==1.0.3
|
||||||
sphinxcontrib-serializinghtml==1.1.4
|
sphinxcontrib-serializinghtml==1.1.4
|
||||||
urllib3==1.25.11
|
urllib3==1.26.2
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ git+https://github.com/pallets/werkzeug.git
|
||||||
## qutebrowser dependencies
|
## qutebrowser dependencies
|
||||||
|
|
||||||
git+https://github.com/tartley/colorama.git
|
git+https://github.com/tartley/colorama.git
|
||||||
hg+https://bitbucket.org/cthedot/cssutils
|
|
||||||
git+https://github.com/pallets/jinja.git
|
git+https://github.com/pallets/jinja.git
|
||||||
git+https://github.com/pallets/markupsafe.git
|
git+https://github.com/pallets/markupsafe.git
|
||||||
hg+http://bitbucket.org/birkenfeld/pygments-main
|
hg+http://bitbucket.org/birkenfeld/pygments-main
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
apipkg==1.5
|
apipkg==1.5
|
||||||
attrs==20.2.0
|
attrs==20.3.0
|
||||||
beautifulsoup4==4.9.3
|
beautifulsoup4==4.9.3
|
||||||
certifi==2020.6.20
|
certifi==2020.11.8
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
cheroot==8.4.5
|
cheroot==8.4.7
|
||||||
click==7.1.2
|
click==7.1.2
|
||||||
# colorama==0.4.4
|
# colorama==0.4.4
|
||||||
coverage==5.3
|
coverage==5.3
|
||||||
|
|
@ -15,7 +15,7 @@ filelock==3.0.12
|
||||||
Flask==1.1.2
|
Flask==1.1.2
|
||||||
glob2==0.7
|
glob2==0.7
|
||||||
hunter==3.3.1
|
hunter==3.3.1
|
||||||
hypothesis==5.38.0
|
hypothesis==5.41.3
|
||||||
icdiff==1.9.1
|
icdiff==1.9.1
|
||||||
idna==2.10
|
idna==2.10
|
||||||
iniconfig==1.1.1
|
iniconfig==1.1.1
|
||||||
|
|
@ -25,7 +25,7 @@ jaraco.functools==3.0.1
|
||||||
Mako==1.1.3
|
Mako==1.1.3
|
||||||
manhole==1.6.0
|
manhole==1.6.0
|
||||||
# MarkupSafe==1.1.1
|
# MarkupSafe==1.1.1
|
||||||
more-itertools==8.5.0
|
more-itertools==8.6.0
|
||||||
packaging==20.4
|
packaging==20.4
|
||||||
parse==1.18.0
|
parse==1.18.0
|
||||||
parse-type==0.5.2
|
parse-type==0.5.2
|
||||||
|
|
@ -35,7 +35,7 @@ py==1.9.0
|
||||||
py-cpuinfo==7.0.0
|
py-cpuinfo==7.0.0
|
||||||
Pygments==2.7.2
|
Pygments==2.7.2
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
pytest==6.1.1
|
pytest==6.1.2
|
||||||
pytest-bdd==4.0.1
|
pytest-bdd==4.0.1
|
||||||
pytest-benchmark==3.2.3
|
pytest-benchmark==3.2.3
|
||||||
pytest-clarity==0.3.0a0
|
pytest-clarity==0.3.0a0
|
||||||
|
|
@ -45,19 +45,19 @@ pytest-icdiff==0.5
|
||||||
pytest-instafail==0.4.2
|
pytest-instafail==0.4.2
|
||||||
pytest-mock==3.3.1
|
pytest-mock==3.3.1
|
||||||
pytest-qt==3.3.0
|
pytest-qt==3.3.0
|
||||||
pytest-repeat==0.8.0
|
pytest-repeat==0.9.1
|
||||||
pytest-rerunfailures==9.1.1
|
pytest-rerunfailures==9.1.1
|
||||||
pytest-xdist==2.1.0
|
pytest-xdist==2.1.0
|
||||||
pytest-xvfb==2.0.0
|
pytest-xvfb==2.0.0
|
||||||
PyVirtualDisplay==1.3.2
|
PyVirtualDisplay==1.3.2
|
||||||
requests==2.24.0
|
requests==2.25.0
|
||||||
requests-file==1.5.1
|
requests-file==1.5.1
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
sortedcontainers==2.2.2
|
sortedcontainers==2.3.0
|
||||||
soupsieve==2.0.1
|
soupsieve==2.0.1
|
||||||
termcolor==1.1.0
|
termcolor==1.1.0
|
||||||
tldextract==3.0.2
|
tldextract==3.1.0
|
||||||
toml==0.10.1
|
toml==0.10.2
|
||||||
urllib3==1.25.11
|
urllib3==1.25.11
|
||||||
vulture==2.1
|
vulture==2.1
|
||||||
Werkzeug==1.0.1
|
Werkzeug==1.0.1
|
||||||
|
|
|
||||||
|
|
@ -33,5 +33,7 @@ pytest-clarity
|
||||||
|
|
||||||
# Needed to test misc/userscripts/qute-lastpass
|
# Needed to test misc/userscripts/qute-lastpass
|
||||||
tldextract
|
tldextract
|
||||||
|
# https://github.com/urllib3/urllib3/issues/2071
|
||||||
|
urllib3!=1.26.0,!=1.26.1,!=1.26.2
|
||||||
|
|
||||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ pluggy==0.13.1
|
||||||
py==1.9.0
|
py==1.9.0
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
toml==0.10.1
|
toml==0.10.2
|
||||||
tox==3.20.1
|
tox==3.20.1
|
||||||
tox-pip-version==0.0.7
|
virtualenv==20.2.1
|
||||||
tox-venv==0.4.0
|
|
||||||
virtualenv==20.1.0
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1 @@
|
||||||
tox
|
tox
|
||||||
tox-venv
|
|
||||||
tox-pip-version
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
toml==0.10.1
|
toml==0.10.2
|
||||||
vulture==2.1
|
vulture==2.1
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
pathspec==0.8.0
|
pathspec==0.8.1
|
||||||
PyYAML==5.3.1
|
PyYAML==5.3.1
|
||||||
yamllint==1.25.0
|
yamllint==1.25.0
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,8 @@ The following userscripts can be found on their own repositories.
|
||||||
selections via Google Translate.
|
selections via Google Translate.
|
||||||
- [qute-snippets](https://github.com/Aledosim/qute-snippets): Bind text snippets to a keyword
|
- [qute-snippets](https://github.com/Aledosim/qute-snippets): Bind text snippets to a keyword
|
||||||
and retrieve they when you want.
|
and retrieve they when you want.
|
||||||
|
- [doi](https://github.com/cadadr/configuration/blob/master/qutebrowser/userscripts/doi):
|
||||||
|
Opens DOIs on Sci-Hub.
|
||||||
|
|
||||||
[Zotero]: https://www.zotero.org/
|
[Zotero]: https://www.zotero.org/
|
||||||
[Pocket]: https://getpocket.com/
|
[Pocket]: https://getpocket.com/
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,10 @@
|
||||||
# (This is unnecessarily long. I use this rarely, feel free to make this script accept parameters.)
|
# (This is unnecessarily long. I use this rarely, feel free to make this script accept parameters.)
|
||||||
#
|
#
|
||||||
|
|
||||||
[ -z "$QUTE_URL" ] && QUTE_URL='http://google.com'
|
|
||||||
|
|
||||||
url=$(echo "$QUTE_URL" | cat - "$QUTE_CONFIG_DIR/quickmarks" "$QUTE_DATA_DIR/history" | dmenu -l 15 -p qutebrowser)
|
[ -z "$QUTE_URL" ] && QUTE_URL='https://duckduckgo.com'
|
||||||
|
|
||||||
|
url=$(printf "%s\n%s" "$QUTE_URL" "$(sqlite3 -separator ' ' "$QUTE_DATA_DIR/history.sqlite" 'select title, url from CompletionHistory')" | cat "$QUTE_CONFIG_DIR/quickmarks" - | dmenu -l 15 -p qutebrowser)
|
||||||
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | grep -E "https?:" || echo "$url")
|
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | grep -E "https?:" || echo "$url")
|
||||||
|
|
||||||
[ -z "${url// }" ] && exit
|
[ -z "${url// }" ] && exit
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
|
|
||||||
# If you would like to set a custom colorscheme/font use these dirs.
|
# If you would like to set a custom colorscheme/font use these dirs.
|
||||||
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/bemenucolors
|
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/bemenucolors
|
||||||
readonly confdir=${XDG_CONFIG_HOME:-$HOME/.config}
|
|
||||||
|
|
||||||
|
|
||||||
|
readonly confdir=${XDG_CONFIG_HOME:-$HOME/.config}
|
||||||
readonly optsfile=$confdir/dmenu/bemenucolors
|
readonly optsfile=$confdir/dmenu/bemenucolors
|
||||||
|
|
||||||
create_menu() {
|
create_menu() {
|
||||||
|
|
@ -22,15 +23,13 @@ create_menu() {
|
||||||
done < "$QUTE_CONFIG_DIR"/bookmarks/urls
|
done < "$QUTE_CONFIG_DIR"/bookmarks/urls
|
||||||
|
|
||||||
# Finally history
|
# Finally history
|
||||||
while read -r _ url; do
|
printf -- '%s\n' "$(sqlite3 -separator ' ' "$QUTE_DATA_DIR/history.sqlite" 'select title, url from CompletionHistory')"
|
||||||
printf -- '%s\n' "$url"
|
|
||||||
done < "$QUTE_DATA_DIR"/history
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_selection() {
|
get_selection() {
|
||||||
opts+=(-p qutebrowser)
|
opts+=(-p qutebrowser)
|
||||||
#create_menu | dmenu -l 10 "${opts[@]}"
|
create_menu | dmenu -l 10 "${opts[@]}"
|
||||||
create_menu | bemenu -l 10 "${opts[@]}"
|
#create_menu | bemenu -l 10 "${opts[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,8 @@ markers =
|
||||||
qtwebkit_skip: Tests not applicable with QtWebKit
|
qtwebkit_skip: Tests not applicable with QtWebKit
|
||||||
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
|
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
|
||||||
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
|
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
|
||||||
js_prompt: Tests needing to display a javascript prompt
|
|
||||||
this: Used to mark tests during development
|
this: Used to mark tests during development
|
||||||
no_invalid_lines: Don't fail on unparseable lines in end2end tests
|
no_invalid_lines: Don't fail on unparseable lines in end2end tests
|
||||||
qtbug60673: Tests which are broken if the conversion from orange selection to real selection is flaky
|
|
||||||
fake_os: Fake utils.is_* to a fake operating system
|
fake_os: Fake utils.is_* to a fake operating system
|
||||||
unicode_locale: Tests which need a unicode locale to work
|
unicode_locale: Tests which need a unicode locale to work
|
||||||
qtwebkit6021_xfail: Tests which would fail on WebKit version 602.1
|
qtwebkit6021_xfail: Tests which would fail on WebKit version 602.1
|
||||||
|
|
@ -77,9 +75,11 @@ qt_log_ignore =
|
||||||
^DirectWrite: CreateFontFaceFromHDC\(\) failed .*
|
^DirectWrite: CreateFontFaceFromHDC\(\) failed .*
|
||||||
^Attribute Qt::AA_ShareOpenGLContexts must be set before QCoreApplication is created\.
|
^Attribute Qt::AA_ShareOpenGLContexts must be set before QCoreApplication is created\.
|
||||||
^QHttpNetworkConnectionPrivate::_q_hostLookupFinished could not de-queue request, failed to report HostNotFoundError
|
^QHttpNetworkConnectionPrivate::_q_hostLookupFinished could not de-queue request, failed to report HostNotFoundError
|
||||||
|
^The available OpenGL surface format was either not version 3\.2 or higher or not a Core Profile.*
|
||||||
xfail_strict = true
|
xfail_strict = true
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
error
|
error
|
||||||
# See https://github.com/HypothesisWorks/hypothesis/issues/2370
|
# See https://github.com/HypothesisWorks/hypothesis/issues/2370
|
||||||
ignore:.*which is reset between function calls but not between test cases generated by:hypothesis.errors.HypothesisDeprecationWarning
|
ignore:.*which is reset between function calls but not between test cases generated by:hypothesis.errors.HypothesisDeprecationWarning
|
||||||
|
default:Test process .* failed to terminate!:UserWarning
|
||||||
faulthandler_timeout = 90
|
faulthandler_timeout = 90
|
||||||
|
|
|
||||||
|
|
@ -75,4 +75,6 @@ def download_temp(url: QUrl) -> TempDownload:
|
||||||
fobj.name = 'temporary: ' + url.host()
|
fobj.name = 'temporary: ' + url.host()
|
||||||
target = downloads.FileObjDownloadTarget(fobj)
|
target = downloads.FileObjDownloadTarget(fobj)
|
||||||
download_manager = objreg.get('qtnetwork-download-manager')
|
download_manager = objreg.get('qtnetwork-download-manager')
|
||||||
return download_manager.get(url, target=target, auto_remove=True)
|
# cache=False is set as a WORKAROUND for MS Defender thinking we're a trojan
|
||||||
|
# downloader when caching the hostblock list...
|
||||||
|
return download_manager.get(url, target=target, auto_remove=True, cache=False)
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ import functools
|
||||||
import tempfile
|
import tempfile
|
||||||
import datetime
|
import datetime
|
||||||
import argparse
|
import argparse
|
||||||
import typing
|
from typing import Iterable, Optional, cast
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QApplication, QWidget
|
from PyQt5.QtWidgets import QApplication, QWidget
|
||||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon
|
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon
|
||||||
|
|
@ -74,7 +74,7 @@ from qutebrowser.misc import utilcmds
|
||||||
# pylint: enable=unused-import
|
# pylint: enable=unused-import
|
||||||
|
|
||||||
|
|
||||||
q_app = typing.cast(QApplication, None)
|
q_app = cast(QApplication, None)
|
||||||
|
|
||||||
|
|
||||||
def run(args):
|
def run(args):
|
||||||
|
|
@ -199,7 +199,7 @@ def _init_pulseaudio():
|
||||||
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-85363
|
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-85363
|
||||||
|
|
||||||
Affected Qt versions:
|
Affected Qt versions:
|
||||||
- Older than 5.11
|
- Older than 5.11 (which is unsupported)
|
||||||
- 5.14.0 to 5.15.0 (inclusive)
|
- 5.14.0 to 5.15.0 (inclusive)
|
||||||
|
|
||||||
However, we set this on all versions so that qutebrowser's icon gets picked
|
However, we set this on all versions so that qutebrowser's icon gets picked
|
||||||
|
|
@ -256,7 +256,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||||
if command_target in {'window', 'private-window'}:
|
if command_target in {'window', 'private-window'}:
|
||||||
command_target = 'tab-silent'
|
command_target = 'tab-silent'
|
||||||
|
|
||||||
win_id = None # type: typing.Optional[int]
|
win_id: Optional[int] = None
|
||||||
|
|
||||||
if via_ipc and not args:
|
if via_ipc and not args:
|
||||||
win_id = mainwindow.get_window(via_ipc=via_ipc,
|
win_id = mainwindow.get_window(via_ipc=via_ipc,
|
||||||
|
|
@ -325,7 +325,7 @@ def _open_startpage(win_id=None):
|
||||||
If set, open the startpage in the given window.
|
If set, open the startpage in the given window.
|
||||||
"""
|
"""
|
||||||
if win_id is not None:
|
if win_id is not None:
|
||||||
window_ids = [win_id] # type: typing.Iterable[int]
|
window_ids: Iterable[int] = [win_id]
|
||||||
else:
|
else:
|
||||||
window_ids = objreg.window_registry
|
window_ids = objreg.window_registry
|
||||||
for cur_win_id in list(window_ids): # Copying as the dict could change
|
for cur_win_id in list(window_ids): # Copying as the dict could change
|
||||||
|
|
@ -366,10 +366,6 @@ def _open_special_pages(args):
|
||||||
objects.backend == usertypes.Backend.QtWebKit,
|
objects.backend == usertypes.Backend.QtWebKit,
|
||||||
'qute://warning/webkit'),
|
'qute://warning/webkit'),
|
||||||
|
|
||||||
('old-qt-warning-shown',
|
|
||||||
not qtutils.version_check('5.11'),
|
|
||||||
'qute://warning/old-qt'),
|
|
||||||
|
|
||||||
('session-warning-shown',
|
('session-warning-shown',
|
||||||
qtutils.version_check('5.15', compiled=False),
|
qtutils.version_check('5.15', compiled=False),
|
||||||
'qute://warning/sessions'),
|
'qute://warning/sessions'),
|
||||||
|
|
@ -536,7 +532,9 @@ class Application(QApplication):
|
||||||
self.launch_time = datetime.datetime.now()
|
self.launch_time = datetime.datetime.now()
|
||||||
self.focusObjectChanged.connect( # type: ignore[attr-defined]
|
self.focusObjectChanged.connect( # type: ignore[attr-defined]
|
||||||
self.on_focus_object_changed)
|
self.on_focus_object_changed)
|
||||||
|
|
||||||
self.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
self.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
||||||
|
self.setAttribute(Qt.AA_MacDontSwapCtrlAndMeta, True)
|
||||||
|
|
||||||
self.new_window.connect(self._on_new_window)
|
self.new_window.connect(self._on_new_window)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -230,14 +230,6 @@ class AbstractPrinting:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def check_printer_support(self) -> None:
|
|
||||||
"""Check whether writing to a printer is supported.
|
|
||||||
|
|
||||||
If it's not supported (by the current Qt version), a WebTabError is
|
|
||||||
raised.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def check_preview_support(self) -> None:
|
def check_preview_support(self) -> None:
|
||||||
"""Check whether showing a print preview is supported.
|
"""Check whether showing a print preview is supported.
|
||||||
|
|
||||||
|
|
@ -263,8 +255,6 @@ class AbstractPrinting:
|
||||||
|
|
||||||
def show_dialog(self) -> None:
|
def show_dialog(self) -> None:
|
||||||
"""Print with a QPrintDialog."""
|
"""Print with a QPrintDialog."""
|
||||||
self.check_printer_support()
|
|
||||||
|
|
||||||
def print_callback(ok: bool) -> None:
|
def print_callback(ok: bool) -> None:
|
||||||
"""Called when printing finished."""
|
"""Called when printing finished."""
|
||||||
if not ok:
|
if not ok:
|
||||||
|
|
@ -1058,9 +1048,6 @@ class AbstractTab(QWidget):
|
||||||
self.data.last_navigation = navigation
|
self.data.last_navigation = navigation
|
||||||
|
|
||||||
if not navigation.url.isValid():
|
if not navigation.url.isValid():
|
||||||
# Also a WORKAROUND for missing IDNA 2008 support in QUrl, see
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-60364
|
|
||||||
|
|
||||||
if navigation.navigation_type == navigation.Type.link_clicked:
|
if navigation.navigation_type == navigation.Type.link_clicked:
|
||||||
msg = urlutils.get_errstring(navigation.url,
|
msg = urlutils.get_errstring(navigation.url,
|
||||||
"Invalid link clicked")
|
"Invalid link clicked")
|
||||||
|
|
@ -1129,14 +1116,11 @@ class AbstractTab(QWidget):
|
||||||
def load_status(self) -> usertypes.LoadStatus:
|
def load_status(self) -> usertypes.LoadStatus:
|
||||||
return self._load_status
|
return self._load_status
|
||||||
|
|
||||||
def _load_url_prepare(self, url: QUrl, *,
|
def _load_url_prepare(self, url: QUrl) -> None:
|
||||||
emit_before_load_started: bool = True) -> None:
|
|
||||||
qtutils.ensure_valid(url)
|
qtutils.ensure_valid(url)
|
||||||
if emit_before_load_started:
|
self.before_load_started.emit(url)
|
||||||
self.before_load_started.emit(url)
|
|
||||||
|
|
||||||
def load_url(self, url: QUrl, *,
|
def load_url(self, url: QUrl) -> None:
|
||||||
emit_before_load_started: bool = True) -> None:
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def reload(self, *, force: bool = False) -> None:
|
def reload(self, *, force: bool = False) -> None:
|
||||||
|
|
|
||||||
|
|
@ -673,7 +673,7 @@ class CommandDispatcher:
|
||||||
|
|
||||||
url = QUrl(self._current_url())
|
url = QUrl(self._current_url())
|
||||||
url_query = QUrlQuery()
|
url_query = QUrlQuery()
|
||||||
url_query_str = urlutils.query_string(url)
|
url_query_str = url.query()
|
||||||
if '&' not in url_query_str and ';' in url_query_str:
|
if '&' not in url_query_str and ';' in url_query_str:
|
||||||
url_query.setQueryDelimiters('=', ';')
|
url_query.setQueryDelimiters('=', ';')
|
||||||
url_query.setQuery(url_query_str)
|
url_query.setQuery(url_query_str)
|
||||||
|
|
@ -1304,7 +1304,7 @@ class CommandDispatcher:
|
||||||
if mhtml_:
|
if mhtml_:
|
||||||
raise cmdutils.CommandError("Can only download the current "
|
raise cmdutils.CommandError("Can only download the current "
|
||||||
"page as mhtml.")
|
"page as mhtml.")
|
||||||
url = urlutils.qurl_from_user_input(url)
|
url = QUrl.fromUserInput(url)
|
||||||
urlutils.raise_cmdexc_if_invalid(url)
|
urlutils.raise_cmdexc_if_invalid(url)
|
||||||
download_manager.get(url, target=target)
|
download_manager.get(url, target=target)
|
||||||
elif mhtml_:
|
elif mhtml_:
|
||||||
|
|
|
||||||
|
|
@ -560,8 +560,8 @@ class AbstractDownloadItem(QObject):
|
||||||
elif self.stats.percentage() is None:
|
elif self.stats.percentage() is None:
|
||||||
return start
|
return start
|
||||||
else:
|
else:
|
||||||
return utils.interpolate_color(start, stop,
|
return qtutils.interpolate_color(
|
||||||
self.stats.percentage(), system)
|
start, stop, self.stats.percentage(), system)
|
||||||
|
|
||||||
def _do_cancel(self):
|
def _do_cancel(self):
|
||||||
"""Actual cancel implementation."""
|
"""Actual cancel implementation."""
|
||||||
|
|
@ -574,6 +574,7 @@ class AbstractDownloadItem(QObject):
|
||||||
Args:
|
Args:
|
||||||
remove_data: Whether to remove the downloaded data.
|
remove_data: Whether to remove the downloaded data.
|
||||||
"""
|
"""
|
||||||
|
assert not self.done
|
||||||
self._do_cancel()
|
self._do_cancel()
|
||||||
log.downloads.debug("cancelled")
|
log.downloads.debug("cancelled")
|
||||||
if remove_data:
|
if remove_data:
|
||||||
|
|
@ -982,7 +983,8 @@ class AbstractDownloadManager(QObject):
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
"""Cancel all downloads when shutting down."""
|
"""Cancel all downloads when shutting down."""
|
||||||
for download in self.downloads:
|
for download in self.downloads:
|
||||||
download.cancel(remove_data=False)
|
if not download.done:
|
||||||
|
download.cancel(remove_data=False)
|
||||||
|
|
||||||
|
|
||||||
class DownloadModel(QAbstractListModel):
|
class DownloadModel(QAbstractListModel):
|
||||||
|
|
|
||||||
|
|
@ -22,36 +22,12 @@
|
||||||
import functools
|
import functools
|
||||||
from typing import Callable, MutableSequence, Tuple, Union
|
from typing import Callable, MutableSequence, Tuple, Union
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
|
from PyQt5.QtCore import pyqtSlot, QSize, Qt
|
||||||
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
||||||
|
|
||||||
from qutebrowser.browser import downloads
|
from qutebrowser.browser import downloads
|
||||||
from qutebrowser.config import stylesheet
|
from qutebrowser.config import stylesheet
|
||||||
from qutebrowser.utils import qtutils, utils
|
from qutebrowser.utils import qtutils, utils
|
||||||
from qutebrowser.qt import sip
|
|
||||||
|
|
||||||
|
|
||||||
def update_geometry(obj):
|
|
||||||
"""Weird WORKAROUND for some weird PyQt bug (probably).
|
|
||||||
|
|
||||||
This actually should be a method of DownloadView, but for some reason the
|
|
||||||
rowsInserted/rowsRemoved signals don't get disconnected from this method
|
|
||||||
when the DownloadView is deleted from Qt (e.g. by closing a window).
|
|
||||||
|
|
||||||
Here we check if obj ("self") was deleted and just ignore the event if so.
|
|
||||||
|
|
||||||
Original bug: https://github.com/qutebrowser/qutebrowser/issues/167
|
|
||||||
Workaround bug: https://github.com/qutebrowser/qutebrowser/issues/171
|
|
||||||
"""
|
|
||||||
def _update_geometry():
|
|
||||||
"""Actually update the geometry if the object still exists."""
|
|
||||||
if sip.isdeleted(obj):
|
|
||||||
return
|
|
||||||
obj.updateGeometry()
|
|
||||||
|
|
||||||
# If we don't use a singleShot QTimer, the geometry isn't updated correctly
|
|
||||||
# and won't include the new item.
|
|
||||||
QTimer.singleShot(0, _update_geometry)
|
|
||||||
|
|
||||||
|
|
||||||
_ActionListType = MutableSequence[
|
_ActionListType = MutableSequence[
|
||||||
|
|
@ -93,9 +69,9 @@ class DownloadView(QListView):
|
||||||
self.setFlow(QListView.LeftToRight)
|
self.setFlow(QListView.LeftToRight)
|
||||||
self.setSpacing(1)
|
self.setSpacing(1)
|
||||||
self._menu = None
|
self._menu = None
|
||||||
model.rowsInserted.connect(functools.partial(update_geometry, self))
|
model.rowsInserted.connect(self._update_geometry)
|
||||||
model.rowsRemoved.connect(functools.partial(update_geometry, self))
|
model.rowsRemoved.connect(self._update_geometry)
|
||||||
model.dataChanged.connect(functools.partial(update_geometry, self))
|
model.dataChanged.connect(self._update_geometry)
|
||||||
self.setModel(model)
|
self.setModel(model)
|
||||||
self.setWrapping(True)
|
self.setWrapping(True)
|
||||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
|
@ -110,6 +86,15 @@ class DownloadView(QListView):
|
||||||
count = model.rowCount()
|
count = model.rowCount()
|
||||||
return utils.get_repr(self, count=count)
|
return utils.get_repr(self, count=count)
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def _update_geometry(self):
|
||||||
|
"""Wrapper to call updateGeometry.
|
||||||
|
|
||||||
|
For some reason, this is needed so that PyQt disconnects the signals and handles
|
||||||
|
arguments correctly. Probably a WORKAROUND for an unknown PyQt bug.
|
||||||
|
"""
|
||||||
|
self.updateGeometry()
|
||||||
|
|
||||||
@pyqtSlot(bool)
|
@pyqtSlot(bool)
|
||||||
def on_fullscreen_requested(self, on):
|
def on_fullscreen_requested(self, on):
|
||||||
"""Hide/show the downloadview when entering/leaving fullscreen."""
|
"""Hide/show the downloadview when entering/leaving fullscreen."""
|
||||||
|
|
|
||||||
|
|
@ -22,48 +22,11 @@
|
||||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import message, log, usertypes, qtutils, objreg
|
from qutebrowser.utils import message, log, usertypes, qtutils
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
|
|
||||||
|
|
||||||
class FocusWorkaroundEventFilter(QObject):
|
|
||||||
|
|
||||||
"""An event filter working Qt 5.11 keyboard focus issues.
|
|
||||||
|
|
||||||
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, win_id, widget, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self._win_id = win_id
|
|
||||||
self._widget = widget
|
|
||||||
|
|
||||||
def eventFilter(self, _obj, event):
|
|
||||||
"""Act on ChildAdded events."""
|
|
||||||
if event.type() != QEvent.ChildAdded:
|
|
||||||
return False
|
|
||||||
|
|
||||||
pass_modes = [usertypes.KeyMode.command,
|
|
||||||
usertypes.KeyMode.prompt,
|
|
||||||
usertypes.KeyMode.yesno]
|
|
||||||
|
|
||||||
if modeman.instance(self._win_id).mode in pass_modes:
|
|
||||||
return False
|
|
||||||
|
|
||||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
|
||||||
window=self._win_id)
|
|
||||||
current_index = tabbed_browser.widget.currentIndex()
|
|
||||||
try:
|
|
||||||
widget_index = tabbed_browser.widget.indexOf(self._widget.parent())
|
|
||||||
except RuntimeError:
|
|
||||||
widget_index = -1
|
|
||||||
if current_index == widget_index:
|
|
||||||
QTimer.singleShot(0, self._widget.setFocus)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ChildEventFilter(QObject):
|
class ChildEventFilter(QObject):
|
||||||
|
|
||||||
"""An event filter re-adding TabEventFilter on ChildEvent.
|
"""An event filter re-adding TabEventFilter on ChildEvent.
|
||||||
|
|
@ -204,13 +167,6 @@ class TabEventFilter(QObject):
|
||||||
message.info("Zoom level: {}%".format(perc), replace=True)
|
message.info("Zoom level: {}%".format(perc), replace=True)
|
||||||
self._tab.zoom.set_factor(factor)
|
self._tab.zoom.set_factor(factor)
|
||||||
return True
|
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:
|
|
||||||
self._tab.scroller.right()
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -226,7 +182,6 @@ class TabEventFilter(QObject):
|
||||||
True if the event should be filtered, False otherwise.
|
True if the event should be filtered, False otherwise.
|
||||||
"""
|
"""
|
||||||
return (e.isAutoRepeat() and
|
return (e.isAutoRepeat() and
|
||||||
qtutils.version_check('5.10', compiled=False) and
|
|
||||||
not qtutils.version_check('5.14', compiled=False) and
|
not qtutils.version_check('5.14', compiled=False) and
|
||||||
objects.backend == usertypes.Backend.QtWebEngine)
|
objects.backend == usertypes.Backend.QtWebEngine)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,7 @@ import attr
|
||||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||||
|
|
||||||
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
|
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
|
||||||
javascript, urlmatch, version, usertypes,
|
javascript, urlmatch, version, usertypes)
|
||||||
qtutils)
|
|
||||||
from qutebrowser.api import cmdutils
|
from qutebrowser.api import cmdutils
|
||||||
from qutebrowser.browser import downloads
|
from qutebrowser.browser import downloads
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
|
|
@ -125,8 +124,7 @@ class GreasemonkeyScript:
|
||||||
def needs_document_end_workaround(self):
|
def needs_document_end_workaround(self):
|
||||||
"""Check whether to force @run-at document-end.
|
"""Check whether to force @run-at document-end.
|
||||||
|
|
||||||
This needs to be done on QtWebEngine with Qt 5.12 for known-broken
|
This needs to be done on QtWebEngine (since Qt 5.12) for known-broken scripts.
|
||||||
scripts.
|
|
||||||
|
|
||||||
On Qt 5.12, accessing the DOM isn't possible with "@run-at
|
On Qt 5.12, accessing the DOM isn't possible with "@run-at
|
||||||
document-start". It was documented to be impossible before, but seems
|
document-start". It was documented to be impossible before, but seems
|
||||||
|
|
@ -135,12 +133,11 @@ class GreasemonkeyScript:
|
||||||
However, some scripts do DOM access with "@run-at document-start". Fix
|
However, some scripts do DOM access with "@run-at document-start". Fix
|
||||||
those by forcing them to use document-end instead.
|
those by forcing them to use document-end instead.
|
||||||
"""
|
"""
|
||||||
if objects.backend != usertypes.Backend.QtWebEngine:
|
if objects.backend == usertypes.Backend.QtWebKit:
|
||||||
assert objects.backend == usertypes.Backend.QtWebKit, objects.backend
|
|
||||||
return False
|
|
||||||
elif not qtutils.version_check('5.12', compiled=False):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
assert objects.backend == usertypes.Backend.QtWebEngine, objects.backend
|
||||||
|
|
||||||
broken_scripts = [
|
broken_scripts = [
|
||||||
('http://userstyles.org', None),
|
('http://userstyles.org', None),
|
||||||
('https://github.com/ParticleCore', 'Iridium'),
|
('https://github.com/ParticleCore', 'Iridium'),
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,7 @@ def create(*, splitter: 'miscwidgets.InspectorSplitter',
|
||||||
# argument and to avoid circular imports.
|
# argument and to avoid circular imports.
|
||||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||||
from qutebrowser.browser.webengine import webengineinspector
|
from qutebrowser.browser.webengine import webengineinspector
|
||||||
if webengineinspector.supports_new():
|
return webengineinspector.WebEngineInspector(splitter, win_id, parent)
|
||||||
return webengineinspector.WebEngineInspector(
|
|
||||||
splitter, win_id, parent)
|
|
||||||
else:
|
|
||||||
return webengineinspector.LegacyWebEngineInspector(
|
|
||||||
splitter, win_id, parent)
|
|
||||||
elif objects.backend == usertypes.Backend.QtWebKit:
|
elif objects.backend == usertypes.Backend.QtWebKit:
|
||||||
from qutebrowser.browser.webkit import webkitinspector
|
from qutebrowser.browser.webkit import webkitinspector
|
||||||
return webkitinspector.WebKitInspector(splitter, win_id, parent)
|
return webkitinspector.WebKitInspector(splitter, win_id, parent)
|
||||||
|
|
|
||||||
|
|
@ -134,13 +134,13 @@ def path_up(url, count):
|
||||||
"""
|
"""
|
||||||
urlutils.ensure_valid(url)
|
urlutils.ensure_valid(url)
|
||||||
url = url.adjusted(QUrl.RemoveFragment | QUrl.RemoveQuery)
|
url = url.adjusted(QUrl.RemoveFragment | QUrl.RemoveQuery)
|
||||||
path = url.path()
|
path = url.path(QUrl.FullyEncoded)
|
||||||
if not path or path == '/':
|
if not path or path == '/':
|
||||||
raise Error("Can't go up!")
|
raise Error("Can't go up!")
|
||||||
for _i in range(0, min(count, path.count('/'))):
|
for _i in range(0, min(count, path.count('/'))):
|
||||||
path = posixpath.join(path, posixpath.pardir)
|
path = posixpath.join(path, posixpath.pardir)
|
||||||
path = posixpath.normpath(path)
|
path = posixpath.normpath(path)
|
||||||
url.setPath(path)
|
url.setPath(path, QUrl.StrictMode)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,7 @@ import os
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, QUrlQuery
|
from PyQt5.QtCore import QUrl, QUrlQuery
|
||||||
|
|
||||||
from qutebrowser.utils import (utils, javascript, jinja, qtutils, usertypes,
|
from qutebrowser.utils import utils, javascript, jinja, standarddir, log
|
||||||
standarddir, log)
|
|
||||||
from qutebrowser.misc import objects
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -104,29 +102,17 @@ def _generate_pdfjs_script(filename):
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
if (typeof window.PDFJS !== 'undefined') {
|
if (typeof window.PDFJS !== 'undefined') {
|
||||||
// v1.x
|
// v1.x
|
||||||
{% if disable_create_object_url %}
|
|
||||||
window.PDFJS.disableCreateObjectURL = true;
|
|
||||||
{% endif %}
|
|
||||||
window.PDFJS.verbosity = window.PDFJS.VERBOSITY_LEVELS.info;
|
window.PDFJS.verbosity = window.PDFJS.VERBOSITY_LEVELS.info;
|
||||||
} else {
|
} else {
|
||||||
// v2.x
|
// v2.x
|
||||||
const options = window.PDFViewerApplicationOptions;
|
const options = window.PDFViewerApplicationOptions;
|
||||||
{% if disable_create_object_url %}
|
|
||||||
options.set('disableCreateObjectURL', true);
|
|
||||||
{% endif %}
|
|
||||||
options.set('verbosity', pdfjsLib.VerbosityLevel.INFOS);
|
options.set('verbosity', pdfjsLib.VerbosityLevel.INFOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewer = window.PDFView || window.PDFViewerApplication;
|
const viewer = window.PDFView || window.PDFViewerApplication;
|
||||||
viewer.open({{ url }});
|
viewer.open({{ url }});
|
||||||
});
|
});
|
||||||
""").render(
|
""").render(url=js_url)
|
||||||
url=js_url,
|
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70420
|
|
||||||
disable_create_object_url=(
|
|
||||||
not qtutils.version_check('5.12') and
|
|
||||||
not qtutils.version_check('5.7.1', exact=True, compiled=False) and
|
|
||||||
objects.backend == usertypes.Backend.QtWebEngine))
|
|
||||||
|
|
||||||
|
|
||||||
def get_pdfjs_res_and_path(path):
|
def get_pdfjs_res_and_path(path):
|
||||||
|
|
|
||||||
|
|
@ -181,11 +181,16 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||||
assert self.done
|
assert self.done
|
||||||
assert not self.successful
|
assert not self.successful
|
||||||
assert self._retry_info is not None
|
assert self._retry_info is not None
|
||||||
|
|
||||||
|
# Not calling self.cancel() here because the download is done (albeit
|
||||||
|
# unsuccessfully)
|
||||||
|
self.remove()
|
||||||
|
self.delete()
|
||||||
|
|
||||||
new_reply = self._retry_info.manager.get(self._retry_info.request)
|
new_reply = self._retry_info.manager.get(self._retry_info.request)
|
||||||
new_download = self._manager.fetch(new_reply,
|
new_download = self._manager.fetch(new_reply,
|
||||||
suggested_filename=self.basename)
|
suggested_filename=self.basename)
|
||||||
self.adopt_download.emit(new_download)
|
self.adopt_download.emit(new_download)
|
||||||
self.cancel()
|
|
||||||
|
|
||||||
def _get_open_filename(self):
|
def _get_open_filename(self):
|
||||||
filename = self._filename
|
filename = self._filename
|
||||||
|
|
@ -414,11 +419,12 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||||
private=config.val.content.private_browsing, parent=self)
|
private=config.val.content.private_browsing, parent=self)
|
||||||
|
|
||||||
@pyqtSlot('QUrl')
|
@pyqtSlot('QUrl')
|
||||||
def get(self, url, **kwargs):
|
def get(self, url, cache=True, **kwargs):
|
||||||
"""Start a download with a link URL.
|
"""Start a download with a link URL.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: The URL to get, as QUrl
|
url: The URL to get, as QUrl
|
||||||
|
cache: If set to False, don't cache the response.
|
||||||
**kwargs: passed to get_request().
|
**kwargs: passed to get_request().
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
|
|
@ -432,6 +438,9 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||||
user_agent = websettings.user_agent(url)
|
user_agent = websettings.user_agent(url)
|
||||||
req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
|
req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
|
||||||
|
|
||||||
|
if not cache:
|
||||||
|
req.setAttribute(QNetworkRequest.CacheSaveControlAttribute, False)
|
||||||
|
|
||||||
return self.get_request(req, **kwargs)
|
return self.get_request(req, **kwargs)
|
||||||
|
|
||||||
def get_mhtml(self, tab, target):
|
def get_mhtml(self, tab, target):
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,13 @@ import collections
|
||||||
import secrets
|
import secrets
|
||||||
from typing import TypeVar, Callable, Dict, List, Optional, Union, Sequence, Tuple
|
from typing import TypeVar, Callable, Dict, List, Optional, Union, Sequence, Tuple
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrlQuery, QUrl, qVersion
|
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.browser import pdfjs, downloads, history
|
from qutebrowser.browser import pdfjs, downloads, history
|
||||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
from qutebrowser.config import config, configdata, configexc
|
||||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||||
objreg, urlutils, standarddir)
|
objreg, standarddir)
|
||||||
from qutebrowser.qt import sip
|
from qutebrowser.qt import sip
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -135,7 +135,7 @@ def data_for_url(url: QUrl) -> Tuple[str, bytes]:
|
||||||
|
|
||||||
path = url.path()
|
path = url.path()
|
||||||
host = url.host()
|
host = url.host()
|
||||||
query = urlutils.query_string(url)
|
query = url.query()
|
||||||
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
|
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
|
||||||
log.misc.debug("url: {}, path: {}, host {}".format(
|
log.misc.debug("url: {}, path: {}, host {}".format(
|
||||||
url.toDisplayString(), path, host))
|
url.toDisplayString(), path, host))
|
||||||
|
|
@ -481,18 +481,10 @@ def qute_back(url: QUrl) -> _HandlerRet:
|
||||||
|
|
||||||
|
|
||||||
@add_handler('configdiff')
|
@add_handler('configdiff')
|
||||||
def qute_configdiff(url: QUrl) -> _HandlerRet:
|
def qute_configdiff(_url: QUrl) -> _HandlerRet:
|
||||||
"""Handler for qute://configdiff."""
|
"""Handler for qute://configdiff."""
|
||||||
if url.path() == '/old':
|
data = config.instance.dump_userconfig().encode('utf-8')
|
||||||
try:
|
return 'text/plain', data
|
||||||
return 'text/html', configdiff.get_diff()
|
|
||||||
except OSError as e:
|
|
||||||
error = (b'Failed to read old config: ' +
|
|
||||||
str(e.strerror).encode('utf-8'))
|
|
||||||
return 'text/plain', error
|
|
||||||
else:
|
|
||||||
data = config.instance.dump_userconfig().encode('utf-8')
|
|
||||||
return 'text/plain', data
|
|
||||||
|
|
||||||
|
|
||||||
@add_handler('pastebin-version')
|
@add_handler('pastebin-version')
|
||||||
|
|
@ -563,11 +555,7 @@ def qute_pdfjs(url: QUrl) -> _HandlerRet:
|
||||||
def qute_warning(url: QUrl) -> _HandlerRet:
|
def qute_warning(url: QUrl) -> _HandlerRet:
|
||||||
"""Handler for qute://warning."""
|
"""Handler for qute://warning."""
|
||||||
path = url.path()
|
path = url.path()
|
||||||
if path == '/old-qt':
|
if path == '/webkit':
|
||||||
src = jinja.render('warning-old-qt.html',
|
|
||||||
title='Old Qt warning',
|
|
||||||
qt_version=qVersion())
|
|
||||||
elif path == '/webkit':
|
|
||||||
src = jinja.render('warning-webkit.html',
|
src = jinja.render('warning-webkit.html',
|
||||||
title='QtWebKit backend warning')
|
title='QtWebKit backend warning')
|
||||||
elif path == '/sessions':
|
elif path == '/sessions':
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ from typing import Callable, Mapping
|
||||||
from PyQt5.QtCore import QUrl
|
from PyQt5.QtCore import QUrl
|
||||||
|
|
||||||
from qutebrowser.config import config
|
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
|
from qutebrowser.mainwindow import mainwindow
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,13 +75,12 @@ def authentication_required(url, authenticator, abort_on):
|
||||||
return answer
|
return answer
|
||||||
|
|
||||||
|
|
||||||
def javascript_confirm(url, js_msg, abort_on, *, escape_msg=True):
|
def javascript_confirm(url, js_msg, abort_on):
|
||||||
"""Display a javascript confirm prompt."""
|
"""Display a javascript confirm prompt."""
|
||||||
log.js.debug("confirm: {}".format(js_msg))
|
log.js.debug("confirm: {}".format(js_msg))
|
||||||
if config.val.content.javascript.modal_dialog:
|
if config.val.content.javascript.modal_dialog:
|
||||||
raise CallSuper
|
raise CallSuper
|
||||||
|
|
||||||
js_msg = html.escape(js_msg) if escape_msg else js_msg
|
|
||||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||||
js_msg)
|
js_msg)
|
||||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||||
|
|
@ -92,7 +90,7 @@ def javascript_confirm(url, js_msg, abort_on, *, escape_msg=True):
|
||||||
return bool(ans)
|
return bool(ans)
|
||||||
|
|
||||||
|
|
||||||
def javascript_prompt(url, js_msg, default, abort_on, *, escape_msg=True):
|
def javascript_prompt(url, js_msg, default, abort_on):
|
||||||
"""Display a javascript prompt."""
|
"""Display a javascript prompt."""
|
||||||
log.js.debug("prompt: {}".format(js_msg))
|
log.js.debug("prompt: {}".format(js_msg))
|
||||||
if config.val.content.javascript.modal_dialog:
|
if config.val.content.javascript.modal_dialog:
|
||||||
|
|
@ -100,7 +98,6 @@ def javascript_prompt(url, js_msg, default, abort_on, *, escape_msg=True):
|
||||||
if not config.val.content.javascript.prompt:
|
if not config.val.content.javascript.prompt:
|
||||||
return (False, "")
|
return (False, "")
|
||||||
|
|
||||||
js_msg = html.escape(js_msg) if escape_msg else js_msg
|
|
||||||
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
|
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||||
js_msg)
|
js_msg)
|
||||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||||
|
|
@ -115,7 +112,7 @@ def javascript_prompt(url, js_msg, default, abort_on, *, escape_msg=True):
|
||||||
return (True, answer)
|
return (True, answer)
|
||||||
|
|
||||||
|
|
||||||
def javascript_alert(url, js_msg, abort_on, *, escape_msg=True):
|
def javascript_alert(url, js_msg, abort_on):
|
||||||
"""Display a javascript alert."""
|
"""Display a javascript alert."""
|
||||||
log.js.debug("alert: {}".format(js_msg))
|
log.js.debug("alert: {}".format(js_msg))
|
||||||
if config.val.content.javascript.modal_dialog:
|
if config.val.content.javascript.modal_dialog:
|
||||||
|
|
@ -124,7 +121,6 @@ def javascript_alert(url, js_msg, abort_on, *, escape_msg=True):
|
||||||
if not config.val.content.javascript.alert:
|
if not config.val.content.javascript.alert:
|
||||||
return
|
return
|
||||||
|
|
||||||
js_msg = html.escape(js_msg) if escape_msg else js_msg
|
|
||||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||||
js_msg)
|
js_msg)
|
||||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||||
|
|
@ -287,9 +283,7 @@ def get_user_stylesheet(searching=False):
|
||||||
css += f.read()
|
css += f.read()
|
||||||
|
|
||||||
setting = config.val.scrolling.bar
|
setting = config.val.scrolling.bar
|
||||||
overlay_bar_available = (qtutils.version_check('5.11', compiled=False) and
|
if setting == 'overlay' and not utils.is_mac:
|
||||||
not utils.is_mac)
|
|
||||||
if setting == 'overlay' and not overlay_bar_available:
|
|
||||||
setting = 'when-searching'
|
setting = 'when-searching'
|
||||||
|
|
||||||
if setting == 'never' or setting == 'when-searching' and not searching:
|
if setting == 'never' or setting == 'when-searching' and not searching:
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
"""Filter for QtWebEngine cookies."""
|
"""Filter for QtWebEngine cookies."""
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import utils, qtutils, log
|
from qutebrowser.utils import utils, log
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,13 +31,6 @@ def _accept_cookie(request):
|
||||||
if not url.isValid():
|
if not url.isValid():
|
||||||
url = None
|
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',
|
accept = config.instance.get('content.cookies.accept',
|
||||||
url=url)
|
url=url)
|
||||||
|
|
||||||
|
|
@ -48,13 +41,13 @@ def _accept_cookie(request):
|
||||||
else request.origin.toDisplayString())
|
else request.origin.toDisplayString())
|
||||||
log.network.debug('Cookie from origin {} on {} (third party: {}) '
|
log.network.debug('Cookie from origin {} on {} (third party: {}) '
|
||||||
'-> applying setting {}'
|
'-> applying setting {}'
|
||||||
.format(origin_str, first_party_str, third_party,
|
.format(origin_str, first_party_str, request.thirdParty,
|
||||||
accept))
|
accept))
|
||||||
|
|
||||||
if accept == 'all':
|
if accept == 'all':
|
||||||
return True
|
return True
|
||||||
elif accept in ['no-3rdparty', 'no-unknown-3rdparty']:
|
elif accept in ['no-3rdparty', 'no-unknown-3rdparty']:
|
||||||
return not third_party
|
return not request.thirdParty
|
||||||
elif accept == 'never':
|
elif accept == 'never':
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|
@ -62,12 +55,5 @@ def _accept_cookie(request):
|
||||||
|
|
||||||
|
|
||||||
def install_filter(profile):
|
def install_filter(profile):
|
||||||
"""Install the cookie filter on the given profile.
|
"""Install the cookie filter on the given profile."""
|
||||||
|
profile.cookieStore().setCookieFilter(_accept_cookie)
|
||||||
On Qt < 5.11, the filter isn't installed.
|
|
||||||
"""
|
|
||||||
store = profile.cookieStore()
|
|
||||||
try:
|
|
||||||
store.setCookieFilter(_accept_cookie)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ Prefix changed to "forceDarkMode".
|
||||||
- As with Qt 5.15.0 / .1, but with "forceDarkMode" as prefix.
|
- As with Qt 5.15.0 / .1, but with "forceDarkMode" as prefix.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import enum
|
import enum
|
||||||
from typing import Any, Iterable, Iterator, Mapping, Optional, Set, Tuple, Union
|
from typing import Any, Iterable, Iterator, Mapping, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
|
|
@ -90,8 +91,6 @@ class Variant(enum.Enum):
|
||||||
|
|
||||||
"""A dark mode variant."""
|
"""A dark mode variant."""
|
||||||
|
|
||||||
unavailable = enum.auto()
|
|
||||||
qt_510 = enum.auto()
|
|
||||||
qt_511_to_513 = enum.auto()
|
qt_511_to_513 = enum.auto()
|
||||||
qt_514 = enum.auto()
|
qt_514 = enum.auto()
|
||||||
qt_515_0 = enum.auto()
|
qt_515_0 = enum.auto()
|
||||||
|
|
@ -119,9 +118,6 @@ _IMAGE_POLICIES = {
|
||||||
'never': 1, # kFilterNone
|
'never': 1, # kFilterNone
|
||||||
'smart': 2, # kFilterSmart
|
'smart': 2, # kFilterSmart
|
||||||
}
|
}
|
||||||
# Image policy smart is not available with Qt 5.10
|
|
||||||
_IMAGE_POLICIES_QT_510 = _IMAGE_POLICIES.copy()
|
|
||||||
_IMAGE_POLICIES_QT_510['smart'] = _IMAGE_POLICIES['never']
|
|
||||||
|
|
||||||
# Mapping from a colors.webpage.darkmode.policy.page setting value to
|
# Mapping from a colors.webpage.darkmode.policy.page setting value to
|
||||||
# Chromium's DarkModePagePolicy enum values.
|
# Chromium's DarkModePagePolicy enum values.
|
||||||
|
|
@ -163,8 +159,6 @@ _QT_514_SETTINGS = [
|
||||||
# workaround warning below if the setting wasn't explicitly customized.
|
# workaround warning below if the setting wasn't explicitly customized.
|
||||||
|
|
||||||
_DARK_MODE_DEFINITIONS: Mapping[Variant, _DarkModeDefinitionType] = {
|
_DARK_MODE_DEFINITIONS: Mapping[Variant, _DarkModeDefinitionType] = {
|
||||||
Variant.unavailable: ([], set()),
|
|
||||||
|
|
||||||
Variant.qt_515_2: ([
|
Variant.qt_515_2: ([
|
||||||
# 'darkMode' renamed to 'forceDarkMode'
|
# 'darkMode' renamed to 'forceDarkMode'
|
||||||
('enabled', 'forceDarkModeEnabled', _BOOLS),
|
('enabled', 'forceDarkModeEnabled', _BOOLS),
|
||||||
|
|
@ -234,19 +228,18 @@ _DARK_MODE_DEFINITIONS: Mapping[Variant, _DarkModeDefinitionType] = {
|
||||||
('contrast', 'highContrastContrast', None),
|
('contrast', 'highContrastContrast', None),
|
||||||
('grayscale.all', 'highContrastGrayscale', _BOOLS),
|
('grayscale.all', 'highContrastGrayscale', _BOOLS),
|
||||||
], {'algorithm', 'policy.images'}),
|
], {'algorithm', 'policy.images'}),
|
||||||
|
|
||||||
Variant.qt_510: ([
|
|
||||||
('algorithm', 'highContrastMode', _ALGORITHMS_BEFORE_QT_514),
|
|
||||||
|
|
||||||
('policy.images', 'highContrastImagePolicy', _IMAGE_POLICIES_QT_510),
|
|
||||||
('contrast', 'highContrastContrast', None),
|
|
||||||
('grayscale.all', 'highContrastGrayscale', _BOOLS),
|
|
||||||
], {'algorithm'}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _variant() -> Variant:
|
def _variant() -> Variant:
|
||||||
"""Get the dark mode variant based on the underlying Qt version."""
|
"""Get the dark mode variant based on the underlying Qt version."""
|
||||||
|
env_var = os.environ.get('QUTE_DARKMODE_VARIANT')
|
||||||
|
if env_var is not None:
|
||||||
|
try:
|
||||||
|
return Variant[env_var]
|
||||||
|
except KeyError:
|
||||||
|
log.init.warning(f"Ignoring invalid QUTE_DARKMODE_VARIANT={env_var}")
|
||||||
|
|
||||||
if PYQT_WEBENGINE_VERSION is not None:
|
if PYQT_WEBENGINE_VERSION is not None:
|
||||||
# Available with Qt >= 5.13
|
# Available with Qt >= 5.13
|
||||||
if PYQT_WEBENGINE_VERSION >= 0x050f02:
|
if PYQT_WEBENGINE_VERSION >= 0x050f02:
|
||||||
|
|
@ -261,17 +254,12 @@ def _variant() -> Variant:
|
||||||
return Variant.qt_511_to_513
|
return Variant.qt_511_to_513
|
||||||
raise utils.Unreachable(hex(PYQT_WEBENGINE_VERSION))
|
raise utils.Unreachable(hex(PYQT_WEBENGINE_VERSION))
|
||||||
|
|
||||||
# If we don't have PYQT_WEBENGINE_VERSION, we'll need to assume based on the Qt
|
# If we don't have PYQT_WEBENGINE_VERSION, we're on 5.12 (or older, but 5.12 is the
|
||||||
# version.
|
# oldest supported version).
|
||||||
assert not qtutils.version_check( # type: ignore[unreachable]
|
assert not qtutils.version_check( # type: ignore[unreachable]
|
||||||
'5.13', compiled=False)
|
'5.13', compiled=False)
|
||||||
|
|
||||||
if qtutils.version_check('5.11', compiled=False):
|
return Variant.qt_511_to_513
|
||||||
return Variant.qt_511_to_513
|
|
||||||
elif qtutils.version_check('5.10', compiled=False):
|
|
||||||
return Variant.qt_510
|
|
||||||
|
|
||||||
return Variant.unavailable
|
|
||||||
|
|
||||||
|
|
||||||
def settings() -> Iterator[Tuple[str, str]]:
|
def settings() -> Iterator[Tuple[str, str]]:
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ from PyQt5.QtCore import QUrl, QByteArray
|
||||||
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
|
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
|
||||||
QWebEngineUrlRequestInfo)
|
QWebEngineUrlRequestInfo)
|
||||||
|
|
||||||
from qutebrowser.config import websettings
|
from qutebrowser.config import websettings, config
|
||||||
from qutebrowser.browser import shared
|
from qutebrowser.browser import shared
|
||||||
from qutebrowser.utils import utils, log, debug, qtutils
|
from qutebrowser.utils import utils, log, debug, qtutils
|
||||||
from qutebrowser.extensions import interceptors
|
from qutebrowser.extensions import interceptors
|
||||||
|
|
@ -39,9 +39,9 @@ class WebEngineRequest(interceptors.Request):
|
||||||
|
|
||||||
_WHITELISTED_REQUEST_METHODS = {QByteArray(b'GET'), QByteArray(b'HEAD')}
|
_WHITELISTED_REQUEST_METHODS = {QByteArray(b'GET'), QByteArray(b'HEAD')}
|
||||||
|
|
||||||
_webengine_info = attr.ib(default=None) # type: QWebEngineUrlRequestInfo
|
_webengine_info: QWebEngineUrlRequestInfo = attr.ib(default=None)
|
||||||
#: If this request has been redirected already
|
#: If this request has been redirected already
|
||||||
_redirected = attr.ib(init=False, default=False) # type: bool
|
_redirected: bool = attr.ib(init=False, default=False)
|
||||||
|
|
||||||
def redirect(self, url: QUrl) -> None:
|
def redirect(self, url: QUrl) -> None:
|
||||||
if self._redirected:
|
if self._redirected:
|
||||||
|
|
@ -129,7 +129,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
||||||
# Qt >= 5.13, GUI thread
|
# Qt >= 5.13, GUI thread
|
||||||
profile.setUrlRequestInterceptor(self)
|
profile.setUrlRequestInterceptor(self)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Qt <= 5.12, IO thread
|
# Qt 5.12, IO thread
|
||||||
profile.setRequestInterceptor(self)
|
profile.setRequestInterceptor(self)
|
||||||
|
|
||||||
# Gets called in the IO thread -> showing crash window will fail
|
# Gets called in the IO thread -> showing crash window will fail
|
||||||
|
|
@ -204,5 +204,11 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
||||||
for header, value in shared.custom_headers(url=url):
|
for header, value in shared.custom_headers(url=url):
|
||||||
info.setHttpHeader(header, value)
|
info.setHttpHeader(header, value)
|
||||||
|
|
||||||
|
# Note this is ignored before Qt 5.12.4 and 5.13.1 due to
|
||||||
|
# https://bugreports.qt.io/browse/QTBUG-60203 - there, we set the
|
||||||
|
# commandline-flag in qtargs.py instead.
|
||||||
|
if config.cache['content.headers.referer'] == 'never':
|
||||||
|
info.setHttpHeader(b'Referer', b'')
|
||||||
|
|
||||||
user_agent = websettings.user_agent(url)
|
user_agent = websettings.user_agent(url)
|
||||||
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
|
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
|
||||||
|
|
|
||||||
|
|
@ -24,23 +24,12 @@ import glob
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QLibraryInfo
|
from qutebrowser.utils import log, message, standarddir
|
||||||
from qutebrowser.utils import log, message, standarddir, qtutils
|
|
||||||
|
|
||||||
_DICT_VERSION_RE = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
|
_DICT_VERSION_RE = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
|
||||||
|
|
||||||
|
|
||||||
def can_use_data_path():
|
|
||||||
"""Whether the current Qt version can use a customized path.
|
|
||||||
|
|
||||||
Qt >= 5.10 understands QTWEBENGINE_DICTIONARIES_PATH which means we don't
|
|
||||||
need to put them to a fixed root-only folder.
|
|
||||||
"""
|
|
||||||
return qtutils.version_check('5.10', compiled=False)
|
|
||||||
|
|
||||||
|
|
||||||
def version(filename):
|
def version(filename):
|
||||||
"""Extract the version number from the dictionary file name."""
|
"""Extract the version number from the dictionary file name."""
|
||||||
match = _DICT_VERSION_RE.fullmatch(filename)
|
match = _DICT_VERSION_RE.fullmatch(filename)
|
||||||
|
|
@ -51,13 +40,9 @@ def version(filename):
|
||||||
return tuple(int(n) for n in match.group('version').split('-'))
|
return tuple(int(n) for n in match.group('version').split('-'))
|
||||||
|
|
||||||
|
|
||||||
def dictionary_dir(old=False):
|
def dictionary_dir():
|
||||||
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
|
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
|
||||||
if can_use_data_path() and not old:
|
return os.path.join(standarddir.data(), 'qtwebengine_dictionaries')
|
||||||
datapath = standarddir.data()
|
|
||||||
else:
|
|
||||||
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
|
|
||||||
return os.path.join(datapath, 'qtwebengine_dictionaries')
|
|
||||||
|
|
||||||
|
|
||||||
def local_files(code):
|
def local_files(code):
|
||||||
|
|
@ -91,13 +76,6 @@ def local_filename(code):
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
"""Initialize the dictionary path if supported."""
|
"""Initialize the dictionary path."""
|
||||||
if can_use_data_path():
|
dict_dir = dictionary_dir()
|
||||||
new_dir = dictionary_dir()
|
os.environ['QTWEBENGINE_DICTIONARIES_PATH'] = dict_dir
|
||||||
old_dir = dictionary_dir(old=True)
|
|
||||||
os.environ['QTWEBENGINE_DICTIONARIES_PATH'] = new_dir
|
|
||||||
try:
|
|
||||||
if os.path.exists(old_dir) and not os.path.exists(new_dir):
|
|
||||||
shutil.copytree(old_dir, new_dir)
|
|
||||||
except OSError:
|
|
||||||
log.misc.exception("Failed to copy old dictionaries")
|
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,13 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import os.path
|
import os.path
|
||||||
import urllib
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QObject
|
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QObject
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
|
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
|
||||||
|
|
||||||
from qutebrowser.browser import downloads, pdfjs
|
from qutebrowser.browser import downloads, pdfjs
|
||||||
from qutebrowser.utils import debug, usertypes, message, log, qtutils, objreg
|
from qutebrowser.utils import debug, usertypes, message, log, objreg
|
||||||
|
|
||||||
|
|
||||||
class DownloadItem(downloads.AbstractDownloadItem):
|
class DownloadItem(downloads.AbstractDownloadItem):
|
||||||
|
|
@ -84,12 +83,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||||
self.stats.finish()
|
self.stats.finish()
|
||||||
elif state == QWebEngineDownloadItem.DownloadInterrupted:
|
elif state == QWebEngineDownloadItem.DownloadInterrupted:
|
||||||
self.successful = False
|
self.successful = False
|
||||||
# https://bugreports.qt.io/browse/QTBUG-56839
|
reason = self._qt_item.interruptReasonString()
|
||||||
try:
|
|
||||||
reason = self._qt_item.interruptReasonString()
|
|
||||||
except AttributeError:
|
|
||||||
# Qt < 5.9
|
|
||||||
reason = "Download failed"
|
|
||||||
self._die(reason)
|
self._die(reason)
|
||||||
else:
|
else:
|
||||||
raise ValueError("_on_state_changed was called with unknown state "
|
raise ValueError("_on_state_changed was called with unknown state "
|
||||||
|
|
@ -102,22 +96,21 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||||
self._qt_item.cancel()
|
self._qt_item.cancel()
|
||||||
|
|
||||||
def _do_cancel(self):
|
def _do_cancel(self):
|
||||||
|
state = self._qt_item.state()
|
||||||
|
state_name = debug.qenum_key(QWebEngineDownloadItem, state)
|
||||||
|
assert state not in [QWebEngineDownloadItem.DownloadCompleted,
|
||||||
|
QWebEngineDownloadItem.DownloadCancelled], state_name
|
||||||
self._qt_item.cancel()
|
self._qt_item.cancel()
|
||||||
|
|
||||||
def retry(self):
|
def retry(self):
|
||||||
state = self._qt_item.state()
|
state = self._qt_item.state()
|
||||||
if state != QWebEngineDownloadItem.DownloadInterrupted:
|
if state != QWebEngineDownloadItem.DownloadInterrupted:
|
||||||
log.downloads.warning(
|
log.downloads.warning(
|
||||||
"Trying to retry download in state {}".format(
|
"Refusing to retry download in state {}".format(
|
||||||
debug.qenum_key(QWebEngineDownloadItem, state)))
|
debug.qenum_key(QWebEngineDownloadItem, state)))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
self._qt_item.resume()
|
||||||
self._qt_item.resume()
|
|
||||||
except AttributeError:
|
|
||||||
raise downloads.UnsupportedOperationError(
|
|
||||||
"Retrying downloads is unsupported with QtWebEngine on "
|
|
||||||
"Qt/PyQt < 5.10")
|
|
||||||
|
|
||||||
def _get_open_filename(self):
|
def _get_open_filename(self):
|
||||||
return self._filename
|
return self._filename
|
||||||
|
|
@ -233,14 +226,7 @@ def _get_suggested_filename(path):
|
||||||
(?=\.|$) # Begin of extension, or filename without extension
|
(?=\.|$) # Begin of extension, or filename without extension
|
||||||
""", re.VERBOSE)
|
""", re.VERBOSE)
|
||||||
|
|
||||||
filename = suffix_re.sub('', filename)
|
return suffix_re.sub('', filename)
|
||||||
if not qtutils.version_check('5.9', compiled=False):
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-58155
|
|
||||||
filename = urllib.parse.unquote(filename)
|
|
||||||
# Doing basename a *second* time because there could be a %2F in
|
|
||||||
# there...
|
|
||||||
filename = os.path.basename(filename)
|
|
||||||
return filename
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadManager(downloads.AbstractDownloadManager):
|
class DownloadManager(downloads.AbstractDownloadManager):
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,7 @@
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Set, Tuple, Union)
|
TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Set, Tuple, Union)
|
||||||
|
|
||||||
from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop
|
from PyQt5.QtCore import QRect, QEventLoop
|
||||||
from PyQt5.QtGui import QMouseEvent
|
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
|
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
|
||||||
|
|
||||||
|
|
@ -230,11 +229,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||||
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
|
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
|
||||||
|
|
||||||
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
|
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
|
self._tab.setFocus() # Needed as WORKAROUND on Qt 5.12
|
||||||
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
|
|
||||||
QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton,
|
|
||||||
Qt.NoModifier, Qt.MouseEventSynthesizedBySystem)
|
|
||||||
self._tab.send_event(ev)
|
|
||||||
# This actually "clicks" the element by calling focus() on it in JS.
|
# This actually "clicks" the element by calling focus() on it in JS.
|
||||||
self._js_call('focus')
|
self._js_call('focus')
|
||||||
self._move_text_cursor()
|
self._move_text_cursor()
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,16 @@
|
||||||
|
|
||||||
"""Customized QWebInspector for QtWebEngine."""
|
"""Customized QWebInspector for QtWebEngine."""
|
||||||
|
|
||||||
import os
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, QLibraryInfo
|
from PyQt5.QtCore import QLibraryInfo
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||||
from PyQt5.QtWidgets import QWidget
|
from PyQt5.QtWidgets import QWidget
|
||||||
|
|
||||||
from qutebrowser.browser import inspector
|
from qutebrowser.browser import inspector
|
||||||
from qutebrowser.browser.webengine import webenginesettings
|
from qutebrowser.browser.webengine import webenginesettings
|
||||||
from qutebrowser.misc import miscwidgets
|
from qutebrowser.misc import miscwidgets
|
||||||
from qutebrowser.utils import version, qtutils
|
from qutebrowser.utils import version
|
||||||
|
|
||||||
|
|
||||||
class WebEngineInspectorView(QWebEngineView):
|
class WebEngineInspectorView(QWebEngineView):
|
||||||
|
|
@ -52,47 +51,9 @@ class WebEngineInspectorView(QWebEngineView):
|
||||||
return self.page().inspectedPage().view().createWindow(wintype)
|
return self.page().inspectedPage().view().createWindow(wintype)
|
||||||
|
|
||||||
|
|
||||||
def supports_new() -> bool:
|
|
||||||
"""Check whether a new-style inspector is supported."""
|
|
||||||
return hasattr(QWebEnginePage, 'setInspectedPage')
|
|
||||||
|
|
||||||
|
|
||||||
class LegacyWebEngineInspector(inspector.AbstractWebInspector):
|
|
||||||
|
|
||||||
"""A web inspector for QtWebEngine without Qt API support.
|
|
||||||
|
|
||||||
Only needed with Qt <= 5.10.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, splitter: miscwidgets.InspectorSplitter,
|
|
||||||
win_id: int,
|
|
||||||
parent: QWidget = None) -> None:
|
|
||||||
super().__init__(splitter, win_id, parent)
|
|
||||||
self._ensure_enabled()
|
|
||||||
view = WebEngineInspectorView()
|
|
||||||
self._settings = webenginesettings.WebEngineSettings(view.settings())
|
|
||||||
self._set_widget(view)
|
|
||||||
|
|
||||||
def _ensure_enabled(self) -> None:
|
|
||||||
if 'QTWEBENGINE_REMOTE_DEBUGGING' not in os.environ:
|
|
||||||
raise inspector.Error(
|
|
||||||
"QtWebEngine inspector is not enabled. See "
|
|
||||||
"'qutebrowser --help' for details.")
|
|
||||||
|
|
||||||
def inspect(self, page: QWebEnginePage) -> None: # type: ignore[override]
|
|
||||||
# We're lying about the URL here a bit, but this way, URL patterns for
|
|
||||||
# Qt 5.11/5.12/5.13 also work in this case.
|
|
||||||
self._settings.update_for_url(QUrl('chrome-devtools://devtools'))
|
|
||||||
port = int(os.environ['QTWEBENGINE_REMOTE_DEBUGGING'])
|
|
||||||
self._widget.load(QUrl('http://localhost:{}/'.format(port)))
|
|
||||||
|
|
||||||
|
|
||||||
class WebEngineInspector(inspector.AbstractWebInspector):
|
class WebEngineInspector(inspector.AbstractWebInspector):
|
||||||
|
|
||||||
"""A web inspector for QtWebEngine with Qt API support.
|
"""A web inspector for QtWebEngine with Qt API support."""
|
||||||
|
|
||||||
Available since Qt 5.11.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, splitter: miscwidgets.InspectorSplitter,
|
def __init__(self, splitter: miscwidgets.InspectorSplitter,
|
||||||
win_id: int,
|
win_id: int,
|
||||||
|
|
@ -130,4 +91,4 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
||||||
|
|
||||||
WORKAROUND for what's likely an unknown Qt bug.
|
WORKAROUND for what's likely an unknown Qt bug.
|
||||||
"""
|
"""
|
||||||
return qtutils.version_check('5.12')
|
return True
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,8 @@
|
||||||
|
|
||||||
from PyQt5.QtCore import QBuffer, QIODevice, QUrl
|
from PyQt5.QtCore import QBuffer, QIODevice, QUrl
|
||||||
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
|
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
|
||||||
QWebEngineUrlRequestJob)
|
QWebEngineUrlRequestJob,
|
||||||
try:
|
QWebEngineUrlScheme)
|
||||||
from PyQt5.QtWebEngineCore import QWebEngineUrlScheme
|
|
||||||
except ImportError:
|
|
||||||
# Added in Qt 5.12
|
|
||||||
QWebEngineUrlScheme = None # type: ignore[misc, assignment]
|
|
||||||
|
|
||||||
from qutebrowser.browser import qutescheme
|
from qutebrowser.browser import qutescheme
|
||||||
from qutebrowser.utils import log, qtutils
|
from qutebrowser.utils import log, qtutils
|
||||||
|
|
@ -42,11 +38,6 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||||
assert QWebEngineUrlScheme.schemeByName(b'qute') is not None
|
assert QWebEngineUrlScheme.schemeByName(b'qute') is not None
|
||||||
|
|
||||||
profile.installUrlSchemeHandler(b'qute', self)
|
profile.installUrlSchemeHandler(b'qute', self)
|
||||||
if (qtutils.version_check('5.11', compiled=False) and
|
|
||||||
not qtutils.version_check('5.12', compiled=False)):
|
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
|
||||||
profile.installUrlSchemeHandler(b'chrome-error', self)
|
|
||||||
profile.installUrlSchemeHandler(b'chrome-extension', self)
|
|
||||||
|
|
||||||
def _check_initiator(self, job):
|
def _check_initiator(self, job):
|
||||||
"""Check whether the initiator of the job should be allowed.
|
"""Check whether the initiator of the job should be allowed.
|
||||||
|
|
@ -60,29 +51,16 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||||
Return:
|
Return:
|
||||||
True if the initiator is allowed, False if it was blocked.
|
True if the initiator is allowed, False if it was blocked.
|
||||||
"""
|
"""
|
||||||
try:
|
initiator = job.initiator()
|
||||||
initiator = job.initiator()
|
request_url = job.requestUrl()
|
||||||
request_url = job.requestUrl()
|
|
||||||
except AttributeError:
|
|
||||||
# Added in Qt 5.11
|
|
||||||
return True
|
|
||||||
|
|
||||||
# https://codereview.qt-project.org/#/c/234849/
|
# https://codereview.qt-project.org/#/c/234849/
|
||||||
is_opaque = initiator == QUrl('null')
|
is_opaque = initiator == QUrl('null')
|
||||||
target = request_url.scheme(), request_url.host()
|
target = request_url.scheme(), request_url.host()
|
||||||
|
|
||||||
if is_opaque and not qtutils.version_check('5.12'):
|
if target == ('qute', 'testdata') and is_opaque:
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70421
|
# Allow requests to qute://testdata, as this is needed for all tests to work
|
||||||
# When we don't register the qute:// scheme, all requests are
|
# properly. No qute://testdata handler is installed outside of tests.
|
||||||
# flagged as opaque.
|
|
||||||
return True
|
|
||||||
|
|
||||||
if (target == ('qute', 'testdata') and
|
|
||||||
is_opaque and
|
|
||||||
qtutils.version_check('5.12')):
|
|
||||||
# Allow requests to qute://testdata, as this is needed in Qt 5.12
|
|
||||||
# for all tests to work properly. No qute://testdata handler is
|
|
||||||
# installed outside of tests.
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if initiator.isValid() and initiator.scheme() != 'qute':
|
if initiator.isValid() and initiator.scheme() != 'qute':
|
||||||
|
|
@ -105,11 +83,6 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||||
"""
|
"""
|
||||||
url = job.requestUrl()
|
url = job.requestUrl()
|
||||||
|
|
||||||
if url.scheme() in ['chrome-error', 'chrome-extension']:
|
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
|
||||||
job.fail(QWebEngineUrlRequestJob.UrlInvalid)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self._check_initiator(job):
|
if not self._check_initiator(job):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,12 @@ import operator
|
||||||
from typing import cast, Any, List, Optional, Tuple, Union
|
from typing import cast, Any, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from PyQt5.QtGui import QFont
|
from PyQt5.QtGui import QFont
|
||||||
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile
|
||||||
QWebEnginePage)
|
|
||||||
|
|
||||||
from qutebrowser.browser.webengine import spell, webenginequtescheme
|
from qutebrowser.browser.webengine import spell, webenginequtescheme
|
||||||
from qutebrowser.config import config, websettings
|
from qutebrowser.config import config, websettings
|
||||||
from qutebrowser.config.websettings import AttributeInfo as Attr
|
from qutebrowser.config.websettings import AttributeInfo as Attr
|
||||||
from qutebrowser.utils import (utils, standarddir, qtutils, message, log,
|
from qutebrowser.utils import standarddir, qtutils, message, log, urlmatch, usertypes
|
||||||
urlmatch, usertypes)
|
|
||||||
|
|
||||||
# The default QWebEngineProfile
|
# The default QWebEngineProfile
|
||||||
default_profile = cast(QWebEngineProfile, None)
|
default_profile = cast(QWebEngineProfile, None)
|
||||||
|
|
@ -126,8 +124,7 @@ class WebEngineSettings(websettings.AbstractSettings):
|
||||||
'content.desktop_capture':
|
'content.desktop_capture':
|
||||||
Attr(QWebEngineSettings.ScreenCaptureEnabled,
|
Attr(QWebEngineSettings.ScreenCaptureEnabled,
|
||||||
converter=lambda val: True if val == 'ask' else val),
|
converter=lambda val: True if val == 'ask' else val),
|
||||||
# 'ask' is handled via the permission system,
|
# 'ask' is handled via the permission system
|
||||||
# or a hardcoded dialog on Qt < 5.10
|
|
||||||
|
|
||||||
'input.spatial_navigation':
|
'input.spatial_navigation':
|
||||||
Attr(QWebEngineSettings.SpatialNavigationEnabled),
|
Attr(QWebEngineSettings.SpatialNavigationEnabled),
|
||||||
|
|
@ -136,6 +133,16 @@ class WebEngineSettings(websettings.AbstractSettings):
|
||||||
|
|
||||||
'scrolling.smooth':
|
'scrolling.smooth':
|
||||||
Attr(QWebEngineSettings.ScrollAnimatorEnabled),
|
Attr(QWebEngineSettings.ScrollAnimatorEnabled),
|
||||||
|
|
||||||
|
'content.print_element_backgrounds':
|
||||||
|
Attr(QWebEngineSettings.PrintElementBackgrounds),
|
||||||
|
|
||||||
|
'content.autoplay':
|
||||||
|
Attr(QWebEngineSettings.PlaybackRequiresUserGesture,
|
||||||
|
converter=operator.not_),
|
||||||
|
|
||||||
|
'content.dns_prefetch':
|
||||||
|
Attr(QWebEngineSettings.DnsPrefetchEnabled),
|
||||||
}
|
}
|
||||||
|
|
||||||
_FONT_SIZES = {
|
_FONT_SIZES = {
|
||||||
|
|
@ -158,18 +165,14 @@ class WebEngineSettings(websettings.AbstractSettings):
|
||||||
'fonts.web.family.fantasy': QWebEngineSettings.FantasyFont,
|
'fonts.web.family.fantasy': QWebEngineSettings.FantasyFont,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only Qt >= 5.11 support UnknownUrlSchemePolicy
|
_UNKNOWN_URL_SCHEME_POLICY = {
|
||||||
try:
|
'disallow':
|
||||||
_UNKNOWN_URL_SCHEME_POLICY = {
|
QWebEngineSettings.DisallowUnknownUrlSchemes,
|
||||||
'disallow':
|
'allow-from-user-interaction':
|
||||||
QWebEngineSettings.DisallowUnknownUrlSchemes,
|
QWebEngineSettings.AllowUnknownUrlSchemesFromUserInteraction,
|
||||||
'allow-from-user-interaction':
|
'allow-all':
|
||||||
QWebEngineSettings.AllowUnknownUrlSchemesFromUserInteraction,
|
QWebEngineSettings.AllowAllUnknownUrlSchemes,
|
||||||
'allow-all':
|
}
|
||||||
QWebEngineSettings.AllowAllUnknownUrlSchemes,
|
|
||||||
}
|
|
||||||
except AttributeError:
|
|
||||||
_UNKNOWN_URL_SCHEME_POLICY = None
|
|
||||||
|
|
||||||
# Mapping from WebEngineSettings::initDefaults in
|
# Mapping from WebEngineSettings::initDefaults in
|
||||||
# qtwebengine/src/core/web_engine_settings.cpp
|
# qtwebengine/src/core/web_engine_settings.cpp
|
||||||
|
|
@ -200,39 +203,13 @@ class WebEngineSettings(websettings.AbstractSettings):
|
||||||
|
|
||||||
def _update_setting(self, setting, value):
|
def _update_setting(self, setting, value):
|
||||||
if setting == 'content.unknown_url_scheme_policy':
|
if setting == 'content.unknown_url_scheme_policy':
|
||||||
if self._UNKNOWN_URL_SCHEME_POLICY:
|
return self.set_unknown_url_scheme_policy(value)
|
||||||
return self.set_unknown_url_scheme_policy(value)
|
|
||||||
return False
|
|
||||||
return super()._update_setting(setting, value)
|
return super()._update_setting(setting, value)
|
||||||
|
|
||||||
def init_settings(self):
|
def init_settings(self):
|
||||||
super().init_settings()
|
super().init_settings()
|
||||||
self.update_setting('content.unknown_url_scheme_policy')
|
self.update_setting('content.unknown_url_scheme_policy')
|
||||||
|
|
||||||
def __init__(self, settings):
|
|
||||||
super().__init__(settings)
|
|
||||||
# Attributes which don't exist in all Qt versions.
|
|
||||||
new_attributes = {
|
|
||||||
# Qt 5.8
|
|
||||||
'content.print_element_backgrounds':
|
|
||||||
('PrintElementBackgrounds', None),
|
|
||||||
|
|
||||||
# Qt 5.11
|
|
||||||
'content.autoplay':
|
|
||||||
('PlaybackRequiresUserGesture', operator.not_),
|
|
||||||
|
|
||||||
# Qt 5.12
|
|
||||||
'content.dns_prefetch':
|
|
||||||
('DnsPrefetchEnabled', None),
|
|
||||||
}
|
|
||||||
for name, (attribute, converter) in new_attributes.items():
|
|
||||||
try:
|
|
||||||
value = getattr(QWebEngineSettings, attribute)
|
|
||||||
except AttributeError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
self._ATTRIBUTES[name] = Attr(value, converter=converter)
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileSetter:
|
class ProfileSetter:
|
||||||
|
|
||||||
|
|
@ -246,8 +223,7 @@ class ProfileSetter:
|
||||||
self.set_http_headers()
|
self.set_http_headers()
|
||||||
self.set_http_cache_size()
|
self.set_http_cache_size()
|
||||||
self._set_hardcoded_settings()
|
self._set_hardcoded_settings()
|
||||||
if qtutils.version_check('5.8'):
|
self.set_dictionary_language()
|
||||||
self.set_dictionary_language()
|
|
||||||
|
|
||||||
def _set_hardcoded_settings(self):
|
def _set_hardcoded_settings(self):
|
||||||
"""Set up settings with a fixed value."""
|
"""Set up settings with a fixed value."""
|
||||||
|
|
@ -255,13 +231,8 @@ class ProfileSetter:
|
||||||
|
|
||||||
settings.setAttribute(
|
settings.setAttribute(
|
||||||
QWebEngineSettings.FullScreenSupportEnabled, True)
|
QWebEngineSettings.FullScreenSupportEnabled, True)
|
||||||
|
settings.setAttribute(
|
||||||
try:
|
QWebEngineSettings.FocusOnNavigationEnabled, False)
|
||||||
settings.setAttribute(
|
|
||||||
QWebEngineSettings.FocusOnNavigationEnabled, False)
|
|
||||||
except AttributeError:
|
|
||||||
# Added in Qt 5.8
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
settings.setAttribute(QWebEngineSettings.PdfViewerEnabled, False)
|
settings.setAttribute(QWebEngineSettings.PdfViewerEnabled, False)
|
||||||
|
|
@ -328,8 +299,7 @@ def _update_settings(option):
|
||||||
|
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
|
||||||
# (note this isn't actually fixed properly before Qt 5.15)
|
# (note this isn't actually fixed properly before Qt 5.15)
|
||||||
header_bug_fixed = (not qtutils.version_check('5.12', compiled=False) or
|
header_bug_fixed = qtutils.version_check('5.15', compiled=False)
|
||||||
qtutils.version_check('5.15', compiled=False))
|
|
||||||
|
|
||||||
if option in ['content.headers.user_agent',
|
if option in ['content.headers.user_agent',
|
||||||
'content.headers.accept_language'] and header_bug_fixed:
|
'content.headers.accept_language'] and header_bug_fixed:
|
||||||
|
|
@ -340,9 +310,7 @@ def _update_settings(option):
|
||||||
default_profile.setter.set_http_cache_size()
|
default_profile.setter.set_http_cache_size()
|
||||||
if private_profile:
|
if private_profile:
|
||||||
private_profile.setter.set_http_cache_size()
|
private_profile.setter.set_http_cache_size()
|
||||||
elif (option == 'content.cookies.store' and
|
elif option == 'content.cookies.store':
|
||||||
# https://bugreports.qt.io/browse/QTBUG-58650
|
|
||||||
qtutils.version_check('5.9', compiled=False)):
|
|
||||||
default_profile.setter.set_persistent_cookie_policy()
|
default_profile.setter.set_persistent_cookie_policy()
|
||||||
# We're not touching the private profile's cookie policy.
|
# We're not touching the private profile's cookie policy.
|
||||||
elif option == 'spellcheck.languages':
|
elif option == 'spellcheck.languages':
|
||||||
|
|
@ -436,10 +404,6 @@ def _init_site_specific_quirks():
|
||||||
'https://*.slack.com/*': new_chrome_ua,
|
'https://*.slack.com/*': new_chrome_ua,
|
||||||
}
|
}
|
||||||
|
|
||||||
if not qtutils.version_check('5.9'):
|
|
||||||
# Shows 502 Bad Gateway with the Qt 5.7 UA.
|
|
||||||
user_agents['https://www.dell.com/support/*'] = new_chrome_ua
|
|
||||||
|
|
||||||
for pattern, ua in user_agents.items():
|
for pattern, ua in user_agents.items():
|
||||||
config.instance.set_obj('content.headers.user_agent', ua,
|
config.instance.set_obj('content.headers.user_agent', ua,
|
||||||
pattern=urlmatch.UrlPattern(pattern),
|
pattern=urlmatch.UrlPattern(pattern),
|
||||||
|
|
@ -450,10 +414,9 @@ def _init_devtools_settings():
|
||||||
"""Make sure the devtools always get images/JS permissions."""
|
"""Make sure the devtools always get images/JS permissions."""
|
||||||
settings: List[Tuple[str, Any]] = [
|
settings: List[Tuple[str, Any]] = [
|
||||||
('content.javascript.enabled', True),
|
('content.javascript.enabled', True),
|
||||||
('content.images', True)
|
('content.images', True),
|
||||||
|
('content.cookies.accept', 'all'),
|
||||||
]
|
]
|
||||||
if qtutils.version_check('5.11'):
|
|
||||||
settings.append(('content.cookies.accept', 'all'))
|
|
||||||
|
|
||||||
for setting, value in settings:
|
for setting, value in settings:
|
||||||
for pattern in ['chrome-devtools://*', 'devtools://*']:
|
for pattern in ['chrome-devtools://*', 'devtools://*']:
|
||||||
|
|
@ -462,12 +425,8 @@ def _init_devtools_settings():
|
||||||
hide_userconfig=True)
|
hide_userconfig=True)
|
||||||
|
|
||||||
|
|
||||||
def init(args):
|
def init():
|
||||||
"""Initialize the global QWebSettings."""
|
"""Initialize the global QWebSettings."""
|
||||||
if (args.enable_webengine_inspector and
|
|
||||||
not hasattr(QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11
|
|
||||||
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
|
|
||||||
|
|
||||||
webenginequtescheme.init()
|
webenginequtescheme.init()
|
||||||
spell.init()
|
spell.init()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,14 @@ import math
|
||||||
import functools
|
import functools
|
||||||
import re
|
import re
|
||||||
import html as html_utils
|
import html as html_utils
|
||||||
from typing import cast, Optional, Union
|
from typing import cast, Union
|
||||||
|
|
||||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl,
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl, QObject
|
||||||
QTimer, QObject)
|
|
||||||
from PyQt5.QtNetwork import QAuthenticator
|
from PyQt5.QtNetwork import QAuthenticator
|
||||||
from PyQt5.QtWidgets import QApplication, QWidget
|
from PyQt5.QtWidgets import QApplication, QWidget
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineHistory
|
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineHistory
|
||||||
|
|
||||||
from qutebrowser.config import configdata, config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.browser import (browsertab, eventfilter, shared, webelem,
|
from qutebrowser.browser import (browsertab, eventfilter, shared, webelem,
|
||||||
history, greasemonkey)
|
history, greasemonkey)
|
||||||
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
||||||
|
|
@ -118,18 +117,7 @@ class WebEngineAction(browsertab.AbstractAction):
|
||||||
self._show_source_pygments()
|
self._show_source_pygments()
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
self._widget.triggerPageAction(QWebEnginePage.ViewSource)
|
||||||
self._widget.triggerPageAction(QWebEnginePage.ViewSource)
|
|
||||||
except AttributeError:
|
|
||||||
# Qt < 5.8
|
|
||||||
tb = objreg.get('tabbed-browser', scope='window',
|
|
||||||
window=self._tab.win_id)
|
|
||||||
urlstr = self._tab.url().toString(
|
|
||||||
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)
|
|
||||||
tb.tabopen(url, background=False, related=True)
|
|
||||||
|
|
||||||
|
|
||||||
class WebEnginePrinting(browsertab.AbstractPrinting):
|
class WebEnginePrinting(browsertab.AbstractPrinting):
|
||||||
|
|
@ -139,11 +127,6 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
|
||||||
def check_pdf_support(self):
|
def check_pdf_support(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def check_printer_support(self):
|
|
||||||
if not hasattr(self._widget.page(), 'print'):
|
|
||||||
raise browsertab.WebTabError(
|
|
||||||
"Printing is unsupported with QtWebEngine on Qt < 5.8")
|
|
||||||
|
|
||||||
def check_preview_support(self):
|
def check_preview_support(self):
|
||||||
raise browsertab.WebTabError(
|
raise browsertab.WebTabError(
|
||||||
"Print previews are unsupported with QtWebEngine")
|
"Print previews are unsupported with QtWebEngine")
|
||||||
|
|
@ -360,8 +343,6 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
||||||
def _flags(self):
|
def _flags(self):
|
||||||
"""Get flags to pass to JS."""
|
"""Get flags to pass to JS."""
|
||||||
flags = set()
|
flags = set()
|
||||||
if qtutils.version_check('5.7.1', compiled=False):
|
|
||||||
flags.add('filter-prefix')
|
|
||||||
if utils.is_windows:
|
if utils.is_windows:
|
||||||
flags.add('windows')
|
flags.add('windows')
|
||||||
return list(flags)
|
return list(flags)
|
||||||
|
|
@ -374,7 +355,6 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
||||||
if self._tab.search.search_displayed:
|
if self._tab.search.search_displayed:
|
||||||
# We are currently in search mode.
|
# We are currently in search mode.
|
||||||
# convert the search to a blue selection so we can operate on it
|
# convert the search to a blue selection so we can operate on it
|
||||||
# https://bugreports.qt.io/browse/QTBUG-60673
|
|
||||||
self._tab.search.clear()
|
self._tab.search.clear()
|
||||||
|
|
||||||
self._tab.run_js_async(
|
self._tab.run_js_async(
|
||||||
|
|
@ -507,7 +487,6 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
||||||
if self._tab.search.search_displayed:
|
if self._tab.search.search_displayed:
|
||||||
# We are currently in search mode.
|
# We are currently in search mode.
|
||||||
# let's click the link via a fake-click
|
# let's click the link via a fake-click
|
||||||
# https://bugreports.qt.io/browse/QTBUG-60673
|
|
||||||
self._tab.search.clear()
|
self._tab.search.clear()
|
||||||
|
|
||||||
log.webview.debug("Clicking a searched link via fake key press.")
|
log.webview.debug("Clicking a searched link via fake key press.")
|
||||||
|
|
@ -673,15 +652,6 @@ class WebEngineHistoryPrivate(browsertab.AbstractHistoryPrivate):
|
||||||
self._history = cast(QWebEngineHistory, None)
|
self._history = cast(QWebEngineHistory, None)
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
if not qtutils.version_check('5.9', compiled=False):
|
|
||||||
# WORKAROUND for
|
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/2289
|
|
||||||
# Don't use the history's currentItem here, because of
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-59599 and because it doesn't
|
|
||||||
# contain view-source.
|
|
||||||
scheme = self._tab.url().scheme()
|
|
||||||
if scheme in ['view-source', 'chrome']:
|
|
||||||
raise browsertab.WebTabError("Can't serialize special URL!")
|
|
||||||
return qtutils.serialize(self._history)
|
return qtutils.serialize(self._history)
|
||||||
|
|
||||||
def deserialize(self, data):
|
def deserialize(self, data):
|
||||||
|
|
@ -912,8 +882,10 @@ class _WebEnginePermissions(QObject):
|
||||||
QWebEnginePage.Geolocation: 'content.geolocation',
|
QWebEnginePage.Geolocation: 'content.geolocation',
|
||||||
QWebEnginePage.MediaAudioCapture: 'content.media.audio_capture',
|
QWebEnginePage.MediaAudioCapture: 'content.media.audio_capture',
|
||||||
QWebEnginePage.MediaVideoCapture: 'content.media.video_capture',
|
QWebEnginePage.MediaVideoCapture: 'content.media.video_capture',
|
||||||
QWebEnginePage.MediaAudioVideoCapture:
|
QWebEnginePage.MediaAudioVideoCapture: 'content.media.audio_video_capture',
|
||||||
'content.media.audio_video_capture',
|
QWebEnginePage.MouseLock: 'content.mouse_lock',
|
||||||
|
QWebEnginePage.DesktopVideoCapture: 'content.desktop_capture',
|
||||||
|
QWebEnginePage.DesktopAudioVideoCapture: 'content.desktop_capture',
|
||||||
}
|
}
|
||||||
|
|
||||||
_messages = {
|
_messages = {
|
||||||
|
|
@ -922,42 +894,15 @@ class _WebEnginePermissions(QObject):
|
||||||
QWebEnginePage.MediaAudioCapture: 'record audio',
|
QWebEnginePage.MediaAudioCapture: 'record audio',
|
||||||
QWebEnginePage.MediaVideoCapture: 'record video',
|
QWebEnginePage.MediaVideoCapture: 'record video',
|
||||||
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
|
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
|
||||||
|
QWebEnginePage.MouseLock: 'hide your mouse pointer',
|
||||||
|
QWebEnginePage.DesktopVideoCapture: 'capture your desktop',
|
||||||
|
QWebEnginePage.DesktopAudioVideoCapture: 'capture your desktop and audio',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, tab, parent=None):
|
def __init__(self, tab, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._tab = tab
|
self._tab = tab
|
||||||
self._widget = cast(QWidget, None)
|
self._widget = cast(QWidget, None)
|
||||||
|
|
||||||
try:
|
|
||||||
self._options.update({
|
|
||||||
QWebEnginePage.MouseLock:
|
|
||||||
'content.mouse_lock',
|
|
||||||
})
|
|
||||||
self._messages.update({
|
|
||||||
QWebEnginePage.MouseLock:
|
|
||||||
'hide your mouse pointer',
|
|
||||||
})
|
|
||||||
except AttributeError:
|
|
||||||
# Added in Qt 5.8
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
self._options.update({
|
|
||||||
QWebEnginePage.DesktopVideoCapture:
|
|
||||||
'content.desktop_capture',
|
|
||||||
QWebEnginePage.DesktopAudioVideoCapture:
|
|
||||||
'content.desktop_capture',
|
|
||||||
})
|
|
||||||
self._messages.update({
|
|
||||||
QWebEnginePage.DesktopVideoCapture:
|
|
||||||
'capture your desktop',
|
|
||||||
QWebEnginePage.DesktopAudioVideoCapture:
|
|
||||||
'capture your desktop and audio',
|
|
||||||
})
|
|
||||||
except AttributeError:
|
|
||||||
# Added in Qt 5.10
|
|
||||||
pass
|
|
||||||
|
|
||||||
assert self._options.keys() == self._messages.keys()
|
assert self._options.keys() == self._messages.keys()
|
||||||
|
|
||||||
def connect_signals(self):
|
def connect_signals(self):
|
||||||
|
|
@ -968,11 +913,9 @@ class _WebEnginePermissions(QObject):
|
||||||
page.featurePermissionRequested.connect(
|
page.featurePermissionRequested.connect(
|
||||||
self._on_feature_permission_requested)
|
self._on_feature_permission_requested)
|
||||||
|
|
||||||
if qtutils.version_check('5.11'):
|
page.quotaRequested.connect(self._on_quota_requested)
|
||||||
page.quotaRequested.connect(
|
page.registerProtocolHandlerRequested.connect(
|
||||||
self._on_quota_requested)
|
self._on_register_protocol_handler_requested)
|
||||||
page.registerProtocolHandlerRequested.connect(
|
|
||||||
self._on_register_protocol_handler_requested)
|
|
||||||
|
|
||||||
@pyqtSlot('QWebEngineFullScreenRequest')
|
@pyqtSlot('QWebEngineFullScreenRequest')
|
||||||
def _on_fullscreen_requested(self, request):
|
def _on_fullscreen_requested(self, request):
|
||||||
|
|
@ -1021,7 +964,6 @@ class _WebEnginePermissions(QObject):
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hasattr(QWebEnginePage, 'DesktopVideoCapture') and
|
|
||||||
feature in [QWebEnginePage.DesktopVideoCapture,
|
feature in [QWebEnginePage.DesktopVideoCapture,
|
||||||
QWebEnginePage.DesktopAudioVideoCapture] and
|
QWebEnginePage.DesktopAudioVideoCapture] and
|
||||||
qtutils.version_check('5.13', compiled=False) and
|
qtutils.version_check('5.13', compiled=False) and
|
||||||
|
|
@ -1110,35 +1052,21 @@ class _WebEngineScripts(QObject):
|
||||||
def _inject_early_js(self, name, js_code, *,
|
def _inject_early_js(self, name, js_code, *,
|
||||||
world=QWebEngineScript.ApplicationWorld,
|
world=QWebEngineScript.ApplicationWorld,
|
||||||
subframes=False):
|
subframes=False):
|
||||||
"""Inject the given script to run early on a page load.
|
"""Inject the given script to run early on a page load."""
|
||||||
|
script = QWebEngineScript()
|
||||||
This runs the script both on DocumentCreation and DocumentReady as on
|
script.setInjectionPoint(QWebEngineScript.DocumentCreation)
|
||||||
some internal pages, DocumentCreation will not work.
|
script.setSourceCode(js_code)
|
||||||
|
script.setWorldId(world)
|
||||||
That is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011
|
script.setRunsOnSubFrames(subframes)
|
||||||
"""
|
script.setName(f'_qute_{name}')
|
||||||
scripts = self._widget.page().scripts()
|
self._widget.page().scripts().insert(script)
|
||||||
for injection in ['creation', 'ready']:
|
|
||||||
injection_points = {
|
|
||||||
'creation': QWebEngineScript.DocumentCreation,
|
|
||||||
'ready': QWebEngineScript.DocumentReady,
|
|
||||||
}
|
|
||||||
script = QWebEngineScript()
|
|
||||||
script.setInjectionPoint(injection_points[injection])
|
|
||||||
script.setSourceCode(js_code)
|
|
||||||
script.setWorldId(world)
|
|
||||||
script.setRunsOnSubFrames(subframes)
|
|
||||||
script.setName('_qute_{}_{}'.format(name, injection))
|
|
||||||
scripts.insert(script)
|
|
||||||
|
|
||||||
def _remove_early_js(self, name):
|
def _remove_early_js(self, name):
|
||||||
"""Remove an early QWebEngineScript."""
|
"""Remove an early QWebEngineScript."""
|
||||||
scripts = self._widget.page().scripts()
|
scripts = self._widget.page().scripts()
|
||||||
for injection in ['creation', 'ready']:
|
script = scripts.findScript(f'_qute_{name}')
|
||||||
full_name = '_qute_{}_{}'.format(name, injection)
|
if not script.isNull():
|
||||||
script = scripts.findScript(full_name)
|
scripts.remove(script)
|
||||||
if not script.isNull():
|
|
||||||
scripts.remove(script)
|
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
"""Initialize global qutebrowser JavaScript."""
|
"""Initialize global qutebrowser JavaScript."""
|
||||||
|
|
@ -1148,29 +1076,14 @@ class _WebEngineScripts(QObject):
|
||||||
utils.read_file('javascript/webelem.js'),
|
utils.read_file('javascript/webelem.js'),
|
||||||
utils.read_file('javascript/caret.js'),
|
utils.read_file('javascript/caret.js'),
|
||||||
)
|
)
|
||||||
if not qtutils.version_check('5.12'):
|
|
||||||
# WORKAROUND for Qt versions < 5.12 not exposing window.print().
|
|
||||||
# Qt 5.12 has a printRequested() signal so we don't need this hack
|
|
||||||
# anymore.
|
|
||||||
self._inject_early_js('js',
|
|
||||||
utils.read_file('javascript/print.js'),
|
|
||||||
subframes=True,
|
|
||||||
world=QWebEngineScript.MainWorld)
|
|
||||||
# FIXME:qtwebengine what about subframes=True?
|
# FIXME:qtwebengine what about subframes=True?
|
||||||
self._inject_early_js('js', js_code, subframes=True)
|
self._inject_early_js('js', js_code, subframes=True)
|
||||||
self._init_stylesheet()
|
self._init_stylesheet()
|
||||||
|
|
||||||
# The Greasemonkey metadata block support in QtWebEngine only starts at
|
self._greasemonkey.scripts_reloaded.connect(
|
||||||
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
|
self._inject_all_greasemonkey_scripts)
|
||||||
# response to urlChanged.
|
self._inject_all_greasemonkey_scripts()
|
||||||
if not qtutils.version_check('5.8'):
|
self._inject_site_specific_quirks()
|
||||||
self._tab.url_changed.connect(
|
|
||||||
self._inject_greasemonkey_scripts_for_url)
|
|
||||||
else:
|
|
||||||
self._greasemonkey.scripts_reloaded.connect(
|
|
||||||
self._inject_all_greasemonkey_scripts)
|
|
||||||
self._inject_all_greasemonkey_scripts()
|
|
||||||
self._inject_site_specific_quirks()
|
|
||||||
|
|
||||||
def _init_stylesheet(self):
|
def _init_stylesheet(self):
|
||||||
"""Initialize custom stylesheets.
|
"""Initialize custom stylesheets.
|
||||||
|
|
@ -1187,16 +1100,6 @@ class _WebEngineScripts(QObject):
|
||||||
)
|
)
|
||||||
self._inject_early_js('stylesheet', js_code, subframes=True)
|
self._inject_early_js('stylesheet', js_code, subframes=True)
|
||||||
|
|
||||||
@pyqtSlot(QUrl)
|
|
||||||
def _inject_greasemonkey_scripts_for_url(self, url):
|
|
||||||
matching_scripts = self._greasemonkey.scripts_for(url)
|
|
||||||
self._inject_greasemonkey_scripts(
|
|
||||||
matching_scripts.start, QWebEngineScript.DocumentCreation, True)
|
|
||||||
self._inject_greasemonkey_scripts(
|
|
||||||
matching_scripts.end, QWebEngineScript.DocumentReady, False)
|
|
||||||
self._inject_greasemonkey_scripts(
|
|
||||||
matching_scripts.idle, QWebEngineScript.Deferred, False)
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _inject_all_greasemonkey_scripts(self):
|
def _inject_all_greasemonkey_scripts(self):
|
||||||
scripts = self._greasemonkey.all_scripts()
|
scripts = self._greasemonkey.all_scripts()
|
||||||
|
|
@ -1279,13 +1182,7 @@ class _WebEngineScripts(QObject):
|
||||||
page_scripts.insert(new_script)
|
page_scripts.insert(new_script)
|
||||||
|
|
||||||
def _inject_site_specific_quirks(self):
|
def _inject_site_specific_quirks(self):
|
||||||
"""Add site-specific quirk scripts.
|
"""Add site-specific quirk scripts."""
|
||||||
|
|
||||||
NOTE: This isn't implemented for Qt 5.7 because of different UserScript
|
|
||||||
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:
|
if not config.val.content.site_specific_quirks:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -1301,6 +1198,9 @@ class _WebEngineScripts(QObject):
|
||||||
quirks.append(('globalthis_quirk',
|
quirks.append(('globalthis_quirk',
|
||||||
QWebEngineScript.DocumentCreation,
|
QWebEngineScript.DocumentCreation,
|
||||||
QWebEngineScript.MainWorld))
|
QWebEngineScript.MainWorld))
|
||||||
|
quirks.append(('object_fromentries_quirk',
|
||||||
|
QWebEngineScript.DocumentCreation,
|
||||||
|
QWebEngineScript.MainWorld))
|
||||||
|
|
||||||
for filename, injection_point, world in quirks:
|
for filename, injection_point, world in quirks:
|
||||||
script = QWebEngineScript()
|
script = QWebEngineScript()
|
||||||
|
|
@ -1376,7 +1276,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
self.backend = usertypes.Backend.QtWebEngine
|
self.backend = usertypes.Backend.QtWebEngine
|
||||||
self._child_event_filter = None
|
self._child_event_filter = None
|
||||||
self._saved_zoom = None
|
self._saved_zoom = None
|
||||||
self._reload_url: Optional[QUrl] = None
|
|
||||||
self._scripts.init()
|
self._scripts.init()
|
||||||
|
|
||||||
def _set_widget(self, widget):
|
def _set_widget(self, widget):
|
||||||
|
|
@ -1396,13 +1295,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
parent=self)
|
parent=self)
|
||||||
self._widget.installEventFilter(self._child_event_filter)
|
self._widget.installEventFilter(self._child_event_filter)
|
||||||
|
|
||||||
if qtutils.version_check('5.11', compiled=False, exact=True):
|
|
||||||
focus_event_filter = eventfilter.FocusWorkaroundEventFilter(
|
|
||||||
win_id=self.win_id,
|
|
||||||
widget=self._widget,
|
|
||||||
parent=self)
|
|
||||||
self._widget.installEventFilter(focus_event_filter)
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _restore_zoom(self):
|
def _restore_zoom(self):
|
||||||
if sip.isdeleted(self._widget):
|
if sip.isdeleted(self._widget):
|
||||||
|
|
@ -1413,20 +1305,17 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
self.zoom.set_factor(self._saved_zoom)
|
self.zoom.set_factor(self._saved_zoom)
|
||||||
self._saved_zoom = None
|
self._saved_zoom = None
|
||||||
|
|
||||||
def load_url(self, url, *, emit_before_load_started=True):
|
def load_url(self, url):
|
||||||
"""Load the given URL in this tab.
|
"""Load the given URL in this tab.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
url: The QUrl to load.
|
url: The QUrl to load.
|
||||||
emit_before_load_started: If set to False, before_load_started is
|
|
||||||
not emitted.
|
|
||||||
"""
|
"""
|
||||||
if sip.isdeleted(self._widget):
|
if sip.isdeleted(self._widget):
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/3896
|
# https://github.com/qutebrowser/qutebrowser/issues/3896
|
||||||
return
|
return
|
||||||
self._saved_zoom = self.zoom.factor()
|
self._saved_zoom = self.zoom.factor()
|
||||||
self._load_url_prepare(
|
self._load_url_prepare(url)
|
||||||
url, emit_before_load_started=emit_before_load_started)
|
|
||||||
self._widget.load(url)
|
self._widget.load(url)
|
||||||
|
|
||||||
def url(self, *, requested=False):
|
def url(self, *, requested=False):
|
||||||
|
|
@ -1536,14 +1425,12 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
mode=usertypes.PromptMode.user_pwd,
|
mode=usertypes.PromptMode.user_pwd,
|
||||||
abort_on=[self.abort_questions], url=urlstr)
|
abort_on=[self.abort_questions], url=urlstr)
|
||||||
|
|
||||||
if answer is not None:
|
if answer is None:
|
||||||
authenticator.setUser(answer.user)
|
sip.assign(authenticator, QAuthenticator())
|
||||||
authenticator.setPassword(answer.password)
|
return
|
||||||
else:
|
|
||||||
try:
|
authenticator.setUser(answer.user)
|
||||||
sip.assign(authenticator, QAuthenticator())
|
authenticator.setPassword(answer.password)
|
||||||
except AttributeError:
|
|
||||||
self._show_error_page(url, "Proxy authentication required")
|
|
||||||
|
|
||||||
@pyqtSlot(QUrl, 'QAuthenticator*')
|
@pyqtSlot(QUrl, 'QAuthenticator*')
|
||||||
def _on_authentication_required(self, url, authenticator):
|
def _on_authentication_required(self, url, authenticator):
|
||||||
|
|
@ -1561,12 +1448,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
url, authenticator, abort_on=[self.abort_questions])
|
url, authenticator, abort_on=[self.abort_questions])
|
||||||
if not netrc_success and answer is None:
|
if not netrc_success and answer is None:
|
||||||
log.network.debug("Aborting auth")
|
log.network.debug("Aborting auth")
|
||||||
try:
|
sip.assign(authenticator, QAuthenticator())
|
||||||
sip.assign(authenticator, QAuthenticator())
|
|
||||||
except AttributeError:
|
|
||||||
# WORKAROUND for
|
|
||||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
|
|
||||||
self._show_error_page(url, "Authentication required")
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _on_load_started(self):
|
def _on_load_started(self):
|
||||||
|
|
@ -1614,9 +1496,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
|
|
||||||
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66643
|
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66643
|
||||||
WORKAROUND for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=882805
|
WORKAROUND for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=882805
|
||||||
|
|
||||||
Needs to check the page content as a WORKAROUND for
|
|
||||||
https://bugreports.qt.io/browse/QTBUG-66661
|
|
||||||
"""
|
"""
|
||||||
match = re.search(r'"errorCode":"([^"]*)"', html)
|
match = re.search(r'"errorCode":"([^"]*)"', html)
|
||||||
if match is None:
|
if match is None:
|
||||||
|
|
@ -1639,7 +1518,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
"""
|
"""
|
||||||
super()._on_load_progress(perc)
|
super()._on_load_progress(perc)
|
||||||
if (perc == 100 and
|
if (perc == 100 and
|
||||||
qtutils.version_check('5.10', compiled=False) and
|
|
||||||
self.load_status() != usertypes.LoadStatus.error):
|
self.load_status() != usertypes.LoadStatus.error):
|
||||||
self._update_load_status(ok=True)
|
self._update_load_status(ok=True)
|
||||||
|
|
||||||
|
|
@ -1648,28 +1526,14 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
"""QtWebEngine-specific loadFinished workarounds."""
|
"""QtWebEngine-specific loadFinished workarounds."""
|
||||||
super()._on_load_finished(ok)
|
super()._on_load_finished(ok)
|
||||||
|
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
if not ok:
|
||||||
if qtutils.version_check('5.10', compiled=False):
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||||
if not ok:
|
|
||||||
self._update_load_status(ok)
|
|
||||||
else:
|
|
||||||
self._update_load_status(ok)
|
self._update_load_status(ok)
|
||||||
|
|
||||||
if not ok:
|
|
||||||
self.dump_async(functools.partial(
|
self.dump_async(functools.partial(
|
||||||
self._error_page_workaround,
|
self._error_page_workaround,
|
||||||
self.settings.test_attribute('content.javascript.enabled')))
|
self.settings.test_attribute('content.javascript.enabled')))
|
||||||
|
|
||||||
if ok and self._reload_url is not None:
|
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
|
|
||||||
log.config.debug(
|
|
||||||
"Loading {} again because of config change".format(
|
|
||||||
self._reload_url.toDisplayString()))
|
|
||||||
QTimer.singleShot(100, functools.partial(
|
|
||||||
self.load_url, self._reload_url,
|
|
||||||
emit_before_load_started=False))
|
|
||||||
self._reload_url = None
|
|
||||||
|
|
||||||
@pyqtSlot(certificateerror.CertificateErrorWrapper)
|
@pyqtSlot(certificateerror.CertificateErrorWrapper)
|
||||||
def _on_ssl_errors(self, error):
|
def _on_ssl_errors(self, error):
|
||||||
url = error.url()
|
url = error.url()
|
||||||
|
|
@ -1687,23 +1551,17 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
log.network.debug("ignore {}, URL {}, requested {}".format(
|
log.network.debug("ignore {}, URL {}, requested {}".format(
|
||||||
error.ignore, url, self.url(requested=True)))
|
error.ignore, url, self.url(requested=True)))
|
||||||
|
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207
|
|
||||||
show_cert_error = (
|
|
||||||
not qtutils.version_check('5.9') and
|
|
||||||
not error.ignore
|
|
||||||
)
|
|
||||||
# WORKAROUND for https://codereview.qt-project.org/c/qt/qtwebengine/+/270556
|
# WORKAROUND for https://codereview.qt-project.org/c/qt/qtwebengine/+/270556
|
||||||
show_non_overr_cert_error = (
|
show_non_overr_cert_error = (
|
||||||
not error.is_overridable() and (
|
not error.is_overridable() and (
|
||||||
# Affected Qt versions:
|
# Affected Qt versions:
|
||||||
# 5.13 before 5.13.2
|
# 5.13 before 5.13.2
|
||||||
# 5.12 before 5.12.6
|
# 5.12 before 5.12.6
|
||||||
# < 5.12
|
# < 5.12 (which is unsupported)
|
||||||
(qtutils.version_check('5.13') and
|
(qtutils.version_check('5.13') and
|
||||||
not qtutils.version_check('5.13.2')) or
|
not qtutils.version_check('5.13.2')) or
|
||||||
(qtutils.version_check('5.12') and
|
(qtutils.version_check('5.12') and
|
||||||
not qtutils.version_check('5.12.6')) or
|
not qtutils.version_check('5.12.6'))
|
||||||
not qtutils.version_check('5.12')
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1712,20 +1570,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
# However, self.url() is not available yet and the requested URL
|
# However, self.url() is not available yet and the requested URL
|
||||||
# might not match the URL we get from the error - so we just apply a
|
# might not match the URL we get from the error - so we just apply a
|
||||||
# heuristic here.
|
# heuristic here.
|
||||||
if ((show_cert_error or show_non_overr_cert_error) and
|
if (show_non_overr_cert_error and
|
||||||
url.matches(self.data.last_navigation.url, QUrl.RemoveScheme)):
|
url.matches(self.data.last_navigation.url, QUrl.RemoveScheme)):
|
||||||
self._show_error_page(url, str(error))
|
self._show_error_page(url, str(error))
|
||||||
|
|
||||||
@pyqtSlot(QUrl)
|
|
||||||
def _on_before_load_started(self, url):
|
|
||||||
"""If we know we're going to visit a URL soon, change the settings.
|
|
||||||
|
|
||||||
This is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
|
|
||||||
"""
|
|
||||||
super()._on_before_load_started(url)
|
|
||||||
if not qtutils.version_check('5.11.1', compiled=False):
|
|
||||||
self.settings.update_for_url(url)
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _on_print_requested(self):
|
def _on_print_requested(self):
|
||||||
"""Slot for window.print() in JS."""
|
"""Slot for window.print() in JS."""
|
||||||
|
|
@ -1753,38 +1601,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
def _on_navigation_request(self, navigation):
|
def _on_navigation_request(self, navigation):
|
||||||
super()._on_navigation_request(navigation)
|
super()._on_navigation_request(navigation)
|
||||||
|
|
||||||
if navigation.url == QUrl('qute://print'):
|
|
||||||
self._on_print_requested()
|
|
||||||
navigation.accepted = False
|
|
||||||
|
|
||||||
if not navigation.accepted or not navigation.is_main_frame:
|
if not navigation.accepted or not navigation.is_main_frame:
|
||||||
return
|
return
|
||||||
|
|
||||||
settings_needing_reload = {
|
self.settings.update_for_url(navigation.url)
|
||||||
'content.plugins',
|
|
||||||
'content.javascript.enabled',
|
|
||||||
'content.javascript.can_access_clipboard',
|
|
||||||
'content.print_element_backgrounds',
|
|
||||||
'input.spatial_navigation',
|
|
||||||
}
|
|
||||||
assert settings_needing_reload.issubset(configdata.DATA)
|
|
||||||
|
|
||||||
changed = self.settings.update_for_url(navigation.url)
|
|
||||||
reload_needed = bool(changed & settings_needing_reload)
|
|
||||||
|
|
||||||
# On Qt < 5.11, we don't don't need a reload when type == link_clicked.
|
|
||||||
# On Qt 5.11.0, we always need a reload.
|
|
||||||
# On Qt > 5.11.0, we never need a reload:
|
|
||||||
# https://codereview.qt-project.org/#/c/229525/1
|
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
|
|
||||||
if qtutils.version_check('5.11.1', compiled=False):
|
|
||||||
reload_needed = False
|
|
||||||
elif not qtutils.version_check('5.11.0', exact=True, compiled=False):
|
|
||||||
if navigation.navigation_type == navigation.Type.link_clicked:
|
|
||||||
reload_needed = False
|
|
||||||
|
|
||||||
if reload_needed:
|
|
||||||
self._reload_url = navigation.url
|
|
||||||
|
|
||||||
def _on_select_client_certificate(self, selection):
|
def _on_select_client_certificate(self, selection):
|
||||||
"""Handle client certificates.
|
"""Handle client certificates.
|
||||||
|
|
@ -1831,9 +1651,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
self._on_proxy_authentication_required)
|
self._on_proxy_authentication_required)
|
||||||
page.contentsSizeChanged.connect(self.contents_size_changed)
|
page.contentsSizeChanged.connect(self.contents_size_changed)
|
||||||
page.navigation_request.connect(self._on_navigation_request)
|
page.navigation_request.connect(self._on_navigation_request)
|
||||||
|
page.printRequested.connect(self._on_print_requested)
|
||||||
if qtutils.version_check('5.12'):
|
|
||||||
page.printRequested.connect(self._on_print_requested)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
|
|
@ -1861,7 +1679,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||||
# Added in Qt 5.15.0
|
# Added in Qt 5.15.0
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.before_load_started.connect(self._on_before_load_started)
|
|
||||||
self.shutting_down.connect(self.abort_questions)
|
self.shutting_down.connect(self.abort_questions)
|
||||||
self.load_started.connect(self.abort_questions)
|
self.load_started.connect(self.abort_questions)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,15 @@
|
||||||
|
|
||||||
"""The main browser widget for QtWebEngine."""
|
"""The main browser widget for QtWebEngine."""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
|
from PyQt5.QtCore import pyqtSignal, QUrl
|
||||||
from PyQt5.QtGui import QPalette
|
from PyQt5.QtGui import QPalette
|
||||||
from PyQt5.QtWidgets import QWidget
|
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||||
|
|
||||||
from qutebrowser.browser import shared
|
from qutebrowser.browser import shared
|
||||||
from qutebrowser.browser.webengine import webenginesettings, certificateerror
|
from qutebrowser.browser.webengine import webenginesettings, certificateerror
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import log, debug, usertypes, qtutils
|
from qutebrowser.utils import log, debug, usertypes
|
||||||
from qutebrowser.misc import miscwidgets, objects
|
|
||||||
from qutebrowser.qt import sip
|
|
||||||
|
|
||||||
|
|
||||||
class WebEngineView(QWebEngineView):
|
class WebEngineView(QWebEngineView):
|
||||||
|
|
@ -54,42 +50,9 @@ class WebEngineView(QWebEngineView):
|
||||||
parent=self)
|
parent=self)
|
||||||
self.setPage(page)
|
self.setPage(page)
|
||||||
|
|
||||||
if qtutils.version_check('5.11.0', compiled=False, exact=True):
|
|
||||||
# Set a PseudoLayout as a WORKAROUND for
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-68224
|
|
||||||
# and other related issues. (Fixed in Qt 5.11.1)
|
|
||||||
sip.delete(self.layout())
|
|
||||||
self._layout = miscwidgets.PseudoLayout(self)
|
|
||||||
|
|
||||||
def render_widget(self):
|
def render_widget(self):
|
||||||
"""Get the RenderWidgetHostViewQt for this view.
|
"""Get the RenderWidgetHostViewQt for this view."""
|
||||||
|
return self.focusProxy()
|
||||||
Normally, this would always be the focusProxy().
|
|
||||||
However, it sometimes isn't, so we use this as a WORKAROUND for
|
|
||||||
https://bugreports.qt.io/browse/QTBUG-68727
|
|
||||||
|
|
||||||
The above bug got introduced in Qt 5.11.0 and fixed in 5.12.0.
|
|
||||||
"""
|
|
||||||
proxy: Optional[QWidget] = self.focusProxy()
|
|
||||||
|
|
||||||
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)]
|
|
||||||
|
|
||||||
if children:
|
|
||||||
log.webview.debug("Found possibly lost focusProxy: {}"
|
|
||||||
.format(children))
|
|
||||||
|
|
||||||
return children[-1] if children else None
|
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.page().shutdown()
|
self.page().shutdown()
|
||||||
|
|
@ -207,42 +170,29 @@ class WebEnginePage(QWebEnginePage):
|
||||||
"""Override javaScriptConfirm to use qutebrowser prompts."""
|
"""Override javaScriptConfirm to use qutebrowser prompts."""
|
||||||
if self._is_shutting_down:
|
if self._is_shutting_down:
|
||||||
return False
|
return False
|
||||||
escape_msg = qtutils.version_check('5.11', compiled=False)
|
|
||||||
try:
|
try:
|
||||||
return shared.javascript_confirm(url, js_msg,
|
return shared.javascript_confirm(
|
||||||
abort_on=[self.loadStarted,
|
url, js_msg, abort_on=[self.loadStarted, self.shutting_down])
|
||||||
self.shutting_down],
|
|
||||||
escape_msg=escape_msg)
|
|
||||||
except shared.CallSuper:
|
except shared.CallSuper:
|
||||||
return super().javaScriptConfirm(url, js_msg)
|
return super().javaScriptConfirm(url, js_msg)
|
||||||
|
|
||||||
if PYQT_VERSION > 0x050700:
|
def javaScriptPrompt(self, url, js_msg, default):
|
||||||
# WORKAROUND
|
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
||||||
# Can't override javaScriptPrompt with older PyQt versions
|
if self._is_shutting_down:
|
||||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-November/038293.html
|
return (False, "")
|
||||||
def javaScriptPrompt(self, url, js_msg, default):
|
try:
|
||||||
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
return shared.javascript_prompt(
|
||||||
escape_msg = qtutils.version_check('5.11', compiled=False)
|
url, js_msg, default, abort_on=[self.loadStarted, self.shutting_down])
|
||||||
if self._is_shutting_down:
|
except shared.CallSuper:
|
||||||
return (False, "")
|
return super().javaScriptPrompt(url, js_msg, default)
|
||||||
try:
|
|
||||||
return shared.javascript_prompt(url, js_msg, default,
|
|
||||||
abort_on=[self.loadStarted,
|
|
||||||
self.shutting_down],
|
|
||||||
escape_msg=escape_msg)
|
|
||||||
except shared.CallSuper:
|
|
||||||
return super().javaScriptPrompt(url, js_msg, default)
|
|
||||||
|
|
||||||
def javaScriptAlert(self, url, js_msg):
|
def javaScriptAlert(self, url, js_msg):
|
||||||
"""Override javaScriptAlert to use qutebrowser prompts."""
|
"""Override javaScriptAlert to use qutebrowser prompts."""
|
||||||
if self._is_shutting_down:
|
if self._is_shutting_down:
|
||||||
return
|
return
|
||||||
escape_msg = qtutils.version_check('5.11', compiled=False)
|
|
||||||
try:
|
try:
|
||||||
shared.javascript_alert(url, js_msg,
|
shared.javascript_alert(
|
||||||
abort_on=[self.loadStarted,
|
url, js_msg, abort_on=[self.loadStarted, self.shutting_down])
|
||||||
self.shutting_down],
|
|
||||||
escape_msg=escape_msg)
|
|
||||||
except shared.CallSuper:
|
except shared.CallSuper:
|
||||||
super().javaScriptAlert(url, js_msg)
|
super().javaScriptAlert(url, js_msg)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import os.path
|
||||||
from PyQt5.QtNetwork import QNetworkDiskCache
|
from PyQt5.QtNetwork import QNetworkDiskCache
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import utils, qtutils, standarddir
|
from qutebrowser.utils import utils, standarddir
|
||||||
|
|
||||||
|
|
||||||
diskcache = cast('DiskCache', None)
|
diskcache = cast('DiskCache', None)
|
||||||
|
|
@ -52,9 +52,6 @@ class DiskCache(QNetworkDiskCache):
|
||||||
size = config.val.content.cache.size
|
size = config.val.content.cache.size
|
||||||
if size is None:
|
if size is None:
|
||||||
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate
|
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909
|
|
||||||
if not qtutils.version_check('5.9', compiled=False):
|
|
||||||
size = 0 # pragma: no cover
|
|
||||||
self.setMaximumCacheSize(size)
|
self.setMaximumCacheSize(size)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ _CSS_URL_PATTERNS = [re.compile(x) for x in [
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
|
||||||
def _get_css_imports_regex(data):
|
def _get_css_imports(data):
|
||||||
"""Return all assets that are referenced in the given CSS document.
|
"""Return all assets that are referenced in the given CSS document.
|
||||||
|
|
||||||
The returned URLs are relative to the stylesheet's URL.
|
The returned URLs are relative to the stylesheet's URL.
|
||||||
|
|
@ -79,55 +79,6 @@ def _get_css_imports_regex(data):
|
||||||
return urls
|
return urls
|
||||||
|
|
||||||
|
|
||||||
def _get_css_imports_cssutils(data, inline=False):
|
|
||||||
"""Return all assets that are referenced in the given CSS document.
|
|
||||||
|
|
||||||
The returned URLs are relative to the stylesheet's URL.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: The content of the stylesheet to scan as string.
|
|
||||||
inline: True if the argument is an inline HTML style attribute.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
import cssutils
|
|
||||||
except ImportError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# We don't care about invalid CSS data, this will only litter the log
|
|
||||||
# output with CSS errors
|
|
||||||
parser = cssutils.CSSParser(loglevel=100,
|
|
||||||
fetcher=lambda url: (None, ""), validate=False)
|
|
||||||
if not inline:
|
|
||||||
sheet = parser.parseString(data)
|
|
||||||
return list(cssutils.getUrls(sheet))
|
|
||||||
else:
|
|
||||||
urls = []
|
|
||||||
declaration = parser.parseStyle(data)
|
|
||||||
# prop = background, color, margin, ...
|
|
||||||
for prop in declaration:
|
|
||||||
# value = red, 10px, url(foobar), ...
|
|
||||||
for value in prop.propertyValue:
|
|
||||||
if isinstance(value, cssutils.css.URIValue):
|
|
||||||
if value.uri:
|
|
||||||
urls.append(value.uri)
|
|
||||||
return urls
|
|
||||||
|
|
||||||
|
|
||||||
def _get_css_imports(data, inline=False):
|
|
||||||
"""Return all assets that are referenced in the given CSS document.
|
|
||||||
|
|
||||||
The returned URLs are relative to the stylesheet's URL.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: The content of the stylesheet to scan as string.
|
|
||||||
inline: True if the argument is an inline HTML style attribute.
|
|
||||||
"""
|
|
||||||
imports = _get_css_imports_cssutils(data, inline)
|
|
||||||
if imports is None:
|
|
||||||
imports = _get_css_imports_regex(data)
|
|
||||||
return imports
|
|
||||||
|
|
||||||
|
|
||||||
def _check_rel(element):
|
def _check_rel(element):
|
||||||
"""Return true if the element's rel attribute fits our criteria.
|
"""Return true if the element's rel attribute fits our criteria.
|
||||||
|
|
||||||
|
|
@ -328,7 +279,7 @@ class _Downloader:
|
||||||
for element in web_frame.findAllElements('[style]'):
|
for element in web_frame.findAllElements('[style]'):
|
||||||
element = webkitelem.WebKitElement(element, tab=self.tab)
|
element = webkitelem.WebKitElement(element, tab=self.tab)
|
||||||
style = element['style']
|
style = element['style']
|
||||||
for element_url in _get_css_imports(style, inline=True):
|
for element_url in _get_css_imports(style):
|
||||||
self._fetch_url(web_url.resolved(QUrl(element_url)))
|
self._fetch_url(web_url.resolved(QUrl(element_url)))
|
||||||
|
|
||||||
# Shortcut if no assets need to be downloaded, otherwise the file would
|
# Shortcut if no assets need to be downloaded, otherwise the file would
|
||||||
|
|
|
||||||
|
|
@ -155,10 +155,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||||
|
|
||||||
def __init__(self, *, win_id, tab_id, private, parent=None):
|
def __init__(self, *, win_id, tab_id, private, parent=None):
|
||||||
log.init.debug("Initializing NetworkManager")
|
log.init.debug("Initializing NetworkManager")
|
||||||
with log.disable_qt_msghandler():
|
super().__init__(parent)
|
||||||
# WORKAROUND for a hang when a message is printed - See:
|
|
||||||
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-November/035045.html
|
|
||||||
super().__init__(parent)
|
|
||||||
log.init.debug("NetworkManager init done")
|
log.init.debug("NetworkManager init done")
|
||||||
self.adopted_downloads = 0
|
self.adopted_downloads = 0
|
||||||
self._win_id = win_id
|
self._win_id = win_id
|
||||||
|
|
@ -368,13 +365,6 @@ class NetworkManager(QNetworkAccessManager):
|
||||||
# https://www.playstation.com/ for example.
|
# https://www.playstation.com/ for example.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# WORKAROUND for:
|
|
||||||
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-September/034806.html
|
|
||||||
#
|
|
||||||
# By returning False, we provoke a TypeError because of a wrong return
|
|
||||||
# type, which does *not* trigger a segfault but invoke our return handler
|
|
||||||
# immediately.
|
|
||||||
@utils.prevent_exceptions(False)
|
|
||||||
def createRequest(self, op, req, outgoing_data):
|
def createRequest(self, op, req, outgoing_data):
|
||||||
"""Return a new QNetworkReply object.
|
"""Return a new QNetworkReply object.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ def _init_user_agent():
|
||||||
parsed_user_agent = websettings.UserAgent.parse(ua)
|
parsed_user_agent = websettings.UserAgent.parse(ua)
|
||||||
|
|
||||||
|
|
||||||
def init(_args):
|
def init():
|
||||||
"""Initialize the global QWebSettings."""
|
"""Initialize the global QWebSettings."""
|
||||||
cache_path = standarddir.cache()
|
cache_path = standarddir.cache()
|
||||||
data_path = standarddir.data()
|
data_path = standarddir.data()
|
||||||
|
|
|
||||||
|
|
@ -82,9 +82,6 @@ class WebKitPrinting(browsertab.AbstractPrinting):
|
||||||
def check_pdf_support(self):
|
def check_pdf_support(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def check_printer_support(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def check_preview_support(self):
|
def check_preview_support(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -850,9 +847,8 @@ class WebKitTab(browsertab.AbstractTab):
|
||||||
settings = widget.settings()
|
settings = widget.settings()
|
||||||
settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
|
settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
|
||||||
|
|
||||||
def load_url(self, url, *, emit_before_load_started=True):
|
def load_url(self, url):
|
||||||
self._load_url_prepare(
|
self._load_url_prepare(url)
|
||||||
url, emit_before_load_started=emit_before_load_started)
|
|
||||||
self._widget.load(url)
|
self._widget.load(url)
|
||||||
|
|
||||||
def url(self, *, requested=False):
|
def url(self, *, requested=False):
|
||||||
|
|
|
||||||
|
|
@ -365,7 +365,7 @@ class BrowserPage(QWebPage):
|
||||||
abort_on=[self.shutting_down, self.loadStarted])
|
abort_on=[self.shutting_down, self.loadStarted])
|
||||||
|
|
||||||
if question is not None:
|
if question is not None:
|
||||||
self.featurePermissionRequestCanceled.connect( # type: ignore
|
self.featurePermissionRequestCanceled.connect( # type: ignore[attr-defined]
|
||||||
functools.partial(self._on_feature_permission_cancelled,
|
functools.partial(self._on_feature_permission_cancelled,
|
||||||
question, frame, feature))
|
question, frame, feature))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@
|
||||||
"""The main browser widgets."""
|
"""The main browser widgets."""
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, Qt, QUrl
|
from PyQt5.QtCore import pyqtSignal, Qt, QUrl
|
||||||
from PyQt5.QtWidgets import QStyleFactory
|
|
||||||
from PyQt5.QtWebKit import QWebSettings
|
from PyQt5.QtWebKit import QWebSettings
|
||||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||||
|
|
||||||
|
|
@ -62,10 +61,6 @@ class WebView(QWebView):
|
||||||
|
|
||||||
def __init__(self, *, win_id, tab_id, tab, private, parent=None):
|
def __init__(self, *, win_id, tab_id, tab, private, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
if utils.is_mac:
|
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
|
|
||||||
# See https://github.com/qutebrowser/qutebrowser/issues/462
|
|
||||||
self.setStyle(QStyleFactory.create('Fusion'))
|
|
||||||
# FIXME:qtwebengine this is only used to set the zoom factor from
|
# FIXME:qtwebengine this is only used to set the zoom factor from
|
||||||
# the QWebPage - we should get rid of it somehow (signals?)
|
# the QWebPage - we should get rid of it somehow (signals?)
|
||||||
self.tab = tab
|
self.tab = tab
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ class _Highlighter(QSyntaxHighlighter):
|
||||||
self._expression = QRegularExpression(
|
self._expression = QRegularExpression(
|
||||||
pat, QRegularExpression.CaseInsensitiveOption
|
pat, QRegularExpression.CaseInsensitiveOption
|
||||||
)
|
)
|
||||||
|
qtutils.ensure_valid(self._expression)
|
||||||
|
|
||||||
def highlightBlock(self, text):
|
def highlightBlock(self, text):
|
||||||
"""Override highlightBlock for custom highlighting."""
|
"""Override highlightBlock for custom highlighting."""
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
|
||||||
|
|
||||||
from qutebrowser.config import config, stylesheet
|
from qutebrowser.config import config, stylesheet
|
||||||
from qutebrowser.completion import completiondelegate
|
from qutebrowser.completion import completiondelegate
|
||||||
from qutebrowser.utils import utils, usertypes, debug, log
|
from qutebrowser.utils import utils, usertypes, debug, log, qtutils
|
||||||
from qutebrowser.api import cmdutils
|
from qutebrowser.api import cmdutils
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from qutebrowser.mainwindow.statusbar import command
|
from qutebrowser.mainwindow.statusbar import command
|
||||||
|
|
@ -223,8 +223,9 @@ class CompletionView(QTreeView):
|
||||||
return model.last_item() if upwards else model.first_item()
|
return model.last_item() if upwards else model.first_item()
|
||||||
|
|
||||||
# Find height of each CompletionView element
|
# Find height of each CompletionView element
|
||||||
element_height = self.visualRect(idx).height()
|
rect = self.visualRect(idx)
|
||||||
page_length = self.height() // element_height
|
qtutils.ensure_valid(rect)
|
||||||
|
page_length = self.height() // rect.height()
|
||||||
|
|
||||||
# Skip one pageful, except leave one old line visible
|
# Skip one pageful, except leave one old line visible
|
||||||
offset = -(page_length - 1) if upwards else page_length - 1
|
offset = -(page_length - 1) if upwards else page_length - 1
|
||||||
|
|
@ -330,7 +331,8 @@ class CompletionView(QTreeView):
|
||||||
QItemSelectionModel.Rows)
|
QItemSelectionModel.Rows)
|
||||||
|
|
||||||
# if the last item is focused, try to fetch more
|
# if the last item is focused, try to fetch more
|
||||||
if idx.row() == self.model().rowCount(idx.parent()) - 1:
|
next_idx = self.indexBelow(idx)
|
||||||
|
if not self.visualRect(next_idx).isValid():
|
||||||
self.expandAll()
|
self.expandAll()
|
||||||
|
|
||||||
count = self.model().count()
|
count = self.model().count()
|
||||||
|
|
|
||||||
|
|
@ -179,16 +179,10 @@ class CompletionModel(QAbstractItemModel):
|
||||||
pattern: The filter pattern to set.
|
pattern: The filter pattern to set.
|
||||||
"""
|
"""
|
||||||
log.completion.debug("Setting completion pattern '{}'".format(pattern))
|
log.completion.debug("Setting completion pattern '{}'".format(pattern))
|
||||||
# WORKAROUND:
|
self.layoutAboutToBeChanged.emit() # type: ignore[attr-defined]
|
||||||
# layoutChanged is broken in PyQt 5.7.1, so we must use metaObject
|
|
||||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2017-January/038483.html
|
|
||||||
meta = self.metaObject()
|
|
||||||
meta.invokeMethod(self, # type: ignore[misc, call-overload]
|
|
||||||
"layoutAboutToBeChanged")
|
|
||||||
for cat in self._categories:
|
for cat in self._categories:
|
||||||
cat.set_pattern(pattern)
|
cat.set_pattern(pattern)
|
||||||
meta.invokeMethod(self, # type: ignore[misc, call-overload]
|
self.layoutChanged.emit() # type: ignore[attr-defined]
|
||||||
"layoutChanged")
|
|
||||||
|
|
||||||
def first_item(self):
|
def first_item(self):
|
||||||
"""Return the index of the first child (non-category) in the model."""
|
"""Return the index of the first child (non-category) in the model."""
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ class ListCategory(QSortFilterProxyModel):
|
||||||
val = re.escape(val)
|
val = re.escape(val)
|
||||||
val = val.replace(r'\ ', '.*')
|
val = val.replace(r'\ ', '.*')
|
||||||
rx = QRegExp(val, Qt.CaseInsensitive)
|
rx = QRegExp(val, Qt.CaseInsensitive)
|
||||||
|
qtutils.ensure_valid(rx)
|
||||||
self.setFilterRegExp(rx)
|
self.setFilterRegExp(rx)
|
||||||
self.invalidate()
|
self.invalidate()
|
||||||
sortcol = 0
|
sortcol = 0
|
||||||
|
|
|
||||||
|
|
@ -343,10 +343,3 @@ def devtools_focus(tab: apitypes.Tab) -> None:
|
||||||
tab.data.splitter.cycle_focus()
|
tab.data.splitter.cycle_focus()
|
||||||
except apitypes.InspectorError as e:
|
except apitypes.InspectorError as e:
|
||||||
raise cmdutils.CommandError(e)
|
raise cmdutils.CommandError(e)
|
||||||
|
|
||||||
|
|
||||||
@cmdutils.register(deprecated='Use :devtools instead')
|
|
||||||
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
|
|
||||||
def inspector(tab: apitypes.Tab) -> None:
|
|
||||||
"""Toggle the web inspector."""
|
|
||||||
devtools(tab)
|
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@
|
||||||
import copy
|
import copy
|
||||||
import contextlib
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
import typing
|
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Mapping,
|
||||||
from typing import Any, Tuple, MutableMapping
|
MutableMapping, MutableSequence, Optional, Tuple, cast)
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||||
|
|
||||||
|
|
@ -32,15 +32,15 @@ from qutebrowser.utils import utils, log, urlmatch
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
from qutebrowser.keyinput import keyutils
|
from qutebrowser.keyinput import keyutils
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from qutebrowser.config import configcache, configfiles
|
from qutebrowser.config import configcache, configfiles
|
||||||
from qutebrowser.misc import savemanager
|
from qutebrowser.misc import savemanager
|
||||||
|
|
||||||
# An easy way to access the config from other code via config.val.foo
|
# An easy way to access the config from other code via config.val.foo
|
||||||
val = typing.cast('ConfigContainer', None)
|
val = cast('ConfigContainer', None)
|
||||||
instance = typing.cast('Config', None)
|
instance = cast('Config', None)
|
||||||
key_instance = typing.cast('KeyConfig', None)
|
key_instance = cast('KeyConfig', None)
|
||||||
cache = typing.cast('configcache.ConfigCache', None)
|
cache = cast('configcache.ConfigCache', None)
|
||||||
|
|
||||||
# Keeping track of all change filters to validate them later.
|
# Keeping track of all change filters to validate them later.
|
||||||
change_filters = []
|
change_filters = []
|
||||||
|
|
@ -83,7 +83,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
|
||||||
not configdata.is_valid_prefix(self._option)):
|
not configdata.is_valid_prefix(self._option)):
|
||||||
raise configexc.NoOptionError(self._option)
|
raise configexc.NoOptionError(self._option)
|
||||||
|
|
||||||
def check_match(self, option: typing.Optional[str]) -> bool:
|
def check_match(self, option: Optional[str]) -> bool:
|
||||||
"""Check if the given option matches the filter."""
|
"""Check if the given option matches the filter."""
|
||||||
if option is None:
|
if option is None:
|
||||||
# Called directly, not from a config change event.
|
# Called directly, not from a config change event.
|
||||||
|
|
@ -96,7 +96,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __call__(self, func: typing.Callable) -> typing.Callable:
|
def __call__(self, func: Callable) -> Callable:
|
||||||
"""Filter calls to the decorated function.
|
"""Filter calls to the decorated function.
|
||||||
|
|
||||||
Gets called when a function should be decorated.
|
Gets called when a function should be decorated.
|
||||||
|
|
@ -114,7 +114,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
|
||||||
"""
|
"""
|
||||||
if self._function:
|
if self._function:
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def func_wrapper(option: str = None) -> typing.Any:
|
def func_wrapper(option: str = None) -> Any:
|
||||||
"""Call the underlying function."""
|
"""Call the underlying function."""
|
||||||
if self.check_match(option):
|
if self.check_match(option):
|
||||||
return func()
|
return func()
|
||||||
|
|
@ -122,8 +122,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
|
||||||
return func_wrapper
|
return func_wrapper
|
||||||
else:
|
else:
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def meth_wrapper(wrapper_self: typing.Any,
|
def meth_wrapper(wrapper_self: Any, option: str = None) -> Any:
|
||||||
option: str = None) -> typing.Any:
|
|
||||||
"""Call the underlying function."""
|
"""Call the underlying function."""
|
||||||
if self.check_match(option):
|
if self.check_match(option):
|
||||||
return func(wrapper_self)
|
return func(wrapper_self)
|
||||||
|
|
@ -141,7 +140,7 @@ class KeyConfig:
|
||||||
_config: The Config object to be used.
|
_config: The Config object to be used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ReverseBindings = typing.Dict[str, typing.MutableSequence[str]]
|
_ReverseBindings = Dict[str, MutableSequence[str]]
|
||||||
|
|
||||||
def __init__(self, config: 'Config') -> None:
|
def __init__(self, config: 'Config') -> None:
|
||||||
self._config = config
|
self._config = config
|
||||||
|
|
@ -153,10 +152,7 @@ class KeyConfig:
|
||||||
if mode not in configdata.DATA['bindings.default'].default:
|
if mode not in configdata.DATA['bindings.default'].default:
|
||||||
raise configexc.KeybindingError("Invalid mode {}!".format(mode))
|
raise configexc.KeybindingError("Invalid mode {}!".format(mode))
|
||||||
|
|
||||||
def get_bindings_for(
|
def get_bindings_for(self, mode: str) -> Dict[keyutils.KeySequence, str]:
|
||||||
self,
|
|
||||||
mode: str
|
|
||||||
) -> typing.Dict[keyutils.KeySequence, str]:
|
|
||||||
"""Get the combined bindings for the given mode."""
|
"""Get the combined bindings for the given mode."""
|
||||||
bindings = dict(val.bindings.default[mode])
|
bindings = dict(val.bindings.default[mode])
|
||||||
for key, binding in val.bindings.commands[mode].items():
|
for key, binding in val.bindings.commands[mode].items():
|
||||||
|
|
@ -168,7 +164,7 @@ class KeyConfig:
|
||||||
|
|
||||||
def get_reverse_bindings_for(self, mode: str) -> '_ReverseBindings':
|
def get_reverse_bindings_for(self, mode: str) -> '_ReverseBindings':
|
||||||
"""Get a dict of commands to a list of bindings for the mode."""
|
"""Get a dict of commands to a list of bindings for the mode."""
|
||||||
cmd_to_keys = {} # type: KeyConfig._ReverseBindings
|
cmd_to_keys: KeyConfig._ReverseBindings = {}
|
||||||
bindings = self.get_bindings_for(mode)
|
bindings = self.get_bindings_for(mode)
|
||||||
for seq, full_cmd in sorted(bindings.items()):
|
for seq, full_cmd in sorted(bindings.items()):
|
||||||
for cmd in full_cmd.split(';;'):
|
for cmd in full_cmd.split(';;'):
|
||||||
|
|
@ -184,7 +180,7 @@ class KeyConfig:
|
||||||
def get_command(self,
|
def get_command(self,
|
||||||
key: keyutils.KeySequence,
|
key: keyutils.KeySequence,
|
||||||
mode: str,
|
mode: str,
|
||||||
default: bool = False) -> typing.Optional[str]:
|
default: bool = False) -> Optional[str]:
|
||||||
"""Get the command for a given key (or None)."""
|
"""Get the command for a given key (or None)."""
|
||||||
self._validate(key, mode)
|
self._validate(key, mode)
|
||||||
if default:
|
if default:
|
||||||
|
|
@ -277,7 +273,7 @@ class Config(QObject):
|
||||||
yaml_config: 'configfiles.YamlConfig',
|
yaml_config: 'configfiles.YamlConfig',
|
||||||
parent: QObject = None) -> None:
|
parent: QObject = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._mutables = {} # type: MutableMapping[str, Tuple[Any, Any]]
|
self._mutables: MutableMapping[str, Tuple[Any, Any]] = {}
|
||||||
self._yaml = yaml_config
|
self._yaml = yaml_config
|
||||||
self._init_values()
|
self._init_values()
|
||||||
self.yaml_loaded = False
|
self.yaml_loaded = False
|
||||||
|
|
@ -286,11 +282,11 @@ class Config(QObject):
|
||||||
|
|
||||||
def _init_values(self) -> None:
|
def _init_values(self) -> None:
|
||||||
"""Populate the self._values dict."""
|
"""Populate the self._values dict."""
|
||||||
self._values = {} # type: typing.Mapping
|
self._values: Mapping = {}
|
||||||
for name, opt in configdata.DATA.items():
|
for name, opt in configdata.DATA.items():
|
||||||
self._values[name] = configutils.Values(opt)
|
self._values[name] = configutils.Values(opt)
|
||||||
|
|
||||||
def __iter__(self) -> typing.Iterator[configutils.Values]:
|
def __iter__(self) -> Iterator[configutils.Values]:
|
||||||
"""Iterate over configutils.Values items."""
|
"""Iterate over configutils.Values items."""
|
||||||
yield from self._values.values()
|
yield from self._values.values()
|
||||||
|
|
||||||
|
|
@ -391,7 +387,7 @@ class Config(QObject):
|
||||||
|
|
||||||
def get_obj_for_pattern(
|
def get_obj_for_pattern(
|
||||||
self, name: str, *,
|
self, name: str, *,
|
||||||
pattern: typing.Optional[urlmatch.UrlPattern]
|
pattern: Optional[urlmatch.UrlPattern]
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Get the given setting as object (for YAML/config.py).
|
"""Get the given setting as object (for YAML/config.py).
|
||||||
|
|
||||||
|
|
@ -525,7 +521,7 @@ class Config(QObject):
|
||||||
Return:
|
Return:
|
||||||
The changed config part as string.
|
The changed config part as string.
|
||||||
"""
|
"""
|
||||||
lines = [] # type: typing.List[str]
|
lines: List[str] = []
|
||||||
for values in sorted(self, key=lambda v: v.opt.name):
|
for values in sorted(self, key=lambda v: v.opt.name):
|
||||||
lines += values.dump()
|
lines += values.dump()
|
||||||
|
|
||||||
|
|
@ -564,7 +560,7 @@ class ConfigContainer:
|
||||||
pattern=self._pattern)
|
pattern=self._pattern)
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _handle_error(self, action: str, name: str) -> typing.Iterator[None]:
|
def _handle_error(self, action: str, name: str) -> Iterator[None]:
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except configexc.Error as e:
|
except configexc.Error as e:
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
"""Implementation of a basic config cache."""
|
"""Implementation of a basic config cache."""
|
||||||
|
|
||||||
import typing
|
from typing import Any, Dict
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
|
|
||||||
|
|
@ -38,14 +38,14 @@ class ConfigCache:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._cache = {} # type: typing.Dict[str, typing.Any]
|
self._cache: Dict[str, Any] = {}
|
||||||
config.instance.changed.connect(self._on_config_changed)
|
config.instance.changed.connect(self._on_config_changed)
|
||||||
|
|
||||||
def _on_config_changed(self, attr: str) -> None:
|
def _on_config_changed(self, attr: str) -> None:
|
||||||
if attr in self._cache:
|
if attr in self._cache:
|
||||||
del self._cache[attr]
|
del self._cache[attr]
|
||||||
|
|
||||||
def __getitem__(self, attr: str) -> typing.Any:
|
def __getitem__(self, attr: str) -> Any:
|
||||||
try:
|
try:
|
||||||
return self._cache[attr]
|
return self._cache[attr]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@
|
||||||
|
|
||||||
"""Commands related to the configuration."""
|
"""Commands related to the configuration."""
|
||||||
|
|
||||||
import typing
|
|
||||||
import os.path
|
import os.path
|
||||||
import contextlib
|
import contextlib
|
||||||
|
from typing import TYPE_CHECKING, Iterator, List, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl
|
from PyQt5.QtCore import QUrl
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ from qutebrowser.config import configtypes, configexc, configfiles, configdata
|
||||||
from qutebrowser.misc import editor
|
from qutebrowser.misc import editor
|
||||||
from qutebrowser.keyinput import keyutils
|
from qutebrowser.keyinput import keyutils
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from qutebrowser.config.config import Config, KeyConfig
|
from qutebrowser.config.config import Config, KeyConfig
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,17 +47,14 @@ class ConfigCommands:
|
||||||
self._keyconfig = keyconfig
|
self._keyconfig = keyconfig
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _handle_config_error(self) -> typing.Iterator[None]:
|
def _handle_config_error(self) -> Iterator[None]:
|
||||||
"""Catch errors in set_command and raise CommandError."""
|
"""Catch errors in set_command and raise CommandError."""
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except configexc.Error as e:
|
except configexc.Error as e:
|
||||||
raise cmdutils.CommandError(str(e))
|
raise cmdutils.CommandError(str(e))
|
||||||
|
|
||||||
def _parse_pattern(
|
def _parse_pattern(self, pattern: Optional[str]) -> Optional[urlmatch.UrlPattern]:
|
||||||
self,
|
|
||||||
pattern: typing.Optional[str]
|
|
||||||
) -> typing.Optional[urlmatch.UrlPattern]:
|
|
||||||
"""Parse a pattern string argument to a pattern."""
|
"""Parse a pattern string argument to a pattern."""
|
||||||
if pattern is None:
|
if pattern is None:
|
||||||
return None
|
return None
|
||||||
|
|
@ -75,8 +72,7 @@ class ConfigCommands:
|
||||||
except keyutils.KeyParseError as e:
|
except keyutils.KeyParseError as e:
|
||||||
raise cmdutils.CommandError(str(e))
|
raise cmdutils.CommandError(str(e))
|
||||||
|
|
||||||
def _print_value(self, option: str,
|
def _print_value(self, option: str, pattern: Optional[urlmatch.UrlPattern]) -> None:
|
||||||
pattern: typing.Optional[urlmatch.UrlPattern]) -> None:
|
|
||||||
"""Print the value of the given option."""
|
"""Print the value of the given option."""
|
||||||
with self._handle_config_error():
|
with self._handle_config_error():
|
||||||
value = self._config.get_str(option, pattern=pattern)
|
value = self._config.get_str(option, pattern=pattern)
|
||||||
|
|
@ -263,17 +259,9 @@ class ConfigCommands:
|
||||||
|
|
||||||
@cmdutils.register(instance='config-commands')
|
@cmdutils.register(instance='config-commands')
|
||||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||||
def config_diff(self, win_id: int, old: bool = False) -> None:
|
def config_diff(self, win_id: int) -> None:
|
||||||
"""Show all customized options.
|
"""Show all customized options."""
|
||||||
|
|
||||||
Args:
|
|
||||||
old: Show difference for the pre-v1.0 files
|
|
||||||
(qutebrowser.conf/keys.conf).
|
|
||||||
"""
|
|
||||||
url = QUrl('qute://configdiff')
|
url = QUrl('qute://configdiff')
|
||||||
if old:
|
|
||||||
url.setPath('/old')
|
|
||||||
|
|
||||||
tabbed_browser = objreg.get('tabbed-browser',
|
tabbed_browser = objreg.get('tabbed-browser',
|
||||||
scope='window', window=win_id)
|
scope='window', window=win_id)
|
||||||
tabbed_browser.load_url(url, newtab=False)
|
tabbed_browser.load_url(url, newtab=False)
|
||||||
|
|
@ -468,7 +456,7 @@ class ConfigCommands:
|
||||||
raise cmdutils.CommandError("{} already exists - use --force to "
|
raise cmdutils.CommandError("{} already exists - use --force to "
|
||||||
"overwrite!".format(filename))
|
"overwrite!".format(filename))
|
||||||
|
|
||||||
options = [] # type: typing.List
|
options: List = []
|
||||||
if defaults:
|
if defaults:
|
||||||
options = [(None, opt, opt.default)
|
options = [(None, opt, opt.default)
|
||||||
for _name, opt in sorted(configdata.DATA.items())]
|
for _name, opt in sorted(configdata.DATA.items())]
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ Module attributes:
|
||||||
DATA: A dict of Option objects after init() has been called.
|
DATA: A dict of Option objects after init() has been called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import typing
|
from typing import (Any, Dict, Iterable, List, Mapping, MutableMapping, Optional,
|
||||||
from typing import Optional
|
Sequence, Tuple, Union, cast)
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
@ -33,10 +33,10 @@ from qutebrowser.config import configtypes
|
||||||
from qutebrowser.utils import usertypes, qtutils, utils
|
from qutebrowser.utils import usertypes, qtutils, utils
|
||||||
from qutebrowser.misc import debugcachestats
|
from qutebrowser.misc import debugcachestats
|
||||||
|
|
||||||
DATA = typing.cast(typing.Mapping[str, 'Option'], None)
|
DATA = cast(Mapping[str, 'Option'], None)
|
||||||
MIGRATIONS = typing.cast('Migrations', None)
|
MIGRATIONS = cast('Migrations', None)
|
||||||
|
|
||||||
_BackendDict = typing.Mapping[str, typing.Union[str, bool]]
|
_BackendDict = Mapping[str, Union[str, bool]]
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
|
|
@ -47,15 +47,15 @@ class Option:
|
||||||
Note that this is just an option which exists, with no value associated.
|
Note that this is just an option which exists, with no value associated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = attr.ib() # type: str
|
name: str = attr.ib()
|
||||||
typ = attr.ib() # type: configtypes.BaseType
|
typ: configtypes.BaseType = attr.ib()
|
||||||
default = attr.ib() # type: typing.Any
|
default: Any = attr.ib()
|
||||||
backends = attr.ib() # type: typing.Iterable[usertypes.Backend]
|
backends: Iterable[usertypes.Backend] = attr.ib()
|
||||||
raw_backends = attr.ib() # type: Optional[typing.Mapping[str, bool]]
|
raw_backends: Optional[Mapping[str, bool]] = attr.ib()
|
||||||
description = attr.ib() # type: str
|
description: str = attr.ib()
|
||||||
supports_pattern = attr.ib(default=False) # type: bool
|
supports_pattern: bool = attr.ib(default=False)
|
||||||
restart = attr.ib(default=False) # type: bool
|
restart: bool = attr.ib(default=False)
|
||||||
no_autoconfig = attr.ib(default=False) # type: bool
|
no_autoconfig: bool = attr.ib(default=False)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
|
|
@ -68,13 +68,11 @@ class Migrations:
|
||||||
deleted: A list of option names which have been removed.
|
deleted: A list of option names which have been removed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
renamed = attr.ib(
|
renamed: Dict[str, str] = attr.ib(default=attr.Factory(dict))
|
||||||
default=attr.Factory(dict)) # type: typing.Dict[str, str]
|
deleted: List[str] = attr.ib(default=attr.Factory(list))
|
||||||
deleted = attr.ib(
|
|
||||||
default=attr.Factory(list)) # type: typing.List[str]
|
|
||||||
|
|
||||||
|
|
||||||
def _raise_invalid_node(name: str, what: str, node: typing.Any) -> None:
|
def _raise_invalid_node(name: str, what: str, node: Any) -> None:
|
||||||
"""Raise an exception for an invalid configdata YAML node.
|
"""Raise an exception for an invalid configdata YAML node.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -88,14 +86,14 @@ def _raise_invalid_node(name: str, what: str, node: typing.Any) -> None:
|
||||||
|
|
||||||
def _parse_yaml_type(
|
def _parse_yaml_type(
|
||||||
name: str,
|
name: str,
|
||||||
node: typing.Union[str, typing.Mapping[str, typing.Any]],
|
node: Union[str, Mapping[str, Any]],
|
||||||
) -> configtypes.BaseType:
|
) -> configtypes.BaseType:
|
||||||
if isinstance(node, str):
|
if isinstance(node, str):
|
||||||
# e.g:
|
# e.g:
|
||||||
# > type: Bool
|
# > type: Bool
|
||||||
# -> create the type object without any arguments
|
# -> create the type object without any arguments
|
||||||
type_name = node
|
type_name = node
|
||||||
kwargs = {} # type: typing.MutableMapping[str, typing.Any]
|
kwargs: MutableMapping[str, Any] = {}
|
||||||
elif isinstance(node, dict):
|
elif isinstance(node, dict):
|
||||||
# e.g:
|
# e.g:
|
||||||
# > type:
|
# > type:
|
||||||
|
|
@ -136,14 +134,14 @@ def _parse_yaml_type(
|
||||||
def _parse_yaml_backends_dict(
|
def _parse_yaml_backends_dict(
|
||||||
name: str,
|
name: str,
|
||||||
node: _BackendDict,
|
node: _BackendDict,
|
||||||
) -> typing.Sequence[usertypes.Backend]:
|
) -> Sequence[usertypes.Backend]:
|
||||||
"""Parse a dict definition for backends.
|
"""Parse a dict definition for backends.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
backends:
|
backends:
|
||||||
QtWebKit: true
|
QtWebKit: true
|
||||||
QtWebEngine: Qt 5.9
|
QtWebEngine: Qt 5.15
|
||||||
"""
|
"""
|
||||||
str_to_backend = {
|
str_to_backend = {
|
||||||
'QtWebKit': usertypes.Backend.QtWebKit,
|
'QtWebKit': usertypes.Backend.QtWebKit,
|
||||||
|
|
@ -160,14 +158,9 @@ def _parse_yaml_backends_dict(
|
||||||
conditionals = {
|
conditionals = {
|
||||||
True: True,
|
True: True,
|
||||||
False: False,
|
False: False,
|
||||||
'Qt 5.8': qtutils.version_check('5.8'),
|
|
||||||
'Qt 5.9': qtutils.version_check('5.9'),
|
|
||||||
'Qt 5.9.2': qtutils.version_check('5.9.2'),
|
|
||||||
'Qt 5.10': qtutils.version_check('5.10'),
|
|
||||||
'Qt 5.11': qtutils.version_check('5.11'),
|
|
||||||
'Qt 5.12': qtutils.version_check('5.12'),
|
|
||||||
'Qt 5.13': qtutils.version_check('5.13'),
|
'Qt 5.13': qtutils.version_check('5.13'),
|
||||||
'Qt 5.14': qtutils.version_check('5.14'),
|
'Qt 5.14': qtutils.version_check('5.14'),
|
||||||
|
'Qt 5.15': qtutils.version_check('5.15'),
|
||||||
}
|
}
|
||||||
for key in sorted(node.keys()):
|
for key in sorted(node.keys()):
|
||||||
if conditionals[node[key]]:
|
if conditionals[node[key]]:
|
||||||
|
|
@ -178,8 +171,8 @@ def _parse_yaml_backends_dict(
|
||||||
|
|
||||||
def _parse_yaml_backends(
|
def _parse_yaml_backends(
|
||||||
name: str,
|
name: str,
|
||||||
node: typing.Union[None, str, _BackendDict],
|
node: Union[None, str, _BackendDict],
|
||||||
) -> typing.Sequence[usertypes.Backend]:
|
) -> Sequence[usertypes.Backend]:
|
||||||
"""Parse a backend node in the yaml.
|
"""Parse a backend node in the yaml.
|
||||||
|
|
||||||
It can have one of those four forms:
|
It can have one of those four forms:
|
||||||
|
|
@ -188,7 +181,7 @@ def _parse_yaml_backends(
|
||||||
- backend: QtWebEngine -> setting only available with QtWebEngine
|
- backend: QtWebEngine -> setting only available with QtWebEngine
|
||||||
- backend:
|
- backend:
|
||||||
QtWebKit: true
|
QtWebKit: true
|
||||||
QtWebEngine: Qt 5.9
|
QtWebEngine: Qt 5.15
|
||||||
-> setting available based on the given conditionals.
|
-> setting available based on the given conditionals.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
|
|
@ -208,7 +201,7 @@ def _parse_yaml_backends(
|
||||||
|
|
||||||
def _read_yaml(
|
def _read_yaml(
|
||||||
yaml_data: str,
|
yaml_data: str,
|
||||||
) -> typing.Tuple[typing.Mapping[str, Option], Migrations]:
|
) -> Tuple[Mapping[str, Option], Migrations]:
|
||||||
"""Read config data from a YAML file.
|
"""Read config data from a YAML file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
|
||||||
|
|
@ -293,16 +293,11 @@ auto_save.session:
|
||||||
content.autoplay:
|
content.autoplay:
|
||||||
default: true
|
default: true
|
||||||
type: Bool
|
type: Bool
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebEngine: Qt 5.10
|
|
||||||
QtWebKit: false
|
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
desc: >-
|
desc: >-
|
||||||
Automatically start playing `<video>` elements.
|
Automatically start playing `<video>` elements.
|
||||||
|
|
||||||
Note: On Qt < 5.11, this option needs a restart and does not support URL
|
|
||||||
patterns.
|
|
||||||
|
|
||||||
content.cache.size:
|
content.cache.size:
|
||||||
default: null
|
default: null
|
||||||
type:
|
type:
|
||||||
|
|
@ -359,9 +354,6 @@ content.cache.appcache:
|
||||||
|
|
||||||
content.cookies.accept:
|
content.cookies.accept:
|
||||||
default: all
|
default: all
|
||||||
backend:
|
|
||||||
QtWebKit: true
|
|
||||||
QtWebEngine: Qt 5.11
|
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
type:
|
type:
|
||||||
name: String
|
name: String
|
||||||
|
|
@ -390,10 +382,7 @@ content.cookies.accept:
|
||||||
content.cookies.store:
|
content.cookies.store:
|
||||||
default: true
|
default: true
|
||||||
type: Bool
|
type: Bool
|
||||||
desc: >-
|
desc: Store cookies.
|
||||||
Store cookies.
|
|
||||||
|
|
||||||
Note this option needs a restart with QtWebEngine on Qt < 5.9.
|
|
||||||
|
|
||||||
content.default_encoding:
|
content.default_encoding:
|
||||||
type: String
|
type: String
|
||||||
|
|
@ -417,9 +406,7 @@ content.unknown_url_scheme_policy:
|
||||||
- allow-all: "Allows all navigation requests to URLs with unknown
|
- allow-all: "Allows all navigation requests to URLs with unknown
|
||||||
schemes."
|
schemes."
|
||||||
default: allow-from-user-interaction
|
default: allow-from-user-interaction
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebEngine: Qt 5.11
|
|
||||||
QtWebKit: false
|
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
desc: >-
|
desc: >-
|
||||||
How navigation requests to URLs with unknown schemes are handled.
|
How navigation requests to URLs with unknown schemes are handled.
|
||||||
|
|
@ -448,11 +435,7 @@ content.desktop_capture:
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
default: ask
|
default: ask
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
desc: >-
|
desc: Allow websites to share screen content.
|
||||||
Allow websites to share screen content.
|
|
||||||
|
|
||||||
On Qt < 5.10, a dialog box is always displayed, even if this is set to
|
|
||||||
"true".
|
|
||||||
|
|
||||||
content.developer_extras:
|
content.developer_extras:
|
||||||
deleted: true
|
deleted: true
|
||||||
|
|
@ -460,9 +443,7 @@ content.developer_extras:
|
||||||
content.dns_prefetch:
|
content.dns_prefetch:
|
||||||
default: true
|
default: true
|
||||||
type: Bool
|
type: Bool
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebKit: true
|
|
||||||
QtWebEngine: Qt 5.12
|
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
desc: Try to pre-fetch DNS entries to speed up browsing.
|
desc: Try to pre-fetch DNS entries to speed up browsing.
|
||||||
|
|
||||||
|
|
@ -495,9 +476,7 @@ content.mouse_lock:
|
||||||
default: ask
|
default: ask
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebKit: false
|
|
||||||
QtWebEngine: Qt 5.8
|
|
||||||
desc: Allow websites to lock your mouse pointer.
|
desc: Allow websites to lock your mouse pointer.
|
||||||
|
|
||||||
content.headers.accept_language:
|
content.headers.accept_language:
|
||||||
|
|
@ -851,9 +830,7 @@ content.persistent_storage:
|
||||||
default: ask
|
default: ask
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebKit: false
|
|
||||||
QtWebEngine: Qt 5.11
|
|
||||||
desc: Allow websites to request persistent storage quota via
|
desc: Allow websites to request persistent storage quota via
|
||||||
`navigator.webkitPersistentStorage.requestQuota`.
|
`navigator.webkitPersistentStorage.requestQuota`.
|
||||||
|
|
||||||
|
|
@ -866,9 +843,7 @@ content.plugins:
|
||||||
content.print_element_backgrounds:
|
content.print_element_backgrounds:
|
||||||
type: Bool
|
type: Bool
|
||||||
default: true
|
default: true
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebKit: true
|
|
||||||
QtWebEngine: Qt 5.8
|
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
desc: >-
|
desc: >-
|
||||||
Draw the background color and images also when the page is printed.
|
Draw the background color and images also when the page is printed.
|
||||||
|
|
@ -901,9 +876,7 @@ content.register_protocol_handler:
|
||||||
default: ask
|
default: ask
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebKit: false
|
|
||||||
QtWebEngine: Qt 5.11
|
|
||||||
desc: Allow websites to register protocol handlers via
|
desc: Allow websites to register protocol handlers via
|
||||||
`navigator.registerProtocolHandler`.
|
`navigator.registerProtocolHandler`.
|
||||||
|
|
||||||
|
|
@ -943,15 +916,11 @@ content.webrtc_ip_handling_policy:
|
||||||
servers unless the proxy server supports UDP. This doesn't expose
|
servers unless the proxy server supports UDP. This doesn't expose
|
||||||
any local addresses either.
|
any local addresses either.
|
||||||
default: all-interfaces
|
default: all-interfaces
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebKit: false
|
|
||||||
QtWebEngine: Qt 5.9.2
|
|
||||||
restart: true
|
restart: true
|
||||||
desc: >-
|
desc: >-
|
||||||
Which interfaces to expose via WebRTC.
|
Which interfaces to expose via WebRTC.
|
||||||
|
|
||||||
On Qt 5.10, this option doesn't work because of a Qt bug.
|
|
||||||
|
|
||||||
content.xss_auditing:
|
content.xss_auditing:
|
||||||
type: Bool
|
type: Bool
|
||||||
default: false
|
default: false
|
||||||
|
|
@ -1573,7 +1542,7 @@ scrolling.bar:
|
||||||
- never: Never show the scrollbar.
|
- never: Never show the scrollbar.
|
||||||
- when-searching: Show the scrollbar when searching for text in the
|
- when-searching: Show the scrollbar when searching for text in the
|
||||||
webpage. With the QtWebKit backend, this is equal to `never`.
|
webpage. With the QtWebKit backend, this is equal to `never`.
|
||||||
- overlay: Show an overlay scrollbar. With Qt < 5.11 or on macOS, this is
|
- overlay: Show an overlay scrollbar. On macOS, this is
|
||||||
unavailable and equal to `when-searching`; with the QtWebKit
|
unavailable and equal to `when-searching`; with the QtWebKit
|
||||||
backend, this is equal to `never`. Enabling/disabling overlay
|
backend, this is equal to `never`. Enabling/disabling overlay
|
||||||
scrollbars requires a restart.
|
scrollbars requires a restart.
|
||||||
|
|
@ -1647,9 +1616,7 @@ spellcheck.languages:
|
||||||
|
|
||||||
You can check for available languages and install dictionaries using
|
You can check for available languages and install dictionaries using
|
||||||
scripts/dictcli.py. Run the script with -h/--help for instructions.
|
scripts/dictcli.py. Run the script with -h/--help for instructions.
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebKit: false
|
|
||||||
QtWebEngine: Qt 5.8
|
|
||||||
|
|
||||||
## statusbar
|
## statusbar
|
||||||
|
|
||||||
|
|
@ -2081,6 +2048,7 @@ url.searchengines:
|
||||||
expands to `slash%2Fand%26amp`).
|
expands to `slash%2Fand%26amp`).
|
||||||
* `{unquoted}` quotes nothing (for `slash/and&` this placeholder
|
* `{unquoted}` quotes nothing (for `slash/and&` this placeholder
|
||||||
expands to `slash/and&`).
|
expands to `slash/and&`).
|
||||||
|
* `{0}` means the same as `{}`, but can be used multiple times.
|
||||||
|
|
||||||
The search engine named `DEFAULT` is used when `url.auto_search` is turned
|
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
|
on and something else than a URL was entered to be opened. Other search
|
||||||
|
|
@ -2142,6 +2110,19 @@ window.title_format:
|
||||||
Format to use for the window title. The same placeholders like for
|
Format to use for the window title. The same placeholders like for
|
||||||
`tabs.title.format` are defined.
|
`tabs.title.format` are defined.
|
||||||
|
|
||||||
|
window.transparent:
|
||||||
|
type: Bool
|
||||||
|
default: false
|
||||||
|
desc: |
|
||||||
|
Set the main window background to transparent.
|
||||||
|
|
||||||
|
This allows having a transparent tab- or statusbar (might require a compositor such
|
||||||
|
as picom). However, it breaks some functionality such as dmenu embedding via its
|
||||||
|
`-w` option. On some systems, it was additionally reported that main window
|
||||||
|
transparency negatively affects performance.
|
||||||
|
|
||||||
|
Note this setting only affects windows opened after setting it.
|
||||||
|
|
||||||
## zoom
|
## zoom
|
||||||
|
|
||||||
zoom.default:
|
zoom.default:
|
||||||
|
|
@ -2747,9 +2728,7 @@ colors.webpage.darkmode.enabled:
|
||||||
- "With selective inversion of everything": Combines the two variants
|
- "With selective inversion of everything": Combines the two variants
|
||||||
above.
|
above.
|
||||||
restart: true
|
restart: true
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebEngine: Qt 5.10
|
|
||||||
QtWebKit: false
|
|
||||||
|
|
||||||
colors.webpage.darkmode.algorithm:
|
colors.webpage.darkmode.algorithm:
|
||||||
default: lightness-cielab
|
default: lightness-cielab
|
||||||
|
|
@ -2771,9 +2750,7 @@ colors.webpage.darkmode.algorithm:
|
||||||
# kInvertBrightness without gamma correction, and only available for
|
# kInvertBrightness without gamma correction, and only available for
|
||||||
# Chromium's automated tests
|
# Chromium's automated tests
|
||||||
restart: true
|
restart: true
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebEngine: Qt 5.10
|
|
||||||
QtWebKit: false
|
|
||||||
|
|
||||||
colors.webpage.darkmode.contrast:
|
colors.webpage.darkmode.contrast:
|
||||||
default: 0.0
|
default: 0.0
|
||||||
|
|
@ -2787,9 +2764,7 @@ colors.webpage.darkmode.contrast:
|
||||||
This only has an effect when `colors.webpage.darkmode.algorithm` is set to
|
This only has an effect when `colors.webpage.darkmode.algorithm` is set to
|
||||||
`lightness-hsl` or `brightness-rgb`.
|
`lightness-hsl` or `brightness-rgb`.
|
||||||
restart: true
|
restart: true
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebEngine: Qt 5.10
|
|
||||||
QtWebKit: false
|
|
||||||
|
|
||||||
colors.webpage.darkmode.policy.images:
|
colors.webpage.darkmode.policy.images:
|
||||||
default: smart
|
default: smart
|
||||||
|
|
@ -2799,19 +2774,16 @@ colors.webpage.darkmode.policy.images:
|
||||||
- always: Apply dark mode filter to all images.
|
- always: Apply dark mode filter to all images.
|
||||||
- never: Never apply dark mode filter to any images.
|
- never: Never apply dark mode filter to any images.
|
||||||
- smart: "Apply dark mode based on image content. Not available with Qt
|
- smart: "Apply dark mode based on image content. Not available with Qt
|
||||||
5.10 / 5.15.0."
|
5.15.0."
|
||||||
desc: >-
|
desc: >-
|
||||||
Which images to apply dark mode to.
|
Which images to apply dark mode to.
|
||||||
|
|
||||||
With QtWebEngine 5.15.0, this setting can cause frequent renderer process
|
With QtWebEngine 5.15.0, this setting can cause frequent renderer process
|
||||||
crashes due to a
|
crashes due to a
|
||||||
https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug
|
https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug
|
||||||
in Qt]. With QtWebEngine 5.10, this is not available at all. In those
|
in Qt].
|
||||||
cases, the 'smart' setting is ignored and treated like 'never'.
|
|
||||||
restart: true
|
restart: true
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebEngine: Qt 5.10
|
|
||||||
QtWebKit: false
|
|
||||||
|
|
||||||
colors.webpage.darkmode.policy.page:
|
colors.webpage.darkmode.policy.page:
|
||||||
default: smart
|
default: smart
|
||||||
|
|
@ -2872,9 +2844,7 @@ colors.webpage.darkmode.grayscale.all:
|
||||||
This only has an effect when `colors.webpage.darkmode.algorithm` is set to
|
This only has an effect when `colors.webpage.darkmode.algorithm` is set to
|
||||||
`lightness-hsl` or `brightness-rgb`.
|
`lightness-hsl` or `brightness-rgb`.
|
||||||
restart: true
|
restart: true
|
||||||
backend:
|
backend: QtWebEngine
|
||||||
QtWebEngine: Qt 5.10
|
|
||||||
QtWebKit: false
|
|
||||||
|
|
||||||
colors.webpage.darkmode.grayscale.images:
|
colors.webpage.darkmode.grayscale.images:
|
||||||
default: 0.0
|
default: 0.0
|
||||||
|
|
|
||||||
|
|
@ -1,761 +0,0 @@
|
||||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
||||||
|
|
||||||
# Copyright 2017-2020 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
||||||
#
|
|
||||||
# This file is part of qutebrowser.
|
|
||||||
#
|
|
||||||
# qutebrowser is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# qutebrowser is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
"""Code to show a diff of the legacy config format."""
|
|
||||||
|
|
||||||
import typing
|
|
||||||
import difflib
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
import pygments
|
|
||||||
import pygments.lexers
|
|
||||||
import pygments.formatters
|
|
||||||
|
|
||||||
from qutebrowser.utils import standarddir
|
|
||||||
|
|
||||||
|
|
||||||
OLD_CONF = """
|
|
||||||
[general]
|
|
||||||
ignore-case = smart
|
|
||||||
startpage = https://start.duckduckgo.com
|
|
||||||
yank-ignored-url-parameters = ref,utm_source,utm_medium,utm_campaign,utm_term,utm_content
|
|
||||||
default-open-dispatcher =
|
|
||||||
default-page = ${startpage}
|
|
||||||
auto-search = naive
|
|
||||||
auto-save-config = true
|
|
||||||
auto-save-interval = 15000
|
|
||||||
editor = gvim -f "{}"
|
|
||||||
editor-encoding = utf-8
|
|
||||||
private-browsing = false
|
|
||||||
developer-extras = false
|
|
||||||
print-element-backgrounds = true
|
|
||||||
xss-auditing = false
|
|
||||||
default-encoding = iso-8859-1
|
|
||||||
new-instance-open-target = tab
|
|
||||||
new-instance-open-target.window = last-focused
|
|
||||||
log-javascript-console = debug
|
|
||||||
save-session = false
|
|
||||||
session-default-name =
|
|
||||||
url-incdec-segments = path,query
|
|
||||||
[ui]
|
|
||||||
history-session-interval = 30
|
|
||||||
zoom-levels = 25%,33%,50%,67%,75%,90%,100%,110%,125%,150%,175%,200%,250%,300%,400%,500%
|
|
||||||
default-zoom = 100%
|
|
||||||
downloads-position = top
|
|
||||||
status-position = bottom
|
|
||||||
message-timeout = 2000
|
|
||||||
message-unfocused = false
|
|
||||||
confirm-quit = never
|
|
||||||
zoom-text-only = false
|
|
||||||
frame-flattening = false
|
|
||||||
user-stylesheet =
|
|
||||||
hide-scrollbar = true
|
|
||||||
smooth-scrolling = false
|
|
||||||
remove-finished-downloads = -1
|
|
||||||
hide-statusbar = false
|
|
||||||
statusbar-padding = 1,1,0,0
|
|
||||||
window-title-format = {perc}{title}{title_sep}qutebrowser
|
|
||||||
modal-js-dialog = false
|
|
||||||
hide-wayland-decoration = false
|
|
||||||
keyhint-blacklist =
|
|
||||||
keyhint-delay = 500
|
|
||||||
prompt-radius = 8
|
|
||||||
prompt-filebrowser = true
|
|
||||||
[network]
|
|
||||||
do-not-track = true
|
|
||||||
accept-language = en-US,en
|
|
||||||
referer-header = same-domain
|
|
||||||
user-agent =
|
|
||||||
proxy = system
|
|
||||||
proxy-dns-requests = true
|
|
||||||
ssl-strict = ask
|
|
||||||
dns-prefetch = true
|
|
||||||
custom-headers =
|
|
||||||
netrc-file =
|
|
||||||
[completion]
|
|
||||||
show = always
|
|
||||||
download-path-suggestion = path
|
|
||||||
timestamp-format = %Y-%m-%d
|
|
||||||
height = 50%
|
|
||||||
cmd-history-max-items = 100
|
|
||||||
web-history-max-items = -1
|
|
||||||
quick-complete = true
|
|
||||||
shrink = false
|
|
||||||
scrollbar-width = 12
|
|
||||||
scrollbar-padding = 2
|
|
||||||
[input]
|
|
||||||
timeout = 500
|
|
||||||
partial-timeout = 5000
|
|
||||||
insert-mode-on-plugins = false
|
|
||||||
auto-leave-insert-mode = true
|
|
||||||
auto-insert-mode = false
|
|
||||||
forward-unbound-keys = auto
|
|
||||||
spatial-navigation = false
|
|
||||||
links-included-in-focus-chain = true
|
|
||||||
rocker-gestures = false
|
|
||||||
mouse-zoom-divider = 512
|
|
||||||
[tabs]
|
|
||||||
background-tabs = false
|
|
||||||
select-on-remove = next
|
|
||||||
new-tab-position = next
|
|
||||||
new-tab-position-explicit = last
|
|
||||||
last-close = ignore
|
|
||||||
show = always
|
|
||||||
show-switching-delay = 800
|
|
||||||
wrap = true
|
|
||||||
movable = true
|
|
||||||
close-mouse-button = middle
|
|
||||||
position = top
|
|
||||||
show-favicons = true
|
|
||||||
favicon-scale = 1.0
|
|
||||||
width = 20%
|
|
||||||
pinned-width = 43
|
|
||||||
indicator-width = 3
|
|
||||||
tabs-are-windows = false
|
|
||||||
title-format = {index}: {title}
|
|
||||||
title-format-pinned = {index}
|
|
||||||
title-alignment = left
|
|
||||||
mousewheel-tab-switching = true
|
|
||||||
padding = 0,0,5,5
|
|
||||||
indicator-padding = 2,2,0,4
|
|
||||||
[storage]
|
|
||||||
download-directory =
|
|
||||||
prompt-download-directory = true
|
|
||||||
remember-download-directory = true
|
|
||||||
maximum-pages-in-cache = 0
|
|
||||||
offline-web-application-cache = true
|
|
||||||
local-storage = true
|
|
||||||
cache-size =
|
|
||||||
[content]
|
|
||||||
allow-images = true
|
|
||||||
allow-javascript = true
|
|
||||||
allow-plugins = false
|
|
||||||
webgl = true
|
|
||||||
hyperlink-auditing = false
|
|
||||||
geolocation = ask
|
|
||||||
notifications = ask
|
|
||||||
media-capture = ask
|
|
||||||
javascript-can-open-windows-automatically = false
|
|
||||||
javascript-can-close-windows = false
|
|
||||||
javascript-can-access-clipboard = false
|
|
||||||
ignore-javascript-prompt = false
|
|
||||||
ignore-javascript-alert = false
|
|
||||||
local-content-can-access-remote-urls = false
|
|
||||||
local-content-can-access-file-urls = true
|
|
||||||
cookies-accept = no-3rdparty
|
|
||||||
cookies-store = true
|
|
||||||
host-block-lists = https://www.malwaredomainlist.com/hostslist/hosts.txt,http://someonewhocares.org/hosts/hosts,http://winhelp2002.mvps.org/hosts.zip,http://malwaredomains.lehigh.edu/files/justdomains.zip,https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext
|
|
||||||
host-blocking-enabled = true
|
|
||||||
host-blocking-whitelist = piwik.org
|
|
||||||
enable-pdfjs = false
|
|
||||||
[hints]
|
|
||||||
border = 1px solid #E3BE23
|
|
||||||
mode = letter
|
|
||||||
chars = asdfghjkl
|
|
||||||
min-chars = 1
|
|
||||||
scatter = true
|
|
||||||
uppercase = false
|
|
||||||
dictionary = /usr/share/dict/words
|
|
||||||
auto-follow = unique-match
|
|
||||||
auto-follow-timeout = 0
|
|
||||||
next-regexes = \\bnext\\b,\\bmore\\b,\\bnewer\\b,\\b[>\u2192\u226b]\\b,\\b(>>|\xbb)\\b,\\bcontinue\\b
|
|
||||||
prev-regexes = \\bprev(ious)?\\b,\\bback\\b,\\bolder\\b,\\b[<\u2190\u226a]\\b,\\b(<<|\xab)\\b
|
|
||||||
find-implementation = python
|
|
||||||
hide-unmatched-rapid-hints = true
|
|
||||||
[searchengines]
|
|
||||||
DEFAULT = https://duckduckgo.com/?q={}
|
|
||||||
[aliases]
|
|
||||||
[colors]
|
|
||||||
completion.fg = white
|
|
||||||
completion.bg = #333333
|
|
||||||
completion.alternate-bg = #444444
|
|
||||||
completion.category.fg = white
|
|
||||||
completion.category.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #888888, stop:1 #505050)
|
|
||||||
completion.category.border.top = black
|
|
||||||
completion.category.border.bottom = ${completion.category.border.top}
|
|
||||||
completion.item.selected.fg = black
|
|
||||||
completion.item.selected.bg = #e8c000
|
|
||||||
completion.item.selected.border.top = #bbbb00
|
|
||||||
completion.item.selected.border.bottom = ${completion.item.selected.border.top}
|
|
||||||
completion.match.fg = #ff4444
|
|
||||||
completion.scrollbar.fg = ${completion.fg}
|
|
||||||
completion.scrollbar.bg = ${completion.bg}
|
|
||||||
statusbar.fg = white
|
|
||||||
statusbar.bg = black
|
|
||||||
statusbar.fg.private = ${statusbar.fg}
|
|
||||||
statusbar.bg.private = #666666
|
|
||||||
statusbar.fg.insert = ${statusbar.fg}
|
|
||||||
statusbar.bg.insert = darkgreen
|
|
||||||
statusbar.fg.command = ${statusbar.fg}
|
|
||||||
statusbar.bg.command = ${statusbar.bg}
|
|
||||||
statusbar.fg.command.private = ${statusbar.fg.private}
|
|
||||||
statusbar.bg.command.private = ${statusbar.bg.private}
|
|
||||||
statusbar.fg.caret = ${statusbar.fg}
|
|
||||||
statusbar.bg.caret = purple
|
|
||||||
statusbar.fg.caret-selection = ${statusbar.fg}
|
|
||||||
statusbar.bg.caret-selection = #a12dff
|
|
||||||
statusbar.progress.bg = white
|
|
||||||
statusbar.url.fg = ${statusbar.fg}
|
|
||||||
statusbar.url.fg.success = white
|
|
||||||
statusbar.url.fg.success.https = lime
|
|
||||||
statusbar.url.fg.error = orange
|
|
||||||
statusbar.url.fg.warn = yellow
|
|
||||||
statusbar.url.fg.hover = aqua
|
|
||||||
tabs.fg.odd = white
|
|
||||||
tabs.bg.odd = grey
|
|
||||||
tabs.fg.even = white
|
|
||||||
tabs.bg.even = darkgrey
|
|
||||||
tabs.fg.selected.odd = white
|
|
||||||
tabs.bg.selected.odd = black
|
|
||||||
tabs.fg.selected.even = ${tabs.fg.selected.odd}
|
|
||||||
tabs.bg.selected.even = ${tabs.bg.selected.odd}
|
|
||||||
tabs.bg.bar = #555555
|
|
||||||
tabs.indicator.start = #0000aa
|
|
||||||
tabs.indicator.stop = #00aa00
|
|
||||||
tabs.indicator.error = #ff0000
|
|
||||||
tabs.indicator.system = rgb
|
|
||||||
hints.fg = black
|
|
||||||
hints.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 247, 133, 0.8), stop:1 rgba(255, 197, 66, 0.8))
|
|
||||||
hints.fg.match = green
|
|
||||||
downloads.bg.bar = black
|
|
||||||
downloads.fg.start = white
|
|
||||||
downloads.bg.start = #0000aa
|
|
||||||
downloads.fg.stop = ${downloads.fg.start}
|
|
||||||
downloads.bg.stop = #00aa00
|
|
||||||
downloads.fg.system = rgb
|
|
||||||
downloads.bg.system = rgb
|
|
||||||
downloads.fg.error = white
|
|
||||||
downloads.bg.error = red
|
|
||||||
webpage.bg = white
|
|
||||||
keyhint.fg = #FFFFFF
|
|
||||||
keyhint.fg.suffix = #FFFF00
|
|
||||||
keyhint.bg = rgba(0, 0, 0, 80%)
|
|
||||||
messages.fg.error = white
|
|
||||||
messages.bg.error = red
|
|
||||||
messages.border.error = #bb0000
|
|
||||||
messages.fg.warning = white
|
|
||||||
messages.bg.warning = darkorange
|
|
||||||
messages.border.warning = #d47300
|
|
||||||
messages.fg.info = white
|
|
||||||
messages.bg.info = black
|
|
||||||
messages.border.info = #333333
|
|
||||||
prompts.fg = white
|
|
||||||
prompts.bg = darkblue
|
|
||||||
prompts.selected.bg = #308cc6
|
|
||||||
[fonts]
|
|
||||||
_monospace = xos4 Terminus, Terminus, Monospace, "DejaVu Sans Mono", Monaco, "Bitstream Vera Sans Mono", "Andale Mono", "Courier New", Courier, "Liberation Mono", monospace, Fixed, Consolas, Terminal
|
|
||||||
completion = 8pt ${_monospace}
|
|
||||||
completion.category = bold ${completion}
|
|
||||||
tabbar = 8pt ${_monospace}
|
|
||||||
statusbar = 8pt ${_monospace}
|
|
||||||
downloads = 8pt ${_monospace}
|
|
||||||
hints = bold 13px ${_monospace}
|
|
||||||
debug-console = 8pt ${_monospace}
|
|
||||||
web-family-standard =
|
|
||||||
web-family-fixed =
|
|
||||||
web-family-serif =
|
|
||||||
web-family-sans-serif =
|
|
||||||
web-family-cursive =
|
|
||||||
web-family-fantasy =
|
|
||||||
web-size-minimum = 0
|
|
||||||
web-size-minimum-logical = 6
|
|
||||||
web-size-default = 16
|
|
||||||
web-size-default-fixed = 13
|
|
||||||
keyhint = 8pt ${_monospace}
|
|
||||||
messages.error = 8pt ${_monospace}
|
|
||||||
messages.warning = 8pt ${_monospace}
|
|
||||||
messages.info = 8pt ${_monospace}
|
|
||||||
prompts = 8pt sans-serif
|
|
||||||
"""
|
|
||||||
|
|
||||||
OLD_KEYS_CONF = """
|
|
||||||
[!normal]
|
|
||||||
leave-mode
|
|
||||||
<escape>
|
|
||||||
<ctrl-[>
|
|
||||||
[normal]
|
|
||||||
clear-keychain ;; search ;; fullscreen --leave
|
|
||||||
<escape>
|
|
||||||
<ctrl-[>
|
|
||||||
set-cmd-text -s :open
|
|
||||||
o
|
|
||||||
set-cmd-text :open {url:pretty}
|
|
||||||
go
|
|
||||||
set-cmd-text -s :open -t
|
|
||||||
O
|
|
||||||
set-cmd-text :open -t -i {url:pretty}
|
|
||||||
gO
|
|
||||||
set-cmd-text -s :open -b
|
|
||||||
xo
|
|
||||||
set-cmd-text :open -b -i {url:pretty}
|
|
||||||
xO
|
|
||||||
set-cmd-text -s :open -w
|
|
||||||
wo
|
|
||||||
set-cmd-text :open -w {url:pretty}
|
|
||||||
wO
|
|
||||||
set-cmd-text /
|
|
||||||
/
|
|
||||||
set-cmd-text ?
|
|
||||||
?
|
|
||||||
set-cmd-text :
|
|
||||||
:
|
|
||||||
open -t
|
|
||||||
ga
|
|
||||||
<ctrl-t>
|
|
||||||
open -w
|
|
||||||
<ctrl-n>
|
|
||||||
tab-close
|
|
||||||
d
|
|
||||||
<ctrl-w>
|
|
||||||
tab-close -o
|
|
||||||
D
|
|
||||||
tab-only
|
|
||||||
co
|
|
||||||
tab-focus
|
|
||||||
T
|
|
||||||
tab-move
|
|
||||||
gm
|
|
||||||
tab-move -
|
|
||||||
gl
|
|
||||||
tab-move +
|
|
||||||
gr
|
|
||||||
tab-next
|
|
||||||
J
|
|
||||||
<ctrl-pgdown>
|
|
||||||
tab-prev
|
|
||||||
K
|
|
||||||
<ctrl-pgup>
|
|
||||||
tab-clone
|
|
||||||
gC
|
|
||||||
reload
|
|
||||||
r
|
|
||||||
<f5>
|
|
||||||
reload -f
|
|
||||||
R
|
|
||||||
<ctrl-f5>
|
|
||||||
back
|
|
||||||
H
|
|
||||||
<back>
|
|
||||||
back -t
|
|
||||||
th
|
|
||||||
back -w
|
|
||||||
wh
|
|
||||||
forward
|
|
||||||
L
|
|
||||||
<forward>
|
|
||||||
forward -t
|
|
||||||
tl
|
|
||||||
forward -w
|
|
||||||
wl
|
|
||||||
fullscreen
|
|
||||||
<f11>
|
|
||||||
hint
|
|
||||||
f
|
|
||||||
hint all tab
|
|
||||||
F
|
|
||||||
hint all window
|
|
||||||
wf
|
|
||||||
hint all tab-bg
|
|
||||||
;b
|
|
||||||
hint all tab-fg
|
|
||||||
;f
|
|
||||||
hint all hover
|
|
||||||
;h
|
|
||||||
hint images
|
|
||||||
;i
|
|
||||||
hint images tab
|
|
||||||
;I
|
|
||||||
hint links fill :open {hint-url}
|
|
||||||
;o
|
|
||||||
hint links fill :open -t -i {hint-url}
|
|
||||||
;O
|
|
||||||
hint links yank
|
|
||||||
;y
|
|
||||||
hint links yank-primary
|
|
||||||
;Y
|
|
||||||
hint --rapid links tab-bg
|
|
||||||
;r
|
|
||||||
hint --rapid links window
|
|
||||||
;R
|
|
||||||
hint links download
|
|
||||||
;d
|
|
||||||
hint inputs
|
|
||||||
;t
|
|
||||||
scroll left
|
|
||||||
h
|
|
||||||
scroll down
|
|
||||||
j
|
|
||||||
scroll up
|
|
||||||
k
|
|
||||||
scroll right
|
|
||||||
l
|
|
||||||
undo
|
|
||||||
u
|
|
||||||
<ctrl-shift-t>
|
|
||||||
scroll-perc 0
|
|
||||||
gg
|
|
||||||
scroll-perc
|
|
||||||
G
|
|
||||||
search-next
|
|
||||||
n
|
|
||||||
search-prev
|
|
||||||
N
|
|
||||||
enter-mode insert
|
|
||||||
i
|
|
||||||
enter-mode caret
|
|
||||||
v
|
|
||||||
enter-mode set_mark
|
|
||||||
`
|
|
||||||
enter-mode jump_mark
|
|
||||||
'
|
|
||||||
yank
|
|
||||||
yy
|
|
||||||
yank -s
|
|
||||||
yY
|
|
||||||
yank title
|
|
||||||
yt
|
|
||||||
yank title -s
|
|
||||||
yT
|
|
||||||
yank domain
|
|
||||||
yd
|
|
||||||
yank domain -s
|
|
||||||
yD
|
|
||||||
yank pretty-url
|
|
||||||
yp
|
|
||||||
yank pretty-url -s
|
|
||||||
yP
|
|
||||||
open -- {clipboard}
|
|
||||||
pp
|
|
||||||
open -- {primary}
|
|
||||||
pP
|
|
||||||
open -t -- {clipboard}
|
|
||||||
Pp
|
|
||||||
open -t -- {primary}
|
|
||||||
PP
|
|
||||||
open -w -- {clipboard}
|
|
||||||
wp
|
|
||||||
open -w -- {primary}
|
|
||||||
wP
|
|
||||||
quickmark-save
|
|
||||||
m
|
|
||||||
set-cmd-text -s :quickmark-load
|
|
||||||
b
|
|
||||||
set-cmd-text -s :quickmark-load -t
|
|
||||||
B
|
|
||||||
set-cmd-text -s :quickmark-load -w
|
|
||||||
wb
|
|
||||||
bookmark-add
|
|
||||||
M
|
|
||||||
set-cmd-text -s :bookmark-load
|
|
||||||
gb
|
|
||||||
set-cmd-text -s :bookmark-load -t
|
|
||||||
gB
|
|
||||||
set-cmd-text -s :bookmark-load -w
|
|
||||||
wB
|
|
||||||
save
|
|
||||||
sf
|
|
||||||
set-cmd-text -s :set
|
|
||||||
ss
|
|
||||||
set-cmd-text -s :set -t
|
|
||||||
sl
|
|
||||||
set-cmd-text -s :bind
|
|
||||||
sk
|
|
||||||
zoom-out
|
|
||||||
-
|
|
||||||
zoom-in
|
|
||||||
+
|
|
||||||
zoom
|
|
||||||
=
|
|
||||||
navigate prev
|
|
||||||
[[
|
|
||||||
navigate next
|
|
||||||
]]
|
|
||||||
navigate prev -t
|
|
||||||
{{
|
|
||||||
navigate next -t
|
|
||||||
}}
|
|
||||||
navigate up
|
|
||||||
gu
|
|
||||||
navigate up -t
|
|
||||||
gU
|
|
||||||
navigate increment
|
|
||||||
<ctrl-a>
|
|
||||||
navigate decrement
|
|
||||||
<ctrl-x>
|
|
||||||
inspector
|
|
||||||
wi
|
|
||||||
download
|
|
||||||
gd
|
|
||||||
download-cancel
|
|
||||||
ad
|
|
||||||
download-clear
|
|
||||||
cd
|
|
||||||
view-source
|
|
||||||
gf
|
|
||||||
set-cmd-text -s :buffer
|
|
||||||
gt
|
|
||||||
tab-focus last
|
|
||||||
<ctrl-tab>
|
|
||||||
<ctrl-6>
|
|
||||||
<ctrl-^>
|
|
||||||
enter-mode passthrough
|
|
||||||
<ctrl-v>
|
|
||||||
quit
|
|
||||||
<ctrl-q>
|
|
||||||
ZQ
|
|
||||||
wq
|
|
||||||
ZZ
|
|
||||||
scroll-page 0 1
|
|
||||||
<ctrl-f>
|
|
||||||
scroll-page 0 -1
|
|
||||||
<ctrl-b>
|
|
||||||
scroll-page 0 0.5
|
|
||||||
<ctrl-d>
|
|
||||||
scroll-page 0 -0.5
|
|
||||||
<ctrl-u>
|
|
||||||
tab-focus 1
|
|
||||||
<alt-1>
|
|
||||||
g0
|
|
||||||
g^
|
|
||||||
tab-focus 2
|
|
||||||
<alt-2>
|
|
||||||
tab-focus 3
|
|
||||||
<alt-3>
|
|
||||||
tab-focus 4
|
|
||||||
<alt-4>
|
|
||||||
tab-focus 5
|
|
||||||
<alt-5>
|
|
||||||
tab-focus 6
|
|
||||||
<alt-6>
|
|
||||||
tab-focus 7
|
|
||||||
<alt-7>
|
|
||||||
tab-focus 8
|
|
||||||
<alt-8>
|
|
||||||
tab-focus -1
|
|
||||||
<alt-9>
|
|
||||||
g$
|
|
||||||
home
|
|
||||||
<ctrl-h>
|
|
||||||
stop
|
|
||||||
<ctrl-s>
|
|
||||||
print
|
|
||||||
<ctrl-alt-p>
|
|
||||||
open qute://settings
|
|
||||||
Ss
|
|
||||||
follow-selected
|
|
||||||
<return>
|
|
||||||
<ctrl-m>
|
|
||||||
<ctrl-j>
|
|
||||||
<shift-return>
|
|
||||||
<enter>
|
|
||||||
<shift-enter>
|
|
||||||
follow-selected -t
|
|
||||||
<ctrl-return>
|
|
||||||
<ctrl-enter>
|
|
||||||
repeat-command
|
|
||||||
.
|
|
||||||
tab-pin
|
|
||||||
<ctrl-p>
|
|
||||||
record-macro
|
|
||||||
q
|
|
||||||
run-macro
|
|
||||||
@
|
|
||||||
[insert]
|
|
||||||
open-editor
|
|
||||||
<ctrl-e>
|
|
||||||
insert-text {primary}
|
|
||||||
<shift-ins>
|
|
||||||
[hint]
|
|
||||||
follow-hint
|
|
||||||
<return>
|
|
||||||
<ctrl-m>
|
|
||||||
<ctrl-j>
|
|
||||||
<shift-return>
|
|
||||||
<enter>
|
|
||||||
<shift-enter>
|
|
||||||
hint --rapid links tab-bg
|
|
||||||
<ctrl-r>
|
|
||||||
hint links
|
|
||||||
<ctrl-f>
|
|
||||||
hint all tab-bg
|
|
||||||
<ctrl-b>
|
|
||||||
[passthrough]
|
|
||||||
[command]
|
|
||||||
command-history-prev
|
|
||||||
<ctrl-p>
|
|
||||||
command-history-next
|
|
||||||
<ctrl-n>
|
|
||||||
completion-item-focus prev
|
|
||||||
<shift-tab>
|
|
||||||
<up>
|
|
||||||
completion-item-focus next
|
|
||||||
<tab>
|
|
||||||
<down>
|
|
||||||
completion-item-focus next-category
|
|
||||||
<ctrl-tab>
|
|
||||||
completion-item-focus prev-category
|
|
||||||
<ctrl-shift-tab>
|
|
||||||
completion-item-del
|
|
||||||
<ctrl-d>
|
|
||||||
command-accept
|
|
||||||
<return>
|
|
||||||
<ctrl-m>
|
|
||||||
<ctrl-j>
|
|
||||||
<shift-return>
|
|
||||||
<enter>
|
|
||||||
<shift-enter>
|
|
||||||
[prompt]
|
|
||||||
prompt-accept
|
|
||||||
<return>
|
|
||||||
<ctrl-m>
|
|
||||||
<ctrl-j>
|
|
||||||
<shift-return>
|
|
||||||
<enter>
|
|
||||||
<shift-enter>
|
|
||||||
prompt-accept yes
|
|
||||||
y
|
|
||||||
prompt-accept no
|
|
||||||
n
|
|
||||||
prompt-open-download
|
|
||||||
<ctrl-x>
|
|
||||||
prompt-item-focus prev
|
|
||||||
<shift-tab>
|
|
||||||
<up>
|
|
||||||
prompt-item-focus next
|
|
||||||
<tab>
|
|
||||||
<down>
|
|
||||||
[command,prompt]
|
|
||||||
rl-backward-char
|
|
||||||
<ctrl-b>
|
|
||||||
rl-forward-char
|
|
||||||
<ctrl-f>
|
|
||||||
rl-backward-word
|
|
||||||
<alt-b>
|
|
||||||
rl-forward-word
|
|
||||||
<alt-f>
|
|
||||||
rl-beginning-of-line
|
|
||||||
<ctrl-a>
|
|
||||||
rl-end-of-line
|
|
||||||
<ctrl-e>
|
|
||||||
rl-unix-line-discard
|
|
||||||
<ctrl-u>
|
|
||||||
rl-kill-line
|
|
||||||
<ctrl-k>
|
|
||||||
rl-kill-word
|
|
||||||
<alt-d>
|
|
||||||
rl-unix-word-rubout
|
|
||||||
<ctrl-w>
|
|
||||||
rl-backward-kill-word
|
|
||||||
<alt-backspace>
|
|
||||||
rl-yank
|
|
||||||
<ctrl-y>
|
|
||||||
rl-delete-char
|
|
||||||
<ctrl-?>
|
|
||||||
rl-backward-delete-char
|
|
||||||
<ctrl-h>
|
|
||||||
[caret]
|
|
||||||
toggle-selection
|
|
||||||
v
|
|
||||||
<space>
|
|
||||||
drop-selection
|
|
||||||
<ctrl-space>
|
|
||||||
enter-mode normal
|
|
||||||
c
|
|
||||||
move-to-next-line
|
|
||||||
j
|
|
||||||
move-to-prev-line
|
|
||||||
k
|
|
||||||
move-to-next-char
|
|
||||||
l
|
|
||||||
move-to-prev-char
|
|
||||||
h
|
|
||||||
move-to-end-of-word
|
|
||||||
e
|
|
||||||
move-to-next-word
|
|
||||||
w
|
|
||||||
move-to-prev-word
|
|
||||||
b
|
|
||||||
move-to-start-of-next-block
|
|
||||||
]
|
|
||||||
move-to-start-of-prev-block
|
|
||||||
[
|
|
||||||
move-to-end-of-next-block
|
|
||||||
}
|
|
||||||
move-to-end-of-prev-block
|
|
||||||
{
|
|
||||||
move-to-start-of-line
|
|
||||||
0
|
|
||||||
move-to-end-of-line
|
|
||||||
$
|
|
||||||
move-to-start-of-document
|
|
||||||
gg
|
|
||||||
move-to-end-of-document
|
|
||||||
G
|
|
||||||
yank selection -s
|
|
||||||
Y
|
|
||||||
yank selection
|
|
||||||
y
|
|
||||||
<return>
|
|
||||||
<ctrl-m>
|
|
||||||
<ctrl-j>
|
|
||||||
<shift-return>
|
|
||||||
<enter>
|
|
||||||
<shift-enter>
|
|
||||||
scroll left
|
|
||||||
H
|
|
||||||
scroll down
|
|
||||||
J
|
|
||||||
scroll up
|
|
||||||
K
|
|
||||||
scroll right
|
|
||||||
L
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def get_diff() -> str:
|
|
||||||
"""Get a HTML diff for the old config files."""
|
|
||||||
old_conf_lines = [] # type: typing.MutableSequence[str]
|
|
||||||
old_key_lines = [] # type: typing.MutableSequence[str]
|
|
||||||
|
|
||||||
for filename, dest in [('qutebrowser.conf', old_conf_lines),
|
|
||||||
('keys.conf', old_key_lines)]:
|
|
||||||
path = os.path.join(standarddir.config(), filename)
|
|
||||||
|
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
|
||||||
for line in f:
|
|
||||||
if not line.strip() or line.startswith('#'):
|
|
||||||
continue
|
|
||||||
dest.append(line.rstrip())
|
|
||||||
|
|
||||||
conf_delta = difflib.unified_diff(OLD_CONF.lstrip().splitlines(),
|
|
||||||
old_conf_lines)
|
|
||||||
key_delta = difflib.unified_diff(OLD_KEYS_CONF.lstrip().splitlines(),
|
|
||||||
old_key_lines)
|
|
||||||
|
|
||||||
conf_diff = '\n'.join(conf_delta)
|
|
||||||
key_diff = '\n'.join(key_delta)
|
|
||||||
|
|
||||||
# pylint: disable=no-member
|
|
||||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/491/
|
|
||||||
lexer = pygments.lexers.DiffLexer()
|
|
||||||
formatter = pygments.formatters.HtmlFormatter(
|
|
||||||
full=True, linenos='table',
|
|
||||||
title='Diffing pre-1.0 default config with pre-1.0 modified config')
|
|
||||||
# pylint: enable=no-member
|
|
||||||
return pygments.highlight(conf_diff + key_diff, lexer, formatter)
|
|
||||||
|
|
@ -19,9 +19,9 @@
|
||||||
|
|
||||||
"""Exceptions related to config parsing."""
|
"""Exceptions related to config parsing."""
|
||||||
|
|
||||||
import typing
|
from typing import Any, Mapping, Optional, Sequence, Union
|
||||||
import attr
|
|
||||||
|
|
||||||
|
import attr
|
||||||
from qutebrowser.utils import usertypes, log
|
from qutebrowser.utils import usertypes, log
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ class BackendError(Error):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name: str,
|
self, name: str,
|
||||||
backend: usertypes.Backend,
|
backend: usertypes.Backend,
|
||||||
raw_backends: typing.Optional[typing.Mapping[str, bool]]
|
raw_backends: Optional[Mapping[str, bool]]
|
||||||
) -> None:
|
) -> None:
|
||||||
if raw_backends is None or not raw_backends[backend.name]:
|
if raw_backends is None or not raw_backends[backend.name]:
|
||||||
msg = ("The {} setting is not available with the {} backend!"
|
msg = ("The {} setting is not available with the {} backend!"
|
||||||
|
|
@ -76,8 +76,7 @@ class ValidationError(Error):
|
||||||
msg: Additional error message.
|
msg: Additional error message.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, value: typing.Any,
|
def __init__(self, value: Any, msg: Union[str, Exception]) -> None:
|
||||||
msg: typing.Union[str, Exception]) -> None:
|
|
||||||
super().__init__("Invalid value '{}' - {}".format(value, msg))
|
super().__init__("Invalid value '{}' - {}".format(value, msg))
|
||||||
self.option = None
|
self.option = None
|
||||||
|
|
||||||
|
|
@ -117,9 +116,9 @@ class ConfigErrorDesc:
|
||||||
traceback: The formatted traceback of the exception.
|
traceback: The formatted traceback of the exception.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text = attr.ib() # type: str
|
text: str = attr.ib()
|
||||||
exception = attr.ib() # type: typing.Union[str, Exception]
|
exception: Union[str, Exception] = attr.ib()
|
||||||
traceback = attr.ib(None) # type: str
|
traceback: str = attr.ib(None)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
if self.traceback:
|
if self.traceback:
|
||||||
|
|
@ -141,7 +140,7 @@ class ConfigFileErrors(Error):
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
basename: str,
|
basename: str,
|
||||||
errors: typing.Sequence[ConfigErrorDesc], *,
|
errors: Sequence[ConfigErrorDesc], *,
|
||||||
fatal: bool = False) -> None:
|
fatal: bool = False) -> None:
|
||||||
super().__init__("Errors occurred while reading {}:\n{}".format(
|
super().__init__("Errors occurred while reading {}:\n{}".format(
|
||||||
basename, '\n'.join(' {}'.format(e) for e in errors)))
|
basename, '\n'.join(' {}'.format(e) for e in errors)))
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,9 @@ import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
import configparser
|
import configparser
|
||||||
import contextlib
|
import contextlib
|
||||||
import typing
|
|
||||||
import re
|
import re
|
||||||
|
from typing import (TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Mapping,
|
||||||
|
MutableMapping, Optional, cast)
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QSettings, qVersion
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QSettings, qVersion
|
||||||
|
|
@ -39,15 +40,15 @@ from qutebrowser.config import (configexc, config, configdata, configutils,
|
||||||
from qutebrowser.keyinput import keyutils
|
from qutebrowser.keyinput import keyutils
|
||||||
from qutebrowser.utils import standarddir, utils, qtutils, log, urlmatch
|
from qutebrowser.utils import standarddir, utils, qtutils, log, urlmatch
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from qutebrowser.misc import savemanager
|
from qutebrowser.misc import savemanager
|
||||||
|
|
||||||
|
|
||||||
# The StateConfig instance
|
# The StateConfig instance
|
||||||
state = typing.cast('StateConfig', None)
|
state = cast('StateConfig', None)
|
||||||
|
|
||||||
|
|
||||||
_SettingsType = typing.Dict[str, typing.Dict[str, typing.Any]]
|
_SettingsType = Dict[str, Dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
class StateConfig(configparser.ConfigParser):
|
class StateConfig(configparser.ConfigParser):
|
||||||
|
|
@ -77,6 +78,7 @@ class StateConfig(configparser.ConfigParser):
|
||||||
deleted_keys = [
|
deleted_keys = [
|
||||||
('general', 'fooled'),
|
('general', 'fooled'),
|
||||||
('general', 'backend-warning-shown'),
|
('general', 'backend-warning-shown'),
|
||||||
|
('general', 'old-qt-warning-shown'),
|
||||||
('geometry', 'inspector'),
|
('geometry', 'inspector'),
|
||||||
]
|
]
|
||||||
for sect, key in deleted_keys:
|
for sect, key in deleted_keys:
|
||||||
|
|
@ -117,7 +119,7 @@ class YamlConfig(QObject):
|
||||||
'autoconfig.yml')
|
'autoconfig.yml')
|
||||||
self._dirty = False
|
self._dirty = False
|
||||||
|
|
||||||
self._values = {} # type: typing.Dict[str, configutils.Values]
|
self._values: Dict[str, configutils.Values] = {}
|
||||||
for name, opt in configdata.DATA.items():
|
for name, opt in configdata.DATA.items():
|
||||||
self._values[name] = configutils.Values(opt)
|
self._values[name] = configutils.Values(opt)
|
||||||
|
|
||||||
|
|
@ -130,7 +132,7 @@ class YamlConfig(QObject):
|
||||||
"""
|
"""
|
||||||
save_manager.add_saveable('yaml-config', self._save, self.changed)
|
save_manager.add_saveable('yaml-config', self._save, self.changed)
|
||||||
|
|
||||||
def __iter__(self) -> typing.Iterator[configutils.Values]:
|
def __iter__(self) -> Iterator[configutils.Values]:
|
||||||
"""Iterate over configutils.Values items."""
|
"""Iterate over configutils.Values items."""
|
||||||
yield from self._values.values()
|
yield from self._values.values()
|
||||||
|
|
||||||
|
|
@ -145,7 +147,7 @@ class YamlConfig(QObject):
|
||||||
if not self._dirty:
|
if not self._dirty:
|
||||||
return
|
return
|
||||||
|
|
||||||
settings = {} # type: _SettingsType
|
settings: _SettingsType = {}
|
||||||
for name, values in sorted(self._values.items()):
|
for name, values in sorted(self._values.items()):
|
||||||
if not values:
|
if not values:
|
||||||
continue
|
continue
|
||||||
|
|
@ -167,10 +169,7 @@ class YamlConfig(QObject):
|
||||||
""".lstrip('\n')))
|
""".lstrip('\n')))
|
||||||
utils.yaml_dump(data, f)
|
utils.yaml_dump(data, f)
|
||||||
|
|
||||||
def _pop_object(self,
|
def _pop_object(self, yaml_data: Any, key: str, typ: type) -> Any:
|
||||||
yaml_data: typing.Any,
|
|
||||||
key: str,
|
|
||||||
typ: type) -> typing.Any:
|
|
||||||
"""Get a global object from the given data."""
|
"""Get a global object from the given data."""
|
||||||
if not isinstance(yaml_data, dict):
|
if not isinstance(yaml_data, dict):
|
||||||
desc = configexc.ConfigErrorDesc("While loading data",
|
desc = configexc.ConfigErrorDesc("While loading data",
|
||||||
|
|
@ -227,19 +226,18 @@ class YamlConfig(QObject):
|
||||||
self._validate_names(settings)
|
self._validate_names(settings)
|
||||||
self._build_values(settings)
|
self._build_values(settings)
|
||||||
|
|
||||||
def _load_settings_object(self, yaml_data: typing.Any) -> '_SettingsType':
|
def _load_settings_object(self, yaml_data: Any) -> '_SettingsType':
|
||||||
"""Load the settings from the settings: key."""
|
"""Load the settings from the settings: key."""
|
||||||
return self._pop_object(yaml_data, 'settings', dict)
|
return self._pop_object(yaml_data, 'settings', dict)
|
||||||
|
|
||||||
def _load_legacy_settings_object(self,
|
def _load_legacy_settings_object(self, yaml_data: Any) -> '_SettingsType':
|
||||||
yaml_data: typing.Any) -> '_SettingsType':
|
|
||||||
data = self._pop_object(yaml_data, 'global', dict)
|
data = self._pop_object(yaml_data, 'global', dict)
|
||||||
settings = {}
|
settings = {}
|
||||||
for name, value in data.items():
|
for name, value in data.items():
|
||||||
settings[name] = {'global': value}
|
settings[name] = {'global': value}
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
def _build_values(self, settings: typing.Mapping) -> None:
|
def _build_values(self, settings: Mapping) -> None:
|
||||||
"""Build up self._values from the values in the given dict."""
|
"""Build up self._values from the values in the given dict."""
|
||||||
errors = []
|
errors = []
|
||||||
for name, yaml_values in settings.items():
|
for name, yaml_values in settings.items():
|
||||||
|
|
@ -285,7 +283,7 @@ class YamlConfig(QObject):
|
||||||
for e in sorted(unknown)]
|
for e in sorted(unknown)]
|
||||||
raise configexc.ConfigFileErrors('autoconfig.yml', errors)
|
raise configexc.ConfigFileErrors('autoconfig.yml', errors)
|
||||||
|
|
||||||
def set_obj(self, name: str, value: typing.Any, *,
|
def set_obj(self, name: str, value: Any, *,
|
||||||
pattern: urlmatch.UrlPattern = None) -> None:
|
pattern: urlmatch.UrlPattern = None) -> None:
|
||||||
"""Set the given setting to the given value."""
|
"""Set the given setting to the given value."""
|
||||||
self._values[name].add(value, pattern)
|
self._values[name].add(value, pattern)
|
||||||
|
|
@ -477,8 +475,7 @@ class YamlMigrations(QObject):
|
||||||
self._settings[name][scope] = value
|
self._settings[name][scope] = value
|
||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
|
|
||||||
def _migrate_to_multiple(self, old_name: str,
|
def _migrate_to_multiple(self, old_name: str, new_names: Iterable[str]) -> None:
|
||||||
new_names: typing.Iterable[str]) -> None:
|
|
||||||
if old_name not in self._settings:
|
if old_name not in self._settings:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -541,12 +538,12 @@ class ConfigAPI:
|
||||||
def __init__(self, conf: config.Config, keyconfig: config.KeyConfig):
|
def __init__(self, conf: config.Config, keyconfig: config.KeyConfig):
|
||||||
self._config = conf
|
self._config = conf
|
||||||
self._keyconfig = keyconfig
|
self._keyconfig = keyconfig
|
||||||
self.errors = [] # type: typing.List[configexc.ConfigErrorDesc]
|
self.errors: List[configexc.ConfigErrorDesc] = []
|
||||||
self.configdir = pathlib.Path(standarddir.config())
|
self.configdir = pathlib.Path(standarddir.config())
|
||||||
self.datadir = pathlib.Path(standarddir.data())
|
self.datadir = pathlib.Path(standarddir.data())
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _handle_error(self, action: str, name: str) -> typing.Iterator[None]:
|
def _handle_error(self, action: str, name: str) -> Iterator[None]:
|
||||||
"""Catch config-related exceptions and save them in self.errors."""
|
"""Catch config-related exceptions and save them in self.errors."""
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
|
|
@ -582,21 +579,19 @@ class ConfigAPI:
|
||||||
with self._handle_error('reading', 'autoconfig.yml'):
|
with self._handle_error('reading', 'autoconfig.yml'):
|
||||||
read_autoconfig()
|
read_autoconfig()
|
||||||
|
|
||||||
def get(self, name: str, pattern: str = None) -> typing.Any:
|
def get(self, name: str, pattern: str = None) -> Any:
|
||||||
"""Get a setting value from the config, optionally with a pattern."""
|
"""Get a setting value from the config, optionally with a pattern."""
|
||||||
with self._handle_error('getting', name):
|
with self._handle_error('getting', name):
|
||||||
urlpattern = urlmatch.UrlPattern(pattern) if pattern else None
|
urlpattern = urlmatch.UrlPattern(pattern) if pattern else None
|
||||||
return self._config.get_mutable_obj(name, pattern=urlpattern)
|
return self._config.get_mutable_obj(name, pattern=urlpattern)
|
||||||
|
|
||||||
def set(self, name: str, value: typing.Any, pattern: str = None) -> None:
|
def set(self, name: str, value: Any, pattern: str = None) -> None:
|
||||||
"""Set a setting value in the config, optionally with a pattern."""
|
"""Set a setting value in the config, optionally with a pattern."""
|
||||||
with self._handle_error('setting', name):
|
with self._handle_error('setting', name):
|
||||||
urlpattern = urlmatch.UrlPattern(pattern) if pattern else None
|
urlpattern = urlmatch.UrlPattern(pattern) if pattern else None
|
||||||
self._config.set_obj(name, value, pattern=urlpattern)
|
self._config.set_obj(name, value, pattern=urlpattern)
|
||||||
|
|
||||||
def bind(self, key: str,
|
def bind(self, key: str, command: Optional[str], mode: str = 'normal') -> None:
|
||||||
command: typing.Optional[str],
|
|
||||||
mode: str = 'normal') -> None:
|
|
||||||
"""Bind a key to a command, with an optional key mode."""
|
"""Bind a key to a command, with an optional key mode."""
|
||||||
with self._handle_error('binding', key):
|
with self._handle_error('binding', key):
|
||||||
seq = keyutils.KeySequence.parse(key)
|
seq = keyutils.KeySequence.parse(key)
|
||||||
|
|
@ -625,7 +620,7 @@ class ConfigAPI:
|
||||||
self.errors += e.errors
|
self.errors += e.errors
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def pattern(self, pattern: str) -> typing.Iterator[config.ConfigContainer]:
|
def pattern(self, pattern: str) -> Iterator[config.ConfigContainer]:
|
||||||
"""Get a ConfigContainer for the given pattern."""
|
"""Get a ConfigContainer for the given pattern."""
|
||||||
# We need to propagate the exception so we don't need to return
|
# We need to propagate the exception so we don't need to return
|
||||||
# something.
|
# something.
|
||||||
|
|
@ -641,9 +636,8 @@ class ConfigPyWriter:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
options: typing.List,
|
options: List,
|
||||||
bindings: typing.MutableMapping[
|
bindings: MutableMapping[str, Mapping[str, Optional[str]]],
|
||||||
str, typing.Mapping[str, typing.Optional[str]]],
|
|
||||||
*, commented: bool) -> None:
|
*, commented: bool) -> None:
|
||||||
self._options = options
|
self._options = options
|
||||||
self._bindings = bindings
|
self._bindings = bindings
|
||||||
|
|
@ -664,7 +658,7 @@ class ConfigPyWriter:
|
||||||
else:
|
else:
|
||||||
return line
|
return line
|
||||||
|
|
||||||
def _gen_lines(self) -> typing.Iterator[str]:
|
def _gen_lines(self) -> Iterator[str]:
|
||||||
"""Generate a config.py with the given settings/bindings.
|
"""Generate a config.py with the given settings/bindings.
|
||||||
|
|
||||||
Yields individual lines.
|
Yields individual lines.
|
||||||
|
|
@ -673,7 +667,7 @@ class ConfigPyWriter:
|
||||||
yield from self._gen_options()
|
yield from self._gen_options()
|
||||||
yield from self._gen_bindings()
|
yield from self._gen_bindings()
|
||||||
|
|
||||||
def _gen_header(self) -> typing.Iterator[str]:
|
def _gen_header(self) -> Iterator[str]:
|
||||||
"""Generate the initial header of the config."""
|
"""Generate the initial header of the config."""
|
||||||
yield self._line("# Autogenerated config.py")
|
yield self._line("# Autogenerated config.py")
|
||||||
yield self._line("#")
|
yield self._line("#")
|
||||||
|
|
@ -706,7 +700,7 @@ class ConfigPyWriter:
|
||||||
yield self._line("config.load_autoconfig(False)")
|
yield self._line("config.load_autoconfig(False)")
|
||||||
yield ''
|
yield ''
|
||||||
|
|
||||||
def _gen_options(self) -> typing.Iterator[str]:
|
def _gen_options(self) -> Iterator[str]:
|
||||||
"""Generate the options part of the config."""
|
"""Generate the options part of the config."""
|
||||||
for pattern, opt, value in self._options:
|
for pattern, opt, value in self._options:
|
||||||
if opt.name in ['bindings.commands', 'bindings.default']:
|
if opt.name in ['bindings.commands', 'bindings.default']:
|
||||||
|
|
@ -734,7 +728,7 @@ class ConfigPyWriter:
|
||||||
opt.name, value, str(pattern)))
|
opt.name, value, str(pattern)))
|
||||||
yield ''
|
yield ''
|
||||||
|
|
||||||
def _gen_bindings(self) -> typing.Iterator[str]:
|
def _gen_bindings(self) -> Iterator[str]:
|
||||||
"""Generate the bindings part of the config."""
|
"""Generate the bindings part of the config."""
|
||||||
normal_bindings = self._bindings.pop('normal', {})
|
normal_bindings = self._bindings.pop('normal', {})
|
||||||
if normal_bindings:
|
if normal_bindings:
|
||||||
|
|
@ -835,7 +829,7 @@ def read_autoconfig() -> None:
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def saved_sys_properties() -> typing.Iterator[None]:
|
def saved_sys_properties() -> Iterator[None]:
|
||||||
"""Save various sys properties such as sys.path and sys.modules."""
|
"""Save various sys properties such as sys.path and sys.modules."""
|
||||||
old_path = sys.path.copy()
|
old_path = sys.path.copy()
|
||||||
old_modules = sys.modules.copy()
|
old_modules = sys.modules.copy()
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,14 @@ import itertools
|
||||||
import functools
|
import functools
|
||||||
import operator
|
import operator
|
||||||
import json
|
import json
|
||||||
import typing
|
from typing import (Any, Callable, Dict as DictType, Iterable, Iterator,
|
||||||
|
List as ListType, Optional, Pattern, Sequence, Tuple, Union)
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import yaml
|
import yaml
|
||||||
from PyQt5.QtCore import QUrl, Qt
|
from PyQt5.QtCore import QUrl, Qt
|
||||||
from PyQt5.QtGui import QColor, QFontDatabase
|
from PyQt5.QtGui import QColor
|
||||||
from PyQt5.QtWidgets import QTabWidget, QTabBar, QApplication
|
from PyQt5.QtWidgets import QTabWidget, QTabBar
|
||||||
from PyQt5.QtNetwork import QNetworkProxy
|
from PyQt5.QtNetwork import QNetworkProxy
|
||||||
|
|
||||||
from qutebrowser.misc import objects, debugcachestats
|
from qutebrowser.misc import objects, debugcachestats
|
||||||
|
|
@ -79,10 +80,10 @@ BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
|
||||||
'0': False, 'no': False, 'false': False, 'off': False}
|
'0': False, 'no': False, 'false': False, 'off': False}
|
||||||
|
|
||||||
|
|
||||||
_Completions = typing.Optional[typing.Iterable[typing.Tuple[str, str]]]
|
_Completions = Optional[Iterable[Tuple[str, str]]]
|
||||||
_StrUnset = typing.Union[str, usertypes.Unset]
|
_StrUnset = Union[str, usertypes.Unset]
|
||||||
_UnsetNone = typing.Union[None, usertypes.Unset]
|
_UnsetNone = Union[None, usertypes.Unset]
|
||||||
_StrUnsetNone = typing.Union[str, _UnsetNone]
|
_StrUnsetNone = Union[str, _UnsetNone]
|
||||||
|
|
||||||
|
|
||||||
class ValidValues:
|
class ValidValues:
|
||||||
|
|
@ -95,35 +96,40 @@ class ValidValues:
|
||||||
generate_docs: Whether to show the values in the docs.
|
generate_docs: Whether to show the values in the docs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
*values: typing.Union[str,
|
self,
|
||||||
typing.Dict[str, str],
|
*values: Union[
|
||||||
typing.Tuple[str, str]],
|
str,
|
||||||
generate_docs: bool = True) -> None:
|
DictType[str, Optional[str]],
|
||||||
|
Tuple[str, Optional[str]],
|
||||||
|
],
|
||||||
|
generate_docs: bool = True,
|
||||||
|
) -> None:
|
||||||
if not values:
|
if not values:
|
||||||
raise ValueError("ValidValues with no values makes no sense!")
|
raise ValueError("ValidValues with no values makes no sense!")
|
||||||
self.descriptions = {} # type: typing.Dict[str, str]
|
self.descriptions: DictType[str, str] = {}
|
||||||
self.values = [] # type: typing.List[str]
|
self.values: ListType[str] = []
|
||||||
self.generate_docs = generate_docs
|
self.generate_docs = generate_docs
|
||||||
for value in values:
|
for value in values:
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
# Value without description
|
# Value without description
|
||||||
self.values.append(value)
|
val = value
|
||||||
|
desc = None
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
# List of dicts from configdata.yml
|
# List of dicts from configdata.yml
|
||||||
assert len(value) == 1, value
|
assert len(value) == 1, value
|
||||||
value, desc = list(value.items())[0]
|
val, desc = list(value.items())[0]
|
||||||
self.values.append(value)
|
|
||||||
self.descriptions[value] = desc
|
|
||||||
else:
|
else:
|
||||||
# (value, description) tuple
|
val, desc = value
|
||||||
self.values.append(value[0])
|
|
||||||
self.descriptions[value[0]] = value[1]
|
self.values.append(val)
|
||||||
|
if desc is not None:
|
||||||
|
self.descriptions[val] = desc
|
||||||
|
|
||||||
def __contains__(self, val: str) -> bool:
|
def __contains__(self, val: str) -> bool:
|
||||||
return val in self.values
|
return val in self.values
|
||||||
|
|
||||||
def __iter__(self) -> typing.Iterator[str]:
|
def __iter__(self) -> Iterator[str]:
|
||||||
return self.values.__iter__()
|
return self.values.__iter__()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|
@ -150,19 +156,19 @@ class BaseType:
|
||||||
|
|
||||||
def __init__(self, none_ok: bool = False) -> None:
|
def __init__(self, none_ok: bool = False) -> None:
|
||||||
self.none_ok = none_ok
|
self.none_ok = none_ok
|
||||||
self.valid_values = None # type: typing.Optional[ValidValues]
|
self.valid_values: Optional[ValidValues] = None
|
||||||
|
|
||||||
def get_name(self) -> str:
|
def get_name(self) -> str:
|
||||||
"""Get a name for the type for documentation."""
|
"""Get a name for the type for documentation."""
|
||||||
return self.__class__.__name__
|
return self.__class__.__name__
|
||||||
|
|
||||||
def get_valid_values(self) -> typing.Optional[ValidValues]:
|
def get_valid_values(self) -> Optional[ValidValues]:
|
||||||
"""Get the type's valid values for documentation."""
|
"""Get the type's valid values for documentation."""
|
||||||
return self.valid_values
|
return self.valid_values
|
||||||
|
|
||||||
def _basic_py_validation(
|
def _basic_py_validation(
|
||||||
self, value: typing.Any,
|
self, value: Any,
|
||||||
pytype: typing.Union[type, typing.Tuple[type, ...]]) -> None:
|
pytype: Union[type, Tuple[type, ...]]) -> None:
|
||||||
"""Do some basic validation for Python values (emptyness, type).
|
"""Do some basic validation for Python values (emptyness, type).
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
@ -214,8 +220,7 @@ class BaseType:
|
||||||
raise configexc.ValidationError(
|
raise configexc.ValidationError(
|
||||||
value, "may not contain unprintable chars!")
|
value, "may not contain unprintable chars!")
|
||||||
|
|
||||||
def _validate_surrogate_escapes(self, full_value: typing.Any,
|
def _validate_surrogate_escapes(self, full_value: Any, value: Any) -> None:
|
||||||
value: typing.Any) -> None:
|
|
||||||
"""Make sure the given value doesn't contain surrogate escapes.
|
"""Make sure the given value doesn't contain surrogate escapes.
|
||||||
|
|
||||||
This is used for values passed to json.dump, as it can't handle those.
|
This is used for values passed to json.dump, as it can't handle those.
|
||||||
|
|
@ -241,7 +246,7 @@ class BaseType:
|
||||||
value,
|
value,
|
||||||
"valid values: {}".format(', '.join(self.valid_values)))
|
"valid values: {}".format(', '.join(self.valid_values)))
|
||||||
|
|
||||||
def from_str(self, value: str) -> typing.Any:
|
def from_str(self, value: str) -> Any:
|
||||||
"""Get the setting value from a string.
|
"""Get the setting value from a string.
|
||||||
|
|
||||||
By default this invokes to_py() for validation and returns the
|
By default this invokes to_py() for validation and returns the
|
||||||
|
|
@ -260,11 +265,11 @@ class BaseType:
|
||||||
return None
|
return None
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def from_obj(self, value: typing.Any) -> typing.Any:
|
def from_obj(self, value: Any) -> Any:
|
||||||
"""Get the setting value from a config.py/YAML object."""
|
"""Get the setting value from a config.py/YAML object."""
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_py(self, value: typing.Any) -> typing.Any:
|
def to_py(self, value: Any) -> Any:
|
||||||
"""Get the setting value from a Python value.
|
"""Get the setting value from a Python value.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -278,7 +283,7 @@ class BaseType:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def to_str(self, value: typing.Any) -> str:
|
def to_str(self, value: Any) -> str:
|
||||||
"""Get a string from the setting value.
|
"""Get a string from the setting value.
|
||||||
|
|
||||||
The resulting string should be parseable again by from_str.
|
The resulting string should be parseable again by from_str.
|
||||||
|
|
@ -288,7 +293,7 @@ class BaseType:
|
||||||
assert isinstance(value, str), value
|
assert isinstance(value, str), value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_doc(self, value: typing.Any, indent: int = 0) -> str:
|
def to_doc(self, value: Any, indent: int = 0) -> str:
|
||||||
"""Get a string with the given value for the documentation.
|
"""Get a string with the given value for the documentation.
|
||||||
|
|
||||||
This currently uses asciidoc syntax.
|
This currently uses asciidoc syntax.
|
||||||
|
|
@ -310,17 +315,10 @@ class BaseType:
|
||||||
"""
|
"""
|
||||||
if self.valid_values is None:
|
if self.valid_values is None:
|
||||||
return None
|
return None
|
||||||
else:
|
return [
|
||||||
out = []
|
(val, self.valid_values.descriptions.get(val, ""))
|
||||||
for val in self.valid_values:
|
for val in self.valid_values
|
||||||
try:
|
]
|
||||||
desc = self.valid_values.descriptions[val]
|
|
||||||
except KeyError:
|
|
||||||
# Some values are self-explaining and don't need a
|
|
||||||
# description.
|
|
||||||
desc = ""
|
|
||||||
out.append((val, desc))
|
|
||||||
return out
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return utils.get_repr(self, none_ok=self.none_ok)
|
return utils.get_repr(self, none_ok=self.none_ok)
|
||||||
|
|
@ -331,24 +329,25 @@ class MappingType(BaseType):
|
||||||
"""Base class for any setting which has a mapping to the given values.
|
"""Base class for any setting which has a mapping to the given values.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
MAPPING: The mapping to use.
|
MAPPING: A mapping from config values to (translated_value, docs) tuples.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MAPPING = {} # type: typing.Dict[str, typing.Any]
|
MAPPING: DictType[str, Tuple[Any, Optional[str]]] = {}
|
||||||
|
|
||||||
def __init__(self, none_ok: bool = False,
|
def __init__(self, none_ok: bool = False) -> None:
|
||||||
valid_values: ValidValues = None) -> None:
|
|
||||||
super().__init__(none_ok)
|
super().__init__(none_ok)
|
||||||
self.valid_values = valid_values
|
self.valid_values = ValidValues(
|
||||||
|
*[(key, doc) for (key, (_val, doc)) in self.MAPPING.items()])
|
||||||
|
|
||||||
def to_py(self, value: typing.Any) -> typing.Any:
|
def to_py(self, value: Any) -> Any:
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
elif not value:
|
elif not value:
|
||||||
return None
|
return None
|
||||||
self._validate_valid_values(value.lower())
|
self._validate_valid_values(value.lower())
|
||||||
return self.MAPPING[value.lower()]
|
mapped, _doc = self.MAPPING[value.lower()]
|
||||||
|
return mapped
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return utils.get_repr(self, none_ok=self.none_ok,
|
return utils.get_repr(self, none_ok=self.none_ok,
|
||||||
|
|
@ -491,10 +490,10 @@ class List(BaseType):
|
||||||
name += " of " + self.valtype.get_name()
|
name += " of " + self.valtype.get_name()
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def get_valid_values(self) -> typing.Optional[ValidValues]:
|
def get_valid_values(self) -> Optional[ValidValues]:
|
||||||
return self.valtype.get_valid_values()
|
return self.valtype.get_valid_values()
|
||||||
|
|
||||||
def from_str(self, value: str) -> typing.Optional[typing.List]:
|
def from_str(self, value: str) -> Optional[ListType]:
|
||||||
self._basic_str_validation(value)
|
self._basic_str_validation(value)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
@ -509,15 +508,15 @@ class List(BaseType):
|
||||||
self.to_py(yaml_val)
|
self.to_py(yaml_val)
|
||||||
return yaml_val
|
return yaml_val
|
||||||
|
|
||||||
def from_obj(self, value: typing.Optional[typing.List]) -> typing.List:
|
def from_obj(self, value: Optional[ListType]) -> ListType:
|
||||||
if value is None:
|
if value is None:
|
||||||
return []
|
return []
|
||||||
return [self.valtype.from_obj(v) for v in value]
|
return [self.valtype.from_obj(v) for v in value]
|
||||||
|
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: typing.Union[typing.List, usertypes.Unset]
|
value: Union[ListType, usertypes.Unset]
|
||||||
) -> typing.Union[typing.List, usertypes.Unset]:
|
) -> Union[ListType, usertypes.Unset]:
|
||||||
self._basic_py_validation(value, list)
|
self._basic_py_validation(value, list)
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
@ -532,13 +531,13 @@ class List(BaseType):
|
||||||
"be set!".format(self.length))
|
"be set!".format(self.length))
|
||||||
return [self.valtype.to_py(v) for v in value]
|
return [self.valtype.to_py(v) for v in value]
|
||||||
|
|
||||||
def to_str(self, value: typing.List) -> str:
|
def to_str(self, value: ListType) -> str:
|
||||||
if not value:
|
if not value:
|
||||||
# An empty list is treated just like None -> empty string
|
# An empty list is treated just like None -> empty string
|
||||||
return ''
|
return ''
|
||||||
return json.dumps(value)
|
return json.dumps(value)
|
||||||
|
|
||||||
def to_doc(self, value: typing.List, indent: int = 0) -> str:
|
def to_doc(self, value: ListType, indent: int = 0) -> str:
|
||||||
if not value:
|
if not value:
|
||||||
return 'empty'
|
return 'empty'
|
||||||
|
|
||||||
|
|
@ -572,14 +571,13 @@ class ListOrValue(BaseType):
|
||||||
|
|
||||||
def __init__(self, valtype: BaseType, *,
|
def __init__(self, valtype: BaseType, *,
|
||||||
none_ok: bool = False,
|
none_ok: bool = False,
|
||||||
**kwargs: typing.Any) -> None:
|
**kwargs: Any) -> None:
|
||||||
super().__init__(none_ok)
|
super().__init__(none_ok)
|
||||||
assert not isinstance(valtype, (List, ListOrValue)), valtype
|
assert not isinstance(valtype, (List, ListOrValue)), valtype
|
||||||
self.listtype = List(valtype, none_ok=none_ok, **kwargs)
|
self.listtype = List(valtype, none_ok=none_ok, **kwargs)
|
||||||
self.valtype = valtype
|
self.valtype = valtype
|
||||||
|
|
||||||
def _val_and_type(self,
|
def _val_and_type(self, value: Any) -> Tuple[Any, BaseType]:
|
||||||
value: typing.Any) -> typing.Tuple[typing.Any, BaseType]:
|
|
||||||
"""Get the value and type to use for to_str/to_doc/from_str."""
|
"""Get the value and type to use for to_str/to_doc/from_str."""
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
if len(value) == 1:
|
if len(value) == 1:
|
||||||
|
|
@ -592,21 +590,21 @@ class ListOrValue(BaseType):
|
||||||
def get_name(self) -> str:
|
def get_name(self) -> str:
|
||||||
return self.listtype.get_name() + ', or ' + self.valtype.get_name()
|
return self.listtype.get_name() + ', or ' + self.valtype.get_name()
|
||||||
|
|
||||||
def get_valid_values(self) -> typing.Optional[ValidValues]:
|
def get_valid_values(self) -> Optional[ValidValues]:
|
||||||
return self.valtype.get_valid_values()
|
return self.valtype.get_valid_values()
|
||||||
|
|
||||||
def from_str(self, value: str) -> typing.Any:
|
def from_str(self, value: str) -> Any:
|
||||||
try:
|
try:
|
||||||
return self.listtype.from_str(value)
|
return self.listtype.from_str(value)
|
||||||
except configexc.ValidationError:
|
except configexc.ValidationError:
|
||||||
return self.valtype.from_str(value)
|
return self.valtype.from_str(value)
|
||||||
|
|
||||||
def from_obj(self, value: typing.Any) -> typing.Any:
|
def from_obj(self, value: Any) -> Any:
|
||||||
if value is None:
|
if value is None:
|
||||||
return []
|
return []
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_py(self, value: typing.Any) -> typing.Any:
|
def to_py(self, value: Any) -> Any:
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
@ -615,14 +613,14 @@ class ListOrValue(BaseType):
|
||||||
except configexc.ValidationError:
|
except configexc.ValidationError:
|
||||||
return self.listtype.to_py(value)
|
return self.listtype.to_py(value)
|
||||||
|
|
||||||
def to_str(self, value: typing.Any) -> str:
|
def to_str(self, value: Any) -> str:
|
||||||
if value is None:
|
if value is None:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
val, typ = self._val_and_type(value)
|
val, typ = self._val_and_type(value)
|
||||||
return typ.to_str(val)
|
return typ.to_str(val)
|
||||||
|
|
||||||
def to_doc(self, value: typing.Any, indent: int = 0) -> str:
|
def to_doc(self, value: Any, indent: int = 0) -> str:
|
||||||
if value is None:
|
if value is None:
|
||||||
return 'empty'
|
return 'empty'
|
||||||
|
|
||||||
|
|
@ -641,7 +639,7 @@ class FlagList(List):
|
||||||
the valid values of the setting.
|
the valid values of the setting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
combinable_values = None # type: typing.Optional[typing.Sequence]
|
combinable_values: Optional[Sequence] = None
|
||||||
|
|
||||||
_show_valtype = False
|
_show_valtype = False
|
||||||
|
|
||||||
|
|
@ -651,15 +649,15 @@ class FlagList(List):
|
||||||
super().__init__(valtype=String(), none_ok=none_ok, length=length)
|
super().__init__(valtype=String(), none_ok=none_ok, length=length)
|
||||||
self.valtype.valid_values = valid_values
|
self.valtype.valid_values = valid_values
|
||||||
|
|
||||||
def _check_duplicates(self, values: typing.List) -> None:
|
def _check_duplicates(self, values: ListType) -> None:
|
||||||
if len(set(values)) != len(values):
|
if len(set(values)) != len(values):
|
||||||
raise configexc.ValidationError(
|
raise configexc.ValidationError(
|
||||||
values, "List contains duplicate values!")
|
values, "List contains duplicate values!")
|
||||||
|
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: typing.Union[usertypes.Unset, typing.List],
|
value: Union[usertypes.Unset, ListType],
|
||||||
) -> typing.Union[usertypes.Unset, typing.List]:
|
) -> Union[usertypes.Unset, ListType]:
|
||||||
vals = super().to_py(value)
|
vals = super().to_py(value)
|
||||||
if not isinstance(vals, usertypes.Unset):
|
if not isinstance(vals, usertypes.Unset):
|
||||||
self._check_duplicates(vals)
|
self._check_duplicates(vals)
|
||||||
|
|
@ -703,13 +701,12 @@ class Bool(BaseType):
|
||||||
super().__init__(none_ok)
|
super().__init__(none_ok)
|
||||||
self.valid_values = ValidValues('true', 'false', generate_docs=False)
|
self.valid_values = ValidValues('true', 'false', generate_docs=False)
|
||||||
|
|
||||||
def to_py(self,
|
def to_py(self, value: Union[bool, str, None]) -> Optional[bool]:
|
||||||
value: typing.Union[bool, str, None]) -> typing.Optional[bool]:
|
|
||||||
self._basic_py_validation(value, bool)
|
self._basic_py_validation(value, bool)
|
||||||
assert not isinstance(value, str)
|
assert not isinstance(value, str)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def from_str(self, value: str) -> typing.Optional[bool]:
|
def from_str(self, value: str) -> Optional[bool]:
|
||||||
self._basic_str_validation(value)
|
self._basic_str_validation(value)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
@ -719,7 +716,7 @@ class Bool(BaseType):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise configexc.ValidationError(value, "must be a boolean!")
|
raise configexc.ValidationError(value, "must be a boolean!")
|
||||||
|
|
||||||
def to_str(self, value: typing.Optional[bool]) -> str:
|
def to_str(self, value: Optional[bool]) -> str:
|
||||||
mapping = {
|
mapping = {
|
||||||
None: '',
|
None: '',
|
||||||
True: 'true',
|
True: 'true',
|
||||||
|
|
@ -737,7 +734,7 @@ class BoolAsk(Bool):
|
||||||
self.valid_values = ValidValues('true', 'false', 'ask')
|
self.valid_values = ValidValues('true', 'false', 'ask')
|
||||||
|
|
||||||
def to_py(self, # type: ignore[override]
|
def to_py(self, # type: ignore[override]
|
||||||
value: typing.Union[bool, str]) -> typing.Union[bool, str, None]:
|
value: Union[bool, str]) -> Union[bool, str, None]:
|
||||||
# basic validation unneeded if it's == 'ask' and done by Bool if we
|
# basic validation unneeded if it's == 'ask' and done by Bool if we
|
||||||
# call super().to_py
|
# call super().to_py
|
||||||
if isinstance(value, str) and value.lower() == 'ask':
|
if isinstance(value, str) and value.lower() == 'ask':
|
||||||
|
|
@ -745,14 +742,14 @@ class BoolAsk(Bool):
|
||||||
return super().to_py(value)
|
return super().to_py(value)
|
||||||
|
|
||||||
def from_str(self, # type: ignore[override]
|
def from_str(self, # type: ignore[override]
|
||||||
value: str) -> typing.Union[bool, str, None]:
|
value: str) -> Union[bool, str, None]:
|
||||||
# basic validation unneeded if it's == 'ask' and done by Bool if we
|
# basic validation unneeded if it's == 'ask' and done by Bool if we
|
||||||
# call super().from_str
|
# call super().from_str
|
||||||
if value.lower() == 'ask':
|
if value.lower() == 'ask':
|
||||||
return 'ask'
|
return 'ask'
|
||||||
return super().from_str(value)
|
return super().from_str(value)
|
||||||
|
|
||||||
def to_str(self, value: typing.Union[bool, str, None]) -> str:
|
def to_str(self, value: Union[bool, str, None]) -> str:
|
||||||
mapping = {
|
mapping = {
|
||||||
None: '',
|
None: '',
|
||||||
True: 'true',
|
True: 'true',
|
||||||
|
|
@ -785,8 +782,8 @@ class _Numeric(BaseType): # pylint: disable=abstract-method
|
||||||
.format(self.minval, self.maxval))
|
.format(self.minval, self.maxval))
|
||||||
|
|
||||||
def _parse_bound(
|
def _parse_bound(
|
||||||
self, bound: typing.Union[None, str, int, float]
|
self, bound: Union[None, str, int, float]
|
||||||
) -> typing.Union[None, int, float]:
|
) -> Union[None, int, float]:
|
||||||
"""Get a numeric bound from a string like 'maxint'."""
|
"""Get a numeric bound from a string like 'maxint'."""
|
||||||
if bound == 'maxint':
|
if bound == 'maxint':
|
||||||
return qtutils.MAXVALS['int']
|
return qtutils.MAXVALS['int']
|
||||||
|
|
@ -798,7 +795,7 @@ class _Numeric(BaseType): # pylint: disable=abstract-method
|
||||||
return bound
|
return bound
|
||||||
|
|
||||||
def _validate_bounds(self,
|
def _validate_bounds(self,
|
||||||
value: typing.Union[int, float, _UnsetNone],
|
value: Union[int, float, _UnsetNone],
|
||||||
suffix: str = '') -> None:
|
suffix: str = '') -> None:
|
||||||
"""Validate self.minval and self.maxval."""
|
"""Validate self.minval and self.maxval."""
|
||||||
if value is None:
|
if value is None:
|
||||||
|
|
@ -814,7 +811,7 @@ class _Numeric(BaseType): # pylint: disable=abstract-method
|
||||||
elif not self.zero_ok and value == 0:
|
elif not self.zero_ok and value == 0:
|
||||||
raise configexc.ValidationError(value, "must not be 0!")
|
raise configexc.ValidationError(value, "must not be 0!")
|
||||||
|
|
||||||
def to_str(self, value: typing.Union[None, int, float]) -> str:
|
def to_str(self, value: Union[None, int, float]) -> str:
|
||||||
if value is None:
|
if value is None:
|
||||||
return ''
|
return ''
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
@ -828,7 +825,7 @@ class Int(_Numeric):
|
||||||
|
|
||||||
"""Base class for an integer setting."""
|
"""Base class for an integer setting."""
|
||||||
|
|
||||||
def from_str(self, value: str) -> typing.Optional[int]:
|
def from_str(self, value: str) -> Optional[int]:
|
||||||
self._basic_str_validation(value)
|
self._basic_str_validation(value)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
@ -840,10 +837,7 @@ class Int(_Numeric):
|
||||||
self.to_py(intval)
|
self.to_py(intval)
|
||||||
return intval
|
return intval
|
||||||
|
|
||||||
def to_py(
|
def to_py(self, value: Union[int, _UnsetNone]) -> Union[int, _UnsetNone]:
|
||||||
self,
|
|
||||||
value: typing.Union[int, _UnsetNone]
|
|
||||||
) -> typing.Union[int, _UnsetNone]:
|
|
||||||
self._basic_py_validation(value, int)
|
self._basic_py_validation(value, int)
|
||||||
self._validate_bounds(value)
|
self._validate_bounds(value)
|
||||||
return value
|
return value
|
||||||
|
|
@ -853,7 +847,7 @@ class Float(_Numeric):
|
||||||
|
|
||||||
"""Base class for a float setting."""
|
"""Base class for a float setting."""
|
||||||
|
|
||||||
def from_str(self, value: str) -> typing.Optional[float]:
|
def from_str(self, value: str) -> Optional[float]:
|
||||||
self._basic_str_validation(value)
|
self._basic_str_validation(value)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
@ -867,8 +861,8 @@ class Float(_Numeric):
|
||||||
|
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: typing.Union[int, float, _UnsetNone],
|
value: Union[int, float, _UnsetNone],
|
||||||
) -> typing.Union[int, float, _UnsetNone]:
|
) -> Union[int, float, _UnsetNone]:
|
||||||
self._basic_py_validation(value, (int, float))
|
self._basic_py_validation(value, (int, float))
|
||||||
self._validate_bounds(value)
|
self._validate_bounds(value)
|
||||||
return value
|
return value
|
||||||
|
|
@ -880,8 +874,8 @@ class Perc(_Numeric):
|
||||||
|
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: typing.Union[float, int, str, _UnsetNone]
|
value: Union[float, int, str, _UnsetNone]
|
||||||
) -> typing.Union[float, int, _UnsetNone]:
|
) -> Union[float, int, _UnsetNone]:
|
||||||
self._basic_py_validation(value, (float, int, str))
|
self._basic_py_validation(value, (float, int, str))
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
@ -898,7 +892,7 @@ class Perc(_Numeric):
|
||||||
self._validate_bounds(value, suffix='%')
|
self._validate_bounds(value, suffix='%')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_str(self, value: typing.Union[None, float, int, str]) -> str:
|
def to_str(self, value: Union[None, float, int, str]) -> str:
|
||||||
if value is None:
|
if value is None:
|
||||||
return ''
|
return ''
|
||||||
elif isinstance(value, str):
|
elif isinstance(value, str):
|
||||||
|
|
@ -929,7 +923,7 @@ class PercOrInt(_Numeric):
|
||||||
raise ValueError("minperc ({}) needs to be <= maxperc "
|
raise ValueError("minperc ({}) needs to be <= maxperc "
|
||||||
"({})!".format(self.minperc, self.maxperc))
|
"({})!".format(self.minperc, self.maxperc))
|
||||||
|
|
||||||
def from_str(self, value: str) -> typing.Union[None, str, int]:
|
def from_str(self, value: str) -> Union[None, str, int]:
|
||||||
self._basic_str_validation(value)
|
self._basic_str_validation(value)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
@ -946,10 +940,7 @@ class PercOrInt(_Numeric):
|
||||||
self.to_py(intval)
|
self.to_py(intval)
|
||||||
return intval
|
return intval
|
||||||
|
|
||||||
def to_py(
|
def to_py(self, value: Union[None, str, int]) -> Union[None, str, int]:
|
||||||
self,
|
|
||||||
value: typing.Union[None, str, int]
|
|
||||||
) -> typing.Union[None, str, int]:
|
|
||||||
"""Expect a value like '42%' as string, or 23 as int."""
|
"""Expect a value like '42%' as string, or 23 as int."""
|
||||||
self._basic_py_validation(value, (int, str))
|
self._basic_py_validation(value, (int, str))
|
||||||
if value is None:
|
if value is None:
|
||||||
|
|
@ -1009,20 +1000,11 @@ class ColorSystem(MappingType):
|
||||||
|
|
||||||
"""The color system to use for color interpolation."""
|
"""The color system to use for color interpolation."""
|
||||||
|
|
||||||
def __init__(self, none_ok: bool = False) -> None:
|
|
||||||
super().__init__(
|
|
||||||
none_ok,
|
|
||||||
valid_values=ValidValues(
|
|
||||||
('rgb', "Interpolate in the RGB color system."),
|
|
||||||
('hsv', "Interpolate in the HSV color system."),
|
|
||||||
('hsl', "Interpolate in the HSL color system."),
|
|
||||||
('none', "Don't show a gradient.")))
|
|
||||||
|
|
||||||
MAPPING = {
|
MAPPING = {
|
||||||
'rgb': QColor.Rgb,
|
'rgb': (QColor.Rgb, "Interpolate in the RGB color system."),
|
||||||
'hsv': QColor.Hsv,
|
'hsv': (QColor.Hsv, "Interpolate in the HSV color system."),
|
||||||
'hsl': QColor.Hsl,
|
'hsl': (QColor.Hsl, "Interpolate in the HSL color system."),
|
||||||
'none': None,
|
'none': (None, "Don't show a gradient."),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1030,19 +1012,13 @@ class IgnoreCase(MappingType):
|
||||||
|
|
||||||
"""Whether to search case insensitively."""
|
"""Whether to search case insensitively."""
|
||||||
|
|
||||||
def __init__(self, none_ok: bool = False) -> None:
|
|
||||||
super().__init__(
|
|
||||||
none_ok,
|
|
||||||
valid_values=ValidValues(
|
|
||||||
('always', "Search case-insensitively."),
|
|
||||||
('never', "Search case-sensitively."),
|
|
||||||
('smart', ("Search case-sensitively if there are capital "
|
|
||||||
"characters."))))
|
|
||||||
|
|
||||||
MAPPING = {
|
MAPPING = {
|
||||||
'always': usertypes.IgnoreCase.always,
|
'always': (usertypes.IgnoreCase.always, "Search case-insensitively."),
|
||||||
'never': usertypes.IgnoreCase.never,
|
'never': (usertypes.IgnoreCase.never, "Search case-sensitively."),
|
||||||
'smart': usertypes.IgnoreCase.smart,
|
'smart': (
|
||||||
|
usertypes.IgnoreCase.smart,
|
||||||
|
"Search case-sensitively if there are capital characters."
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1076,7 +1052,7 @@ class QtColor(BaseType):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise configexc.ValidationError(val, "must be a valid color value")
|
raise configexc.ValidationError(val, "must be a valid color value")
|
||||||
|
|
||||||
def to_py(self, value: _StrUnset) -> typing.Union[_UnsetNone, QColor]:
|
def to_py(self, value: _StrUnset) -> Union[_UnsetNone, QColor]:
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
@ -1088,12 +1064,12 @@ class QtColor(BaseType):
|
||||||
kind = value[:openparen]
|
kind = value[:openparen]
|
||||||
vals = value[openparen+1:-1].split(',')
|
vals = value[openparen+1:-1].split(',')
|
||||||
|
|
||||||
converters = {
|
converters: DictType[str, Callable[..., QColor]] = {
|
||||||
'rgba': QColor.fromRgb,
|
'rgba': QColor.fromRgb,
|
||||||
'rgb': QColor.fromRgb,
|
'rgb': QColor.fromRgb,
|
||||||
'hsva': QColor.fromHsv,
|
'hsva': QColor.fromHsv,
|
||||||
'hsv': QColor.fromHsv,
|
'hsv': QColor.fromHsv,
|
||||||
} # type: typing.Dict[str, typing.Callable[..., QColor]]
|
}
|
||||||
|
|
||||||
conv = converters.get(kind)
|
conv = converters.get(kind)
|
||||||
if not conv:
|
if not conv:
|
||||||
|
|
@ -1159,8 +1135,8 @@ class FontBase(BaseType):
|
||||||
"""Base class for Font/FontFamily."""
|
"""Base class for Font/FontFamily."""
|
||||||
|
|
||||||
# Gets set when the config is initialized.
|
# Gets set when the config is initialized.
|
||||||
default_family = None # type: str
|
default_family: Optional[str] = None
|
||||||
default_size = None # type: str
|
default_size: Optional[str] = None
|
||||||
font_regex = re.compile(r"""
|
font_regex = re.compile(r"""
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
|
|
@ -1178,61 +1154,21 @@ class FontBase(BaseType):
|
||||||
(?P<family>.+) # mandatory font family""", re.VERBOSE)
|
(?P<family>.+) # mandatory font family""", re.VERBOSE)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_defaults(cls, default_family: typing.List[str],
|
def set_defaults(cls, default_family: ListType[str], default_size: str) -> None:
|
||||||
default_size: str) -> None:
|
|
||||||
"""Make sure default_family/default_size are available.
|
"""Make sure default_family/default_size are available.
|
||||||
|
|
||||||
If the given family value (fonts.default_family in the config) is
|
If the given family value (fonts.default_family in the config) is
|
||||||
unset, a system-specific default monospace font is used.
|
unset, a system-specific default monospace font is used.
|
||||||
|
|
||||||
Note that (at least) three ways of getting the default monospace font
|
|
||||||
exist:
|
|
||||||
|
|
||||||
1) f = QFont()
|
|
||||||
f.setStyleHint(QFont.Monospace)
|
|
||||||
print(f.defaultFamily())
|
|
||||||
|
|
||||||
2) f = QFont()
|
|
||||||
f.setStyleHint(QFont.TypeWriter)
|
|
||||||
print(f.defaultFamily())
|
|
||||||
|
|
||||||
3) f = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
|
||||||
print(f.family())
|
|
||||||
|
|
||||||
They yield different results depending on the OS:
|
|
||||||
|
|
||||||
QFont.Monospace | QFont.TypeWriter | QFontDatabase
|
|
||||||
------------------------------------------------------
|
|
||||||
Windows: Courier New | Courier New | Courier New
|
|
||||||
Linux: DejaVu Sans Mono | DejaVu Sans Mono | monospace
|
|
||||||
macOS: Menlo | American Typewriter | Monaco
|
|
||||||
|
|
||||||
Test script: https://p.cmpl.cc/d4dfe573
|
|
||||||
|
|
||||||
On Linux, it seems like both actually resolve to the same font.
|
|
||||||
|
|
||||||
On macOS, "American Typewriter" looks like it indeed tries to imitate a
|
|
||||||
typewriter, so it's not really a suitable UI font.
|
|
||||||
|
|
||||||
Looking at those Wikipedia articles:
|
|
||||||
|
|
||||||
https://en.wikipedia.org/wiki/Monaco_(typeface)
|
|
||||||
https://en.wikipedia.org/wiki/Menlo_(typeface)
|
|
||||||
|
|
||||||
the "right" choice isn't really obvious. Thus, let's go for the
|
|
||||||
QFontDatabase approach here, since it's by far the simplest one.
|
|
||||||
"""
|
"""
|
||||||
if default_family:
|
if default_family:
|
||||||
families = configutils.FontFamilies(default_family)
|
families = configutils.FontFamilies(default_family)
|
||||||
else:
|
else:
|
||||||
assert QApplication.instance() is not None
|
families = configutils.FontFamilies.from_system_default()
|
||||||
font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
|
||||||
families = configutils.FontFamilies([font.family()])
|
|
||||||
|
|
||||||
cls.default_family = families.to_str(quote=True)
|
cls.default_family = families.to_str(quote=True)
|
||||||
cls.default_size = default_size
|
cls.default_size = default_size
|
||||||
|
|
||||||
def to_py(self, value: typing.Any) -> typing.Any:
|
def to_py(self, value: Any) -> Any:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1315,7 +1251,7 @@ class Regex(BaseType):
|
||||||
operator.or_,
|
operator.or_,
|
||||||
(getattr(re, flag.strip()) for flag in flags.split(' | ')))
|
(getattr(re, flag.strip()) for flag in flags.split(' | ')))
|
||||||
|
|
||||||
def _compile_regex(self, pattern: str) -> typing.Pattern[str]:
|
def _compile_regex(self, pattern: str) -> Pattern[str]:
|
||||||
"""Check if the given regex is valid.
|
"""Check if the given regex is valid.
|
||||||
|
|
||||||
Some semi-invalid regexes can also raise warnings - we also treat them as
|
Some semi-invalid regexes can also raise warnings - we also treat them as
|
||||||
|
|
@ -1336,8 +1272,8 @@ class Regex(BaseType):
|
||||||
|
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: typing.Union[str, typing.Pattern[str], usertypes.Unset]
|
value: Union[str, Pattern[str], usertypes.Unset]
|
||||||
) -> typing.Union[_UnsetNone, typing.Pattern[str]]:
|
) -> Union[_UnsetNone, Pattern[str]]:
|
||||||
"""Get a compiled regex from either a string or a regex object."""
|
"""Get a compiled regex from either a string or a regex object."""
|
||||||
self._basic_py_validation(value, (str, self._regex_type))
|
self._basic_py_validation(value, (str, self._regex_type))
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
|
|
@ -1349,8 +1285,7 @@ class Regex(BaseType):
|
||||||
else:
|
else:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_str(self,
|
def to_str(self, value: Union[None, str, Pattern[str]]) -> str:
|
||||||
value: typing.Union[None, str, typing.Pattern[str]]) -> str:
|
|
||||||
if value is None:
|
if value is None:
|
||||||
return ''
|
return ''
|
||||||
elif isinstance(value, self._regex_type):
|
elif isinstance(value, self._regex_type):
|
||||||
|
|
@ -1370,10 +1305,10 @@ class Dict(BaseType):
|
||||||
When setting from a string, pass a json-like dict, e.g. `{"key", "value"}`.
|
When setting from a string, pass a json-like dict, e.g. `{"key", "value"}`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, keytype: typing.Union[String, 'Key'],
|
def __init__(self, keytype: Union[String, 'Key'],
|
||||||
valtype: BaseType, *,
|
valtype: BaseType, *,
|
||||||
fixed_keys: typing.Iterable = None,
|
fixed_keys: Iterable = None,
|
||||||
required_keys: typing.Iterable = None,
|
required_keys: Iterable = None,
|
||||||
none_ok: bool = False) -> None:
|
none_ok: bool = False) -> None:
|
||||||
super().__init__(none_ok)
|
super().__init__(none_ok)
|
||||||
# If the keytype is not a string, we'll get problems with showing it as
|
# If the keytype is not a string, we'll get problems with showing it as
|
||||||
|
|
@ -1384,7 +1319,7 @@ class Dict(BaseType):
|
||||||
self.fixed_keys = fixed_keys
|
self.fixed_keys = fixed_keys
|
||||||
self.required_keys = required_keys
|
self.required_keys = required_keys
|
||||||
|
|
||||||
def _validate_keys(self, value: typing.Dict) -> None:
|
def _validate_keys(self, value: DictType) -> None:
|
||||||
if (self.fixed_keys is not None and not
|
if (self.fixed_keys is not None and not
|
||||||
set(value.keys()).issubset(self.fixed_keys)):
|
set(value.keys()).issubset(self.fixed_keys)):
|
||||||
raise configexc.ValidationError(
|
raise configexc.ValidationError(
|
||||||
|
|
@ -1395,7 +1330,7 @@ class Dict(BaseType):
|
||||||
raise configexc.ValidationError(
|
raise configexc.ValidationError(
|
||||||
value, "Required keys {}".format(self.required_keys))
|
value, "Required keys {}".format(self.required_keys))
|
||||||
|
|
||||||
def from_str(self, value: str) -> typing.Optional[typing.Dict]:
|
def from_str(self, value: str) -> Optional[DictType]:
|
||||||
self._basic_str_validation(value)
|
self._basic_str_validation(value)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
@ -1410,14 +1345,14 @@ class Dict(BaseType):
|
||||||
self.to_py(yaml_val)
|
self.to_py(yaml_val)
|
||||||
return yaml_val
|
return yaml_val
|
||||||
|
|
||||||
def from_obj(self, value: typing.Optional[typing.Dict]) -> typing.Dict:
|
def from_obj(self, value: Optional[DictType]) -> DictType:
|
||||||
if value is None:
|
if value is None:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
return {self.keytype.from_obj(key): self.valtype.from_obj(val)
|
return {self.keytype.from_obj(key): self.valtype.from_obj(val)
|
||||||
for key, val in value.items()}
|
for key, val in value.items()}
|
||||||
|
|
||||||
def _fill_fixed_keys(self, value: typing.Dict) -> typing.Dict:
|
def _fill_fixed_keys(self, value: DictType) -> DictType:
|
||||||
"""Fill missing fixed keys with a None-value."""
|
"""Fill missing fixed keys with a None-value."""
|
||||||
if self.fixed_keys is None:
|
if self.fixed_keys is None:
|
||||||
return value
|
return value
|
||||||
|
|
@ -1428,8 +1363,8 @@ class Dict(BaseType):
|
||||||
|
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: typing.Union[typing.Dict, _UnsetNone]
|
value: Union[DictType, _UnsetNone]
|
||||||
) -> typing.Union[typing.Dict, usertypes.Unset]:
|
) -> Union[DictType, usertypes.Unset]:
|
||||||
self._basic_py_validation(value, dict)
|
self._basic_py_validation(value, dict)
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
@ -1445,13 +1380,13 @@ class Dict(BaseType):
|
||||||
for key, val in value.items()}
|
for key, val in value.items()}
|
||||||
return self._fill_fixed_keys(d)
|
return self._fill_fixed_keys(d)
|
||||||
|
|
||||||
def to_str(self, value: typing.Dict) -> str:
|
def to_str(self, value: DictType) -> str:
|
||||||
if not value:
|
if not value:
|
||||||
# An empty Dict is treated just like None -> empty string
|
# An empty Dict is treated just like None -> empty string
|
||||||
return ''
|
return ''
|
||||||
return json.dumps(value, sort_keys=True)
|
return json.dumps(value, sort_keys=True)
|
||||||
|
|
||||||
def to_doc(self, value: typing.Dict, indent: int = 0) -> str:
|
def to_doc(self, value: DictType, indent: int = 0) -> str:
|
||||||
if not value:
|
if not value:
|
||||||
return 'empty'
|
return 'empty'
|
||||||
lines = ['\n']
|
lines = ['\n']
|
||||||
|
|
@ -1474,7 +1409,7 @@ class File(BaseType):
|
||||||
|
|
||||||
"""A file on the local filesystem."""
|
"""A file on the local filesystem."""
|
||||||
|
|
||||||
def __init__(self, required: bool = True, **kwargs: typing.Any) -> None:
|
def __init__(self, required: bool = True, **kwargs: Any) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.required = required
|
self.required = required
|
||||||
|
|
||||||
|
|
@ -1540,7 +1475,7 @@ class FormatString(BaseType):
|
||||||
completions: completions to be used, or None
|
completions: completions to be used, or None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fields: typing.Iterable[str],
|
def __init__(self, fields: Iterable[str],
|
||||||
none_ok: bool = False,
|
none_ok: bool = False,
|
||||||
completions: _Completions = None) -> None:
|
completions: _Completions = None) -> None:
|
||||||
super().__init__(none_ok)
|
super().__init__(none_ok)
|
||||||
|
|
@ -1593,8 +1528,8 @@ class ShellCommand(List):
|
||||||
|
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: typing.Union[typing.List, usertypes.Unset],
|
value: Union[ListType, usertypes.Unset],
|
||||||
) -> typing.Union[typing.List, usertypes.Unset]:
|
) -> Union[ListType, usertypes.Unset]:
|
||||||
py_value = super().to_py(value)
|
py_value = super().to_py(value)
|
||||||
if isinstance(py_value, usertypes.Unset):
|
if isinstance(py_value, usertypes.Unset):
|
||||||
return py_value
|
return py_value
|
||||||
|
|
@ -1627,7 +1562,7 @@ class Proxy(BaseType):
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: _StrUnset
|
value: _StrUnset
|
||||||
) -> typing.Union[_UnsetNone, QNetworkProxy, _SystemProxy, pac.PACFetcher]:
|
) -> Union[_UnsetNone, QNetworkProxy, _SystemProxy, pac.PACFetcher]:
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
@ -1697,7 +1632,7 @@ class FuzzyUrl(BaseType):
|
||||||
|
|
||||||
"""A URL which gets interpreted as search if needed."""
|
"""A URL which gets interpreted as search if needed."""
|
||||||
|
|
||||||
def to_py(self, value: _StrUnset) -> typing.Union[QUrl, _UnsetNone]:
|
def to_py(self, value: _StrUnset) -> Union[QUrl, _UnsetNone]:
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
@ -1715,10 +1650,10 @@ class PaddingValues:
|
||||||
|
|
||||||
"""Four padding values."""
|
"""Four padding values."""
|
||||||
|
|
||||||
top = attr.ib() # type: int
|
top: int = attr.ib()
|
||||||
bottom = attr.ib() # type: int
|
bottom: int = attr.ib()
|
||||||
left = attr.ib() # type: int
|
left: int = attr.ib()
|
||||||
right = attr.ib() # type: int
|
right: int = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
class Padding(Dict):
|
class Padding(Dict):
|
||||||
|
|
@ -1735,8 +1670,8 @@ class Padding(Dict):
|
||||||
|
|
||||||
def to_py( # type: ignore[override]
|
def to_py( # type: ignore[override]
|
||||||
self,
|
self,
|
||||||
value: typing.Union[typing.Dict, _UnsetNone],
|
value: Union[DictType, _UnsetNone],
|
||||||
) -> typing.Union[usertypes.Unset, PaddingValues]:
|
) -> Union[usertypes.Unset, PaddingValues]:
|
||||||
d = super().to_py(value)
|
d = super().to_py(value)
|
||||||
if isinstance(d, usertypes.Unset):
|
if isinstance(d, usertypes.Unset):
|
||||||
return d
|
return d
|
||||||
|
|
@ -1766,33 +1701,23 @@ class Position(MappingType):
|
||||||
"""The position of the tab bar."""
|
"""The position of the tab bar."""
|
||||||
|
|
||||||
MAPPING = {
|
MAPPING = {
|
||||||
'top': QTabWidget.North,
|
'top': (QTabWidget.North, None),
|
||||||
'bottom': QTabWidget.South,
|
'bottom': (QTabWidget.South, None),
|
||||||
'left': QTabWidget.West,
|
'left': (QTabWidget.West, None),
|
||||||
'right': QTabWidget.East,
|
'right': (QTabWidget.East, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, none_ok: bool = False) -> None:
|
|
||||||
super().__init__(
|
|
||||||
none_ok,
|
|
||||||
valid_values=ValidValues('top', 'bottom', 'left', 'right'))
|
|
||||||
|
|
||||||
|
|
||||||
class TextAlignment(MappingType):
|
class TextAlignment(MappingType):
|
||||||
|
|
||||||
"""Alignment of text."""
|
"""Alignment of text."""
|
||||||
|
|
||||||
MAPPING = {
|
MAPPING = {
|
||||||
'left': Qt.AlignLeft,
|
'left': (Qt.AlignLeft, None),
|
||||||
'right': Qt.AlignRight,
|
'right': (Qt.AlignRight, None),
|
||||||
'center': Qt.AlignCenter,
|
'center': (Qt.AlignCenter, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, none_ok: bool = False) -> None:
|
|
||||||
super().__init__(
|
|
||||||
none_ok,
|
|
||||||
valid_values=ValidValues('left', 'right', 'center'))
|
|
||||||
|
|
||||||
|
|
||||||
class VerticalPosition(String):
|
class VerticalPosition(String):
|
||||||
|
|
||||||
|
|
@ -1807,7 +1732,7 @@ class Url(BaseType):
|
||||||
|
|
||||||
"""A URL as a string."""
|
"""A URL as a string."""
|
||||||
|
|
||||||
def to_py(self, value: _StrUnset) -> typing.Union[_UnsetNone, QUrl]:
|
def to_py(self, value: _StrUnset) -> Union[_UnsetNone, QUrl]:
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
@ -1841,21 +1766,22 @@ class SelectOnRemove(MappingType):
|
||||||
"""Which tab to select when the focused tab is removed."""
|
"""Which tab to select when the focused tab is removed."""
|
||||||
|
|
||||||
MAPPING = {
|
MAPPING = {
|
||||||
'prev': QTabBar.SelectLeftTab,
|
'prev': (
|
||||||
'next': QTabBar.SelectRightTab,
|
QTabBar.SelectLeftTab,
|
||||||
'last-used': QTabBar.SelectPreviousTab,
|
("Select the tab which came before the closed one "
|
||||||
|
"(left in horizontal, above in vertical)."),
|
||||||
|
),
|
||||||
|
'next': (
|
||||||
|
QTabBar.SelectRightTab,
|
||||||
|
("Select the tab which came after the closed one "
|
||||||
|
"(right in horizontal, below in vertical)."),
|
||||||
|
),
|
||||||
|
'last-used': (
|
||||||
|
QTabBar.SelectPreviousTab,
|
||||||
|
"Select the previously selected tab.",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, none_ok: bool = False) -> None:
|
|
||||||
super().__init__(
|
|
||||||
none_ok,
|
|
||||||
valid_values=ValidValues(
|
|
||||||
('prev', "Select the tab which came before the closed one "
|
|
||||||
"(left in horizontal, above in vertical)."),
|
|
||||||
('next', "Select the tab which came after the closed one "
|
|
||||||
"(right in horizontal, below in vertical)."),
|
|
||||||
('last-used', "Select the previously selected tab.")))
|
|
||||||
|
|
||||||
|
|
||||||
class ConfirmQuit(FlagList):
|
class ConfirmQuit(FlagList):
|
||||||
|
|
||||||
|
|
@ -1877,8 +1803,8 @@ class ConfirmQuit(FlagList):
|
||||||
|
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: typing.Union[usertypes.Unset, typing.List],
|
value: Union[usertypes.Unset, ListType],
|
||||||
) -> typing.Union[typing.List, usertypes.Unset]:
|
) -> Union[ListType, usertypes.Unset]:
|
||||||
values = super().to_py(value)
|
values = super().to_py(value)
|
||||||
if isinstance(values, usertypes.Unset):
|
if isinstance(values, usertypes.Unset):
|
||||||
return values
|
return values
|
||||||
|
|
@ -1931,7 +1857,7 @@ class Key(BaseType):
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: _StrUnset
|
value: _StrUnset
|
||||||
) -> typing.Union[_UnsetNone, keyutils.KeySequence]:
|
) -> Union[_UnsetNone, keyutils.KeySequence]:
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
@ -1955,7 +1881,7 @@ class UrlPattern(BaseType):
|
||||||
def to_py(
|
def to_py(
|
||||||
self,
|
self,
|
||||||
value: _StrUnset
|
value: _StrUnset
|
||||||
) -> typing.Union[_UnsetNone, urlmatch.UrlPattern]:
|
) -> Union[_UnsetNone, urlmatch.UrlPattern]:
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if isinstance(value, usertypes.Unset):
|
if isinstance(value, usertypes.Unset):
|
||||||
return value
|
return value
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,25 @@
|
||||||
"""Utilities and data structures used by various config code."""
|
"""Utilities and data structures used by various config code."""
|
||||||
|
|
||||||
|
|
||||||
import typing
|
|
||||||
import collections
|
import collections
|
||||||
import itertools
|
import itertools
|
||||||
import operator
|
import operator
|
||||||
|
from typing import (
|
||||||
|
TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Union,
|
||||||
|
MutableMapping)
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl
|
from PyQt5.QtCore import QUrl
|
||||||
|
from PyQt5.QtGui import QFontDatabase
|
||||||
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from qutebrowser.utils import utils, urlmatch, usertypes, qtutils
|
from qutebrowser.utils import utils, urlmatch, usertypes, qtutils
|
||||||
from qutebrowser.config import configexc
|
from qutebrowser.config import configexc
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from qutebrowser.config import configdata
|
from qutebrowser.config import configdata
|
||||||
|
|
||||||
|
|
||||||
def _widened_hostnames(hostname: str) -> typing.Iterable[str]:
|
def _widened_hostnames(hostname: str) -> Iterable[str]:
|
||||||
"""A generator for widening string hostnames.
|
"""A generator for widening string hostnames.
|
||||||
|
|
||||||
Ex: a.c.foo -> [a.c.foo, c.foo, foo]"""
|
Ex: a.c.foo -> [a.c.foo, c.foo, foo]"""
|
||||||
|
|
@ -56,8 +60,8 @@ class ScopedValue:
|
||||||
|
|
||||||
id_gen = itertools.count(0)
|
id_gen = itertools.count(0)
|
||||||
|
|
||||||
def __init__(self, value: typing.Any,
|
def __init__(self, value: Any,
|
||||||
pattern: typing.Optional[urlmatch.UrlPattern],
|
pattern: Optional[urlmatch.UrlPattern],
|
||||||
hide_userconfig: bool = False) -> None:
|
hide_userconfig: bool = False) -> None:
|
||||||
self.value = value
|
self.value = value
|
||||||
self.pattern = pattern
|
self.pattern = pattern
|
||||||
|
|
@ -90,17 +94,17 @@ class Values:
|
||||||
_domain_map: A mapping from hostnames to all associated ScopedValues.
|
_domain_map: A mapping from hostnames to all associated ScopedValues.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_VmapKeyType = typing.Optional[urlmatch.UrlPattern]
|
_VmapKeyType = Optional[urlmatch.UrlPattern]
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
opt: 'configdata.Option',
|
opt: 'configdata.Option',
|
||||||
values: typing.Sequence[ScopedValue] = ()) -> None:
|
values: Sequence[ScopedValue] = ()) -> None:
|
||||||
self.opt = opt
|
self.opt = opt
|
||||||
self._vmap = collections.OrderedDict() \
|
self._vmap: MutableMapping[
|
||||||
# type: collections.OrderedDict[Values._VmapKeyType, ScopedValue]
|
Values._VmapKeyType, ScopedValue] = collections.OrderedDict()
|
||||||
# A map from domain parts to rules that fall under them.
|
# A map from domain parts to rules that fall under them.
|
||||||
self._domain_map = collections.defaultdict(set) \
|
self._domain_map: Dict[
|
||||||
# type: typing.Dict[typing.Optional[str], typing.Set[ScopedValue]]
|
Optional[str], Set[ScopedValue]] = collections.defaultdict(set)
|
||||||
|
|
||||||
for scoped in values:
|
for scoped in values:
|
||||||
self._add_scoped(scoped)
|
self._add_scoped(scoped)
|
||||||
|
|
@ -117,7 +121,7 @@ class Values:
|
||||||
return '\n'.join(lines)
|
return '\n'.join(lines)
|
||||||
return '{}: <unchanged>'.format(self.opt.name)
|
return '{}: <unchanged>'.format(self.opt.name)
|
||||||
|
|
||||||
def dump(self, include_hidden: bool = False) -> typing.Sequence[str]:
|
def dump(self, include_hidden: bool = False) -> Sequence[str]:
|
||||||
"""Dump all customizations for this value.
|
"""Dump all customizations for this value.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
@ -138,7 +142,7 @@ class Values:
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def __iter__(self) -> typing.Iterator['ScopedValue']:
|
def __iter__(self) -> Iterator['ScopedValue']:
|
||||||
"""Yield ScopedValue elements.
|
"""Yield ScopedValue elements.
|
||||||
|
|
||||||
This yields in "normal" order, i.e. global and then first-set settings
|
This yields in "normal" order, i.e. global and then first-set settings
|
||||||
|
|
@ -151,12 +155,12 @@ class Values:
|
||||||
return bool(self._vmap)
|
return bool(self._vmap)
|
||||||
|
|
||||||
def _check_pattern_support(
|
def _check_pattern_support(
|
||||||
self, arg: typing.Union[urlmatch.UrlPattern, QUrl, None]) -> None:
|
self, arg: Union[urlmatch.UrlPattern, QUrl, None]) -> None:
|
||||||
"""Make sure patterns are supported if one was given."""
|
"""Make sure patterns are supported if one was given."""
|
||||||
if arg is not None and not self.opt.supports_pattern:
|
if arg is not None and not self.opt.supports_pattern:
|
||||||
raise configexc.NoPatternError(self.opt.name)
|
raise configexc.NoPatternError(self.opt.name)
|
||||||
|
|
||||||
def add(self, value: typing.Any,
|
def add(self, value: Any,
|
||||||
pattern: urlmatch.UrlPattern = None, *,
|
pattern: urlmatch.UrlPattern = None, *,
|
||||||
hide_userconfig: bool = False) -> None:
|
hide_userconfig: bool = False) -> None:
|
||||||
"""Add a value with the given pattern to the list of values.
|
"""Add a value with the given pattern to the list of values.
|
||||||
|
|
@ -201,7 +205,7 @@ class Values:
|
||||||
self._vmap.clear()
|
self._vmap.clear()
|
||||||
self._domain_map.clear()
|
self._domain_map.clear()
|
||||||
|
|
||||||
def _get_fallback(self, fallback: bool) -> typing.Any:
|
def _get_fallback(self, fallback: bool) -> Any:
|
||||||
"""Get the fallback global/default value."""
|
"""Get the fallback global/default value."""
|
||||||
if None in self._vmap:
|
if None in self._vmap:
|
||||||
return self._vmap[None].value
|
return self._vmap[None].value
|
||||||
|
|
@ -211,8 +215,7 @@ class Values:
|
||||||
else:
|
else:
|
||||||
return usertypes.UNSET
|
return usertypes.UNSET
|
||||||
|
|
||||||
def get_for_url(self, url: QUrl = None, *,
|
def get_for_url(self, url: QUrl = None, *, fallback: bool = True) -> Any:
|
||||||
fallback: bool = True) -> typing.Any:
|
|
||||||
"""Get a config value, falling back when needed.
|
"""Get a config value, falling back when needed.
|
||||||
|
|
||||||
This first tries to find a value matching the URL (if given).
|
This first tries to find a value matching the URL (if given).
|
||||||
|
|
@ -225,7 +228,7 @@ class Values:
|
||||||
return self._get_fallback(fallback)
|
return self._get_fallback(fallback)
|
||||||
qtutils.ensure_valid(url)
|
qtutils.ensure_valid(url)
|
||||||
|
|
||||||
candidates = [] # type: typing.List[ScopedValue]
|
candidates: List[ScopedValue] = []
|
||||||
# Urls trailing with '.' are equivalent to non-trailing types.
|
# Urls trailing with '.' are equivalent to non-trailing types.
|
||||||
# urlutils strips them, so in order to match we will need to as well.
|
# urlutils strips them, so in order to match we will need to as well.
|
||||||
widened_hosts = _widened_hostnames(url.host().rstrip('.'))
|
widened_hosts = _widened_hostnames(url.host().rstrip('.'))
|
||||||
|
|
@ -247,8 +250,8 @@ class Values:
|
||||||
return self._get_fallback(fallback)
|
return self._get_fallback(fallback)
|
||||||
|
|
||||||
def get_for_pattern(self,
|
def get_for_pattern(self,
|
||||||
pattern: typing.Optional[urlmatch.UrlPattern], *,
|
pattern: Optional[urlmatch.UrlPattern], *,
|
||||||
fallback: bool = True) -> typing.Any:
|
fallback: bool = True) -> Any:
|
||||||
"""Get a value only if it's been overridden for the given pattern.
|
"""Get a value only if it's been overridden for the given pattern.
|
||||||
|
|
||||||
This is useful when showing values to the user.
|
This is useful when showing values to the user.
|
||||||
|
|
@ -272,28 +275,82 @@ class FontFamilies:
|
||||||
|
|
||||||
"""A list of font family names."""
|
"""A list of font family names."""
|
||||||
|
|
||||||
def __init__(self, families: typing.Sequence[str]) -> None:
|
def __init__(self, families: Sequence[str]) -> None:
|
||||||
self._families = families
|
self._families = families
|
||||||
self.family = families[0] if families else None
|
self.family = families[0] if families else None
|
||||||
|
|
||||||
def __iter__(self) -> typing.Iterator[str]:
|
def __iter__(self) -> Iterator[str]:
|
||||||
yield from self._families
|
yield from self._families
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self._families)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return utils.get_repr(self, families=self._families, constructor=True)
|
return utils.get_repr(self, families=self._families, constructor=True)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.to_str()
|
return self.to_str()
|
||||||
|
|
||||||
def _quoted_families(self) -> typing.Iterator[str]:
|
def _quoted_families(self) -> Iterator[str]:
|
||||||
for f in self._families:
|
for f in self._families:
|
||||||
needs_quoting = any(c in f for c in ', ')
|
needs_quoting = any(c in f for c in '., ')
|
||||||
yield '"{}"'.format(f) if needs_quoting else f
|
yield '"{}"'.format(f) if needs_quoting else f
|
||||||
|
|
||||||
def to_str(self, *, quote: bool = True) -> str:
|
def to_str(self, *, quote: bool = True) -> str:
|
||||||
families = self._quoted_families() if quote else self._families
|
families = self._quoted_families() if quote else self._families
|
||||||
return ', '.join(families)
|
return ', '.join(families)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_system_default(
|
||||||
|
cls,
|
||||||
|
font_type: QFontDatabase.SystemFont = QFontDatabase.FixedFont,
|
||||||
|
) -> 'FontFamilies':
|
||||||
|
"""Get a FontFamilies object for the default system font.
|
||||||
|
|
||||||
|
By default, the monospace font is returned, though via the "font_type" argument,
|
||||||
|
other types can be requested as well.
|
||||||
|
|
||||||
|
Note that (at least) three ways of getting the default monospace font
|
||||||
|
exist:
|
||||||
|
|
||||||
|
1) f = QFont()
|
||||||
|
f.setStyleHint(QFont.Monospace)
|
||||||
|
print(f.defaultFamily())
|
||||||
|
|
||||||
|
2) f = QFont()
|
||||||
|
f.setStyleHint(QFont.TypeWriter)
|
||||||
|
print(f.defaultFamily())
|
||||||
|
|
||||||
|
3) f = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
||||||
|
print(f.family())
|
||||||
|
|
||||||
|
They yield different results depending on the OS:
|
||||||
|
|
||||||
|
QFont.Monospace | QFont.TypeWriter | QFontDatabase
|
||||||
|
------------------------------------------------------
|
||||||
|
Windows: Courier New | Courier New | Courier New
|
||||||
|
Linux: DejaVu Sans Mono | DejaVu Sans Mono | monospace
|
||||||
|
macOS: Menlo | American Typewriter | Monaco
|
||||||
|
|
||||||
|
Test script: https://p.cmpl.cc/d4dfe573
|
||||||
|
|
||||||
|
On Linux, it seems like both actually resolve to the same font.
|
||||||
|
|
||||||
|
On macOS, "American Typewriter" looks like it indeed tries to imitate a
|
||||||
|
typewriter, so it's not really a suitable UI font.
|
||||||
|
|
||||||
|
Looking at those Wikipedia articles:
|
||||||
|
|
||||||
|
https://en.wikipedia.org/wiki/Monaco_(typeface)
|
||||||
|
https://en.wikipedia.org/wiki/Menlo_(typeface)
|
||||||
|
|
||||||
|
the "right" choice isn't really obvious. Thus, let's go for the
|
||||||
|
QFontDatabase approach here, since it's by far the simplest one.
|
||||||
|
"""
|
||||||
|
assert QApplication.instance() is not None
|
||||||
|
font = QFontDatabase.systemFont(font_type)
|
||||||
|
return cls([font.family()])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_str(cls, family_str: str) -> 'FontFamilies':
|
def from_str(cls, family_str: str) -> 'FontFamilies':
|
||||||
"""Parse a CSS-like string of font families."""
|
"""Parse a CSS-like string of font families."""
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,15 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import typing
|
|
||||||
import argparse
|
import argparse
|
||||||
|
from typing import Any, Dict, Iterator, List, Optional, Sequence
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
from qutebrowser.utils import usertypes, qtutils, utils
|
from qutebrowser.utils import usertypes, qtutils, utils
|
||||||
|
|
||||||
|
|
||||||
def qt_args(namespace: argparse.Namespace) -> typing.List[str]:
|
def qt_args(namespace: argparse.Namespace) -> List[str]:
|
||||||
"""Get the Qt QApplication arguments based on an argparse namespace.
|
"""Get the Qt QApplication arguments based on an argparse namespace.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -61,9 +61,7 @@ def qt_args(namespace: argparse.Namespace) -> typing.List[str]:
|
||||||
return argv
|
return argv
|
||||||
|
|
||||||
|
|
||||||
def _qtwebengine_enabled_features(
|
def _qtwebengine_enabled_features(feature_flags: Sequence[str]) -> Iterator[str]:
|
||||||
feature_flags: typing.Sequence[str],
|
|
||||||
) -> typing.Iterator[str]:
|
|
||||||
"""Get --enable-features flags for QtWebEngine.
|
"""Get --enable-features flags for QtWebEngine.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -95,7 +93,7 @@ def _qtwebengine_enabled_features(
|
||||||
# just turn it on unconditionally on Linux, which shouldn't hurt.
|
# just turn it on unconditionally on Linux, which shouldn't hurt.
|
||||||
yield 'WebRTCPipeWireCapturer'
|
yield 'WebRTCPipeWireCapturer'
|
||||||
|
|
||||||
if qtutils.version_check('5.11', compiled=False) and not utils.is_mac:
|
if not utils.is_mac:
|
||||||
# Enable overlay scrollbars.
|
# Enable overlay scrollbars.
|
||||||
#
|
#
|
||||||
# There are two additional flags in Chromium:
|
# There are two additional flags in Chromium:
|
||||||
|
|
@ -111,22 +109,27 @@ def _qtwebengine_enabled_features(
|
||||||
if config.val.scrolling.bar == 'overlay':
|
if config.val.scrolling.bar == 'overlay':
|
||||||
yield 'OverlayScrollbar'
|
yield 'OverlayScrollbar'
|
||||||
|
|
||||||
|
if (qtutils.version_check('5.14', compiled=False) and
|
||||||
|
config.val.content.headers.referer == 'same-domain'):
|
||||||
|
# Handling of reduced-referrer-granularity in Chromium 76+
|
||||||
|
# https://chromium-review.googlesource.com/c/chromium/src/+/1572699
|
||||||
|
#
|
||||||
|
# Note that this is removed entirely (and apparently the default) starting with
|
||||||
|
# Chromium 89 (Qt 5.15.x or 6.x):
|
||||||
|
# https://chromium-review.googlesource.com/c/chromium/src/+/2545444
|
||||||
|
yield 'ReducedReferrerGranularity'
|
||||||
|
|
||||||
|
|
||||||
def _qtwebengine_args(
|
def _qtwebengine_args(
|
||||||
namespace: argparse.Namespace,
|
namespace: argparse.Namespace,
|
||||||
feature_flags: typing.Sequence[str],
|
feature_flags: Sequence[str],
|
||||||
) -> typing.Iterator[str]:
|
) -> Iterator[str]:
|
||||||
"""Get the QtWebEngine arguments to use based on the config."""
|
"""Get the QtWebEngine arguments to use based on the config."""
|
||||||
is_qt_514 = (qtutils.version_check('5.14', compiled=False) and
|
is_qt_514 = (qtutils.version_check('5.14', compiled=False) and
|
||||||
not qtutils.version_check('5.15', compiled=False))
|
not qtutils.version_check('5.15', compiled=False))
|
||||||
|
|
||||||
if not qtutils.version_check('5.11', compiled=False) or is_qt_514:
|
if is_qt_514:
|
||||||
# WORKAROUND equivalent to
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-82105
|
||||||
# https://codereview.qt-project.org/#/c/217932/
|
|
||||||
# Needed for Qt < 5.9.5 and < 5.10.1
|
|
||||||
#
|
|
||||||
# For Qt 5,14, WORKAROUND for
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-82105
|
|
||||||
yield '--disable-shared-workers'
|
yield '--disable-shared-workers'
|
||||||
|
|
||||||
# WORKAROUND equivalent to
|
# WORKAROUND equivalent to
|
||||||
|
|
@ -152,8 +155,7 @@ def _qtwebengine_args(
|
||||||
from qutebrowser.browser.webengine import darkmode
|
from qutebrowser.browser.webengine import darkmode
|
||||||
blink_settings = list(darkmode.settings())
|
blink_settings = list(darkmode.settings())
|
||||||
if blink_settings:
|
if blink_settings:
|
||||||
yield '--blink-settings=' + ','.join('{}={}'.format(k, v)
|
yield '--blink-settings=' + ','.join(f'{k}={v}' for k, v in blink_settings)
|
||||||
for k, v in blink_settings)
|
|
||||||
|
|
||||||
enabled_features = list(_qtwebengine_enabled_features(feature_flags))
|
enabled_features = list(_qtwebengine_enabled_features(feature_flags))
|
||||||
if enabled_features:
|
if enabled_features:
|
||||||
|
|
@ -162,8 +164,8 @@ def _qtwebengine_args(
|
||||||
yield from _qtwebengine_settings_args()
|
yield from _qtwebengine_settings_args()
|
||||||
|
|
||||||
|
|
||||||
def _qtwebengine_settings_args() -> typing.Iterator[str]:
|
def _qtwebengine_settings_args() -> Iterator[str]:
|
||||||
settings = {
|
settings: Dict[str, Dict[Any, Optional[str]]] = {
|
||||||
'qt.force_software_rendering': {
|
'qt.force_software_rendering': {
|
||||||
'software-opengl': None,
|
'software-opengl': None,
|
||||||
'qt-quick': None,
|
'qt-quick': None,
|
||||||
|
|
@ -198,23 +200,26 @@ def _qtwebengine_settings_args() -> typing.Iterator[str]:
|
||||||
},
|
},
|
||||||
'content.headers.referer': {
|
'content.headers.referer': {
|
||||||
'always': None,
|
'always': None,
|
||||||
'never': '--no-referrers',
|
|
||||||
'same-domain': '--reduced-referrer-granularity',
|
|
||||||
}
|
}
|
||||||
} # type: typing.Dict[str, typing.Dict[typing.Any, typing.Optional[str]]]
|
}
|
||||||
|
|
||||||
if not qtutils.version_check('5.11'):
|
referrer_setting = settings['content.headers.referer']
|
||||||
# On Qt 5.11, we can control this via QWebEngineSettings
|
if qtutils.version_check('5.14', compiled=False):
|
||||||
settings['content.autoplay'] = {
|
|
||||||
True: None,
|
|
||||||
False: '--autoplay-policy=user-gesture-required',
|
|
||||||
}
|
|
||||||
|
|
||||||
if qtutils.version_check('5.14'):
|
|
||||||
settings['colors.webpage.prefers_color_scheme_dark'] = {
|
settings['colors.webpage.prefers_color_scheme_dark'] = {
|
||||||
True: '--force-dark-mode',
|
True: '--force-dark-mode',
|
||||||
False: None,
|
False: None,
|
||||||
}
|
}
|
||||||
|
# Starting with Qt 5.14, this is handled via --enable-features
|
||||||
|
referrer_setting['same-domain'] = None
|
||||||
|
else:
|
||||||
|
referrer_setting['same-domain'] = '--reduced-referrer-granularity'
|
||||||
|
|
||||||
|
can_override_referer = (
|
||||||
|
qtutils.version_check('5.12.4', compiled=False) and
|
||||||
|
not qtutils.version_check('5.13.0', compiled=False, exact=True)
|
||||||
|
)
|
||||||
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-60203
|
||||||
|
referrer_setting['never'] = None if can_override_referer else '--no-referrers'
|
||||||
|
|
||||||
for setting, args in sorted(settings.items()):
|
for setting, args in sorted(settings.items()):
|
||||||
arg = args[config.instance.get(setting)]
|
arg = args[config.instance.get(setting)]
|
||||||
|
|
|
||||||
|
|
@ -81,13 +81,13 @@ class _StyleSheetObserver(QObject):
|
||||||
if update:
|
if update:
|
||||||
self.setParent(self._obj)
|
self.setParent(self._obj)
|
||||||
if stylesheet is None:
|
if stylesheet is None:
|
||||||
self._stylesheet = obj.STYLESHEET # type: str
|
self._stylesheet: str = obj.STYLESHEET
|
||||||
else:
|
else:
|
||||||
self._stylesheet = stylesheet
|
self._stylesheet = stylesheet
|
||||||
|
|
||||||
if update:
|
if update:
|
||||||
self._options = jinja.template_config_variables(
|
self._options: Optional[FrozenSet[str]] = jinja.template_config_variables(
|
||||||
self._stylesheet) # type: Optional[FrozenSet[str]]
|
self._stylesheet)
|
||||||
else:
|
else:
|
||||||
self._options = None
|
self._options = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@
|
||||||
"""Bridge from QWeb(Engine)Settings to our own settings."""
|
"""Bridge from QWeb(Engine)Settings to our own settings."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import typing
|
|
||||||
import argparse
|
import argparse
|
||||||
import functools
|
import functools
|
||||||
|
from typing import Any, Callable, Dict, Optional
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
from PyQt5.QtCore import QUrl, pyqtSlot, qVersion
|
from PyQt5.QtCore import QUrl, pyqtSlot, qVersion
|
||||||
|
|
@ -30,7 +30,7 @@ from PyQt5.QtGui import QFont
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import log, usertypes, urlmatch, qtutils, utils
|
from qutebrowser.utils import usertypes, urlmatch, qtutils, utils
|
||||||
from qutebrowser.misc import objects, debugcachestats
|
from qutebrowser.misc import objects, debugcachestats
|
||||||
|
|
||||||
UNSET = object()
|
UNSET = object()
|
||||||
|
|
@ -41,11 +41,11 @@ class UserAgent:
|
||||||
|
|
||||||
"""A parsed user agent."""
|
"""A parsed user agent."""
|
||||||
|
|
||||||
os_info = attr.ib() # type: str
|
os_info: str = attr.ib()
|
||||||
webkit_version = attr.ib() # type: str
|
webkit_version: str = attr.ib()
|
||||||
upstream_browser_key = attr.ib() # type: str
|
upstream_browser_key: str = attr.ib()
|
||||||
upstream_browser_version = attr.ib() # type: str
|
upstream_browser_version: str = attr.ib()
|
||||||
qt_key = attr.ib() # type: str
|
qt_key: str = attr.ib()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, ua: str) -> 'UserAgent':
|
def parse(cls, ua: str) -> 'UserAgent':
|
||||||
|
|
@ -82,8 +82,7 @@ class AttributeInfo:
|
||||||
|
|
||||||
"""Info about a settings attribute."""
|
"""Info about a settings attribute."""
|
||||||
|
|
||||||
def __init__(self, *attributes: typing.Any,
|
def __init__(self, *attributes: Any, converter: Callable = None) -> None:
|
||||||
converter: typing.Callable = None) -> None:
|
|
||||||
self.attributes = attributes
|
self.attributes = attributes
|
||||||
if converter is None:
|
if converter is None:
|
||||||
self.converter = lambda val: val
|
self.converter = lambda val: val
|
||||||
|
|
@ -95,37 +94,28 @@ class AbstractSettings:
|
||||||
|
|
||||||
"""Abstract base class for settings set via QWeb(Engine)Settings."""
|
"""Abstract base class for settings set via QWeb(Engine)Settings."""
|
||||||
|
|
||||||
_ATTRIBUTES = {} # type: typing.Dict[str, AttributeInfo]
|
_ATTRIBUTES: Dict[str, AttributeInfo] = {}
|
||||||
_FONT_SIZES = {} # type: typing.Dict[str, typing.Any]
|
_FONT_SIZES: Dict[str, Any] = {}
|
||||||
_FONT_FAMILIES = {} # type: typing.Dict[str, typing.Any]
|
_FONT_FAMILIES: Dict[str, Any] = {}
|
||||||
_FONT_TO_QFONT = {} # type: typing.Dict[typing.Any, QFont.StyleHint]
|
_FONT_TO_QFONT: Dict[Any, QFont.StyleHint] = {}
|
||||||
|
|
||||||
def __init__(self, settings: typing.Any) -> None:
|
def __init__(self, settings: Any) -> None:
|
||||||
self._settings = settings
|
self._settings = settings
|
||||||
|
|
||||||
def _assert_not_unset(self, value: typing.Any) -> None:
|
def _assert_not_unset(self, value: Any) -> None:
|
||||||
assert value is not usertypes.UNSET
|
assert value is not usertypes.UNSET
|
||||||
|
|
||||||
def set_attribute(self, name: str, value: typing.Any) -> bool:
|
def set_attribute(self, name: str, value: Any) -> None:
|
||||||
"""Set the given QWebSettings/QWebEngineSettings attribute.
|
"""Set the given QWebSettings/QWebEngineSettings attribute.
|
||||||
|
|
||||||
If the value is usertypes.UNSET, the value is reset instead.
|
If the value is usertypes.UNSET, the value is reset instead.
|
||||||
|
|
||||||
Return:
|
|
||||||
True if there was a change, False otherwise.
|
|
||||||
"""
|
"""
|
||||||
old_value = self.test_attribute(name)
|
|
||||||
|
|
||||||
info = self._ATTRIBUTES[name]
|
info = self._ATTRIBUTES[name]
|
||||||
for attribute in info.attributes:
|
for attribute in info.attributes:
|
||||||
if value is usertypes.UNSET:
|
if value is usertypes.UNSET:
|
||||||
self._settings.resetAttribute(attribute)
|
self._settings.resetAttribute(attribute)
|
||||||
new_value = self.test_attribute(name)
|
|
||||||
else:
|
else:
|
||||||
self._settings.setAttribute(attribute, info.converter(value))
|
self._settings.setAttribute(attribute, info.converter(value))
|
||||||
new_value = value
|
|
||||||
|
|
||||||
return old_value != new_value
|
|
||||||
|
|
||||||
def test_attribute(self, name: str) -> bool:
|
def test_attribute(self, name: str) -> bool:
|
||||||
"""Get the value for the given attribute.
|
"""Get the value for the given attribute.
|
||||||
|
|
@ -136,26 +126,17 @@ class AbstractSettings:
|
||||||
info = self._ATTRIBUTES[name]
|
info = self._ATTRIBUTES[name]
|
||||||
return self._settings.testAttribute(info.attributes[0])
|
return self._settings.testAttribute(info.attributes[0])
|
||||||
|
|
||||||
def set_font_size(self, name: str, value: int) -> bool:
|
def set_font_size(self, name: str, value: int) -> None:
|
||||||
"""Set the given QWebSettings/QWebEngineSettings font size.
|
"""Set the given QWebSettings/QWebEngineSettings font size."""
|
||||||
|
|
||||||
Return:
|
|
||||||
True if there was a change, False otherwise.
|
|
||||||
"""
|
|
||||||
self._assert_not_unset(value)
|
self._assert_not_unset(value)
|
||||||
family = self._FONT_SIZES[name]
|
family = self._FONT_SIZES[name]
|
||||||
old_value = self._settings.fontSize(family)
|
|
||||||
self._settings.setFontSize(family, value)
|
self._settings.setFontSize(family, value)
|
||||||
return old_value != value
|
|
||||||
|
|
||||||
def set_font_family(self, name: str, value: typing.Optional[str]) -> bool:
|
def set_font_family(self, name: str, value: Optional[str]) -> None:
|
||||||
"""Set the given QWebSettings/QWebEngineSettings font family.
|
"""Set the given QWebSettings/QWebEngineSettings font family.
|
||||||
|
|
||||||
With None (the default), QFont is used to get the default font for the
|
With None (the default), QFont is used to get the default font for the
|
||||||
family.
|
family.
|
||||||
|
|
||||||
Return:
|
|
||||||
True if there was a change, False otherwise.
|
|
||||||
"""
|
"""
|
||||||
self._assert_not_unset(value)
|
self._assert_not_unset(value)
|
||||||
family = self._FONT_FAMILIES[name]
|
family = self._FONT_FAMILIES[name]
|
||||||
|
|
@ -164,23 +145,14 @@ class AbstractSettings:
|
||||||
font.setStyleHint(self._FONT_TO_QFONT[family])
|
font.setStyleHint(self._FONT_TO_QFONT[family])
|
||||||
value = font.defaultFamily()
|
value = font.defaultFamily()
|
||||||
|
|
||||||
old_value = self._settings.fontFamily(family)
|
|
||||||
self._settings.setFontFamily(family, value)
|
self._settings.setFontFamily(family, value)
|
||||||
|
|
||||||
return value != old_value
|
def set_default_text_encoding(self, encoding: str) -> None:
|
||||||
|
"""Set the default text encoding to use."""
|
||||||
def set_default_text_encoding(self, encoding: str) -> bool:
|
|
||||||
"""Set the default text encoding to use.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
True if there was a change, False otherwise.
|
|
||||||
"""
|
|
||||||
self._assert_not_unset(encoding)
|
self._assert_not_unset(encoding)
|
||||||
old_value = self._settings.defaultTextEncoding()
|
|
||||||
self._settings.setDefaultTextEncoding(encoding)
|
self._settings.setDefaultTextEncoding(encoding)
|
||||||
return old_value != encoding
|
|
||||||
|
|
||||||
def _update_setting(self, setting: str, value: typing.Any) -> bool:
|
def _update_setting(self, setting: str, value: Any) -> bool:
|
||||||
"""Update the given setting/value.
|
"""Update the given setting/value.
|
||||||
|
|
||||||
Unknown settings are ignored.
|
Unknown settings are ignored.
|
||||||
|
|
@ -189,13 +161,13 @@ class AbstractSettings:
|
||||||
True if there was a change, False otherwise.
|
True if there was a change, False otherwise.
|
||||||
"""
|
"""
|
||||||
if setting in self._ATTRIBUTES:
|
if setting in self._ATTRIBUTES:
|
||||||
return self.set_attribute(setting, value)
|
self.set_attribute(setting, value)
|
||||||
elif setting in self._FONT_SIZES:
|
elif setting in self._FONT_SIZES:
|
||||||
return self.set_font_size(setting, value)
|
self.set_font_size(setting, value)
|
||||||
elif setting in self._FONT_FAMILIES:
|
elif setting in self._FONT_FAMILIES:
|
||||||
return self.set_font_family(setting, value)
|
self.set_font_family(setting, value)
|
||||||
elif setting == 'content.default_encoding':
|
elif setting == 'content.default_encoding':
|
||||||
return self.set_default_text_encoding(value)
|
self.set_default_text_encoding(value)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update_setting(self, setting: str) -> None:
|
def update_setting(self, setting: str) -> None:
|
||||||
|
|
@ -203,27 +175,15 @@ class AbstractSettings:
|
||||||
value = config.instance.get(setting)
|
value = config.instance.get(setting)
|
||||||
self._update_setting(setting, value)
|
self._update_setting(setting, value)
|
||||||
|
|
||||||
def update_for_url(self, url: QUrl) -> typing.Set[str]:
|
def update_for_url(self, url: QUrl) -> None:
|
||||||
"""Update settings customized for the given tab.
|
"""Update settings customized for the given tab."""
|
||||||
|
|
||||||
Return:
|
|
||||||
A set of settings which actually changed.
|
|
||||||
"""
|
|
||||||
qtutils.ensure_valid(url)
|
qtutils.ensure_valid(url)
|
||||||
changed_settings = set()
|
|
||||||
for values in config.instance:
|
for values in config.instance:
|
||||||
if not values.opt.supports_pattern:
|
if not values.opt.supports_pattern:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
value = values.get_for_url(url, fallback=False)
|
value = values.get_for_url(url, fallback=False)
|
||||||
|
self._update_setting(values.opt.name, value)
|
||||||
changed = self._update_setting(values.opt.name, value)
|
|
||||||
if changed:
|
|
||||||
log.config.debug("Changed for {}: {} = {}".format(
|
|
||||||
url.toDisplayString(), values.opt.name, value))
|
|
||||||
changed_settings.add(values.opt.name)
|
|
||||||
|
|
||||||
return changed_settings
|
|
||||||
|
|
||||||
def init_settings(self) -> None:
|
def init_settings(self) -> None:
|
||||||
"""Set all supported settings correctly."""
|
"""Set all supported settings correctly."""
|
||||||
|
|
@ -268,10 +228,10 @@ def init(args: argparse.Namespace) -> None:
|
||||||
"""Initialize all QWeb(Engine)Settings."""
|
"""Initialize all QWeb(Engine)Settings."""
|
||||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||||
from qutebrowser.browser.webengine import webenginesettings
|
from qutebrowser.browser.webengine import webenginesettings
|
||||||
webenginesettings.init(args)
|
webenginesettings.init()
|
||||||
elif objects.backend == usertypes.Backend.QtWebKit:
|
elif objects.backend == usertypes.Backend.QtWebKit:
|
||||||
from qutebrowser.browser.webkit import webkitsettings
|
from qutebrowser.browser.webkit import webkitsettings
|
||||||
webkitsettings.init(args)
|
webkitsettings.init()
|
||||||
else:
|
else:
|
||||||
raise utils.Unreachable(objects.backend)
|
raise utils.Unreachable(objects.backend)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
{% extends "styled.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{{ title }}</h1>
|
|
||||||
<span class="note">Note this warning will only appear once. Use <span class="mono">:open
|
|
||||||
qute://warning/old-qt</span> to show it again at a later time.</span>
|
|
||||||
|
|
||||||
<p>You're using qutebrowser with Qt {{qt_version}}.</p>
|
|
||||||
|
|
||||||
{% if qt_version.startswith('5.7.') %}
|
|
||||||
<p>Qt 5.7 was released in June 2016, with the 5.7.1 patch release in December
|
|
||||||
2016. It is based on Chromium 49 (March 2016) with (some) security fixes up to
|
|
||||||
Chromium 54 (October 2016).</p>
|
|
||||||
{% elif qt_version.startswith('5.8.') %}
|
|
||||||
<p>Qt 5.8 has had various bugs, and has been unsupported (but working to some
|
|
||||||
degree) in qutebrowser for a while.</p>
|
|
||||||
{% elif qt_version.startswith('5.9.') %}
|
|
||||||
<p>Qt 5.9 LTS was released in May 2017 and is based on Chromium 56 (January 2017). It is a long term support release with the 5.9.9 patch release in December 2019 including (some) security fixes up to Chromium 78 (November 2019). However, its usage was found to be low, and the next LTS (Qt 5.12) was released in December 2018.</p>
|
|
||||||
{% elif qt_version.startswith('5.10.') %}
|
|
||||||
<p>Qt 5.10 was released in December 2017, with the 5.10.1 patch release in February
|
|
||||||
2018. It is based on Chromium 65 (March 2018) with (some) security fixes up to
|
|
||||||
Chromium 70 (November 2018).</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
Also, note that QtWebEngine is <a href="https://www.debian.org/releases/stable/amd64/release-notes/ch-information.en.html#browser-security">not covered</a> by Debian security updates.
|
|
||||||
|
|
||||||
<p>Because of those security issues and the maintaince burden coming with
|
|
||||||
supporting old versions, support for Qt < 5.11 will be dropped in qutebrowser
|
|
||||||
v2.0. You might want to check
|
|
||||||
<a href="https://qutebrowser.org/doc/install.html">alternate installation methods</a>
|
|
||||||
which allow you to get a newer Qt.</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -12,9 +12,7 @@
|
||||||
|
|
||||||
env:
|
env:
|
||||||
browser: true
|
browser: true
|
||||||
|
es6: true
|
||||||
parserOptions:
|
|
||||||
ecmaVersion: 6
|
|
||||||
|
|
||||||
extends:
|
extends:
|
||||||
"eslint:all"
|
"eslint:all"
|
||||||
|
|
@ -31,7 +29,7 @@ rules:
|
||||||
init-declarations: "off"
|
init-declarations: "off"
|
||||||
no-plusplus: "off"
|
no-plusplus: "off"
|
||||||
no-extra-parens: "off"
|
no-extra-parens: "off"
|
||||||
id-length: ["error", {"exceptions": ["i", "k", "x", "y"]}]
|
id-length: ["error", {"exceptions": ["i", "k", "v", "x", "y"]}]
|
||||||
object-shorthand: "off"
|
object-shorthand: "off"
|
||||||
max-statements: ["error", {"max": 40}]
|
max-statements: ["error", {"max": 40}]
|
||||||
quotes: ["error", "double", {"avoidEscape": true}]
|
quotes: ["error", "double", {"avoidEscape": true}]
|
||||||
|
|
@ -43,7 +41,7 @@ rules:
|
||||||
func-names: "off"
|
func-names: "off"
|
||||||
sort-keys: "off"
|
sort-keys: "off"
|
||||||
no-warning-comments: "off"
|
no-warning-comments: "off"
|
||||||
max-len: ["error", {"ignoreUrls": true}]
|
max-len: ["error", {"ignoreUrls": true, "code": 88}]
|
||||||
capitalized-comments: "off"
|
capitalized-comments: "off"
|
||||||
prefer-destructuring: "off"
|
prefer-destructuring: "off"
|
||||||
line-comment-position: "off"
|
line-comment-position: "off"
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue