Merge branch 'master' into more-sophisticated-adblock
This commit is contained in:
commit
729d6c9d8f
|
|
@ -1,5 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 1.14.0
|
||||
current_version = 1.14.1
|
||||
commit = True
|
||||
message = Release v{new_version}
|
||||
tag = True
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- name: Set up problem matchers
|
||||
run: "python scripts/dev/ci/problemmatchers.py py38 ${{ runner.temp }}"
|
||||
- run: tox -e py38
|
||||
- run: tox -e py
|
||||
|
||||
tests:
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
|
|
@ -112,6 +112,10 @@ jobs:
|
|||
- testenv: py38-pyqt514
|
||||
os: ubuntu-20.04
|
||||
python: 3.8
|
||||
### PyQt 5.15.0 (Python 3.9)
|
||||
- testenv: py39-pyqt5150
|
||||
os: ubuntu-20.04
|
||||
python: 3.9
|
||||
### PyQt 5.15 (Python 3.9, with coverage)
|
||||
- testenv: py39-pyqt515-cov
|
||||
os: ubuntu-20.04
|
||||
|
|
@ -173,14 +177,6 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ on:
|
|||
|
||||
jobs:
|
||||
docker:
|
||||
if: "github.repository == 'qutebrowser/qutebrowser'"
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ Sessionx.vim
|
|||
/.pytest_cache
|
||||
/.testmondata
|
||||
/.hypothesis
|
||||
/.benchmarks
|
||||
.mypy_cache
|
||||
/prof
|
||||
/venv
|
||||
|
|
|
|||
|
|
@ -125,12 +125,15 @@ The following software and libraries are required to run qutebrowser:
|
|||
sensitive data.**
|
||||
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.12.0 or newer
|
||||
for Python 3
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] (being
|
||||
phased out for qutebrowser v2.0.0)
|
||||
* https://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
* http://pygments.org/[pygments]
|
||||
* https://github.com/yaml/pyyaml[PyYAML]
|
||||
* https://www.attrs.org/[attrs]
|
||||
* https://importlib-resources.readthedocs.io/[importlib_resources] (on Python
|
||||
3.8 or older)
|
||||
|
||||
The following libraries are optional:
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ Major changes
|
|||
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].
|
||||
- TODO: The former dependency on the `pkg_resources` module (part of the
|
||||
`setuptools` project) got dropped.
|
||||
- A new dependency on the `importlib_resources` module got introduced for
|
||||
Python versions up to and including 3.8. Note that the stdlib
|
||||
`importlib.resources` module for Python 3.7 and 3.8 is missing the needed APIs,
|
||||
thus requiring the backports for those versions as well.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
|
@ -64,6 +70,12 @@ Added
|
|||
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.
|
||||
- New `--desktop-file-name` commandline argument, which can be used to customize
|
||||
the desktop filename passed to Qt (which is used to set the `app_id` on
|
||||
Wayland).
|
||||
- New userscripts:
|
||||
- `kodi` to play videos in Kodi
|
||||
- `qr` to generate a QR code of the current URL
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
|
@ -83,14 +95,30 @@ Changed
|
|||
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.
|
||||
- The `mkvenv.py` script now runs a "smoke test" after setting up the virtual
|
||||
environment to ensure it's working as expected. If necessary, the test can be
|
||||
skipped via a new `--skip-smoke-test` flag.
|
||||
- Both qutebrowser userscripts and Greasemonkey scripts are now additionally
|
||||
picked up from qutebrowser's config directory (the `userscripts` and
|
||||
`greasemonkey` subdirectories of e.g. `~/.config/qutebrowser/`) rather than only
|
||||
the data directory (the same subdirectories of e.g.
|
||||
`~/.local/share/qutebrowser/`).
|
||||
- The `:later` command now understands a time specification like `5m` or
|
||||
`1h5m2s`, rather than just taking milliseconds.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- With interpolated color settings (`colors.tabs.indicator.*` and
|
||||
`colors.downloads.*`), the alpha channel is now handled correctly.
|
||||
- The `format_json` userscript now uses `env` in its shebang, making it work
|
||||
correctly on systems where `bash` isn't located in `/bin`.
|
||||
- TODO: Due to a long-standing bug in the `pkg_resources` dependency, it caused
|
||||
qutebrowser's startup to slow down by around 150ms-1s (heavily depending on
|
||||
the system). Since the dependency is now removed, qutebrowser's startup time
|
||||
thus improved.
|
||||
|
||||
v1.14.1 (unreleased)
|
||||
v1.14.1 (2020-12-04)
|
||||
--------------------
|
||||
|
||||
Added
|
||||
|
|
@ -178,8 +206,17 @@ Fixed
|
|||
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.
|
||||
- With Qt 5.15.2, lines/borders coming from the `readability-js` userscript
|
||||
were invisible. This is now fixed by changing the border color to grey (with all
|
||||
Qt versions).
|
||||
- Due to changes in the underlying Chromium, the
|
||||
`colors.webpage.prefers_color_scheme_dark` setting broke with Qt 5.15.2. It now
|
||||
works properly again.
|
||||
- A bug in the `pkg_resources` module used by qutebrowser caused deprecation
|
||||
warnings to appear on start with Python 3.9 on some setups. Those are now
|
||||
hidden.
|
||||
- Minor performance improvements.
|
||||
- (TODO) Fix for various functionality breaking in private windows with v1.14.0,
|
||||
- Fix for various functionality breaking in private windows with v1.14.0,
|
||||
after the last private window is closed. This includes:
|
||||
* Ad blocking
|
||||
* Downloads
|
||||
|
|
|
|||
|
|
@ -597,7 +597,7 @@ Syntax: +:greasemonkey-reload [*--force*]+
|
|||
|
||||
Re-read Greasemonkey scripts from disk.
|
||||
|
||||
The scripts are read from a 'greasemonkey' subdirectory in qutebrowser's data directory (see `:version`).
|
||||
The scripts are read from a 'greasemonkey' subdirectory in qutebrowser's data or config directories (see `:version`).
|
||||
|
||||
==== optional arguments
|
||||
* +*-f*+, +*--force*+: For any scripts that have required dependencies, re-download them.
|
||||
|
|
@ -784,12 +784,12 @@ Jump to the mark named by `key`.
|
|||
|
||||
[[later]]
|
||||
=== later
|
||||
Syntax: +:later 'ms' 'command'+
|
||||
Syntax: +:later 'duration' 'command'+
|
||||
|
||||
Execute a command after some time.
|
||||
|
||||
==== positional arguments
|
||||
* +'ms'+: How many milliseconds to wait.
|
||||
* +'duration'+: Duration to wait in format XhYmZs or a number for milliseconds.
|
||||
* +'command'+: The command to run, with optional args.
|
||||
|
||||
==== note
|
||||
|
|
@ -1308,7 +1308,8 @@ Note that the command is *not* run in a shell, so things like `$VAR` or `> outpu
|
|||
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
|
||||
* +*-o*+, +*--output*+: Show the output in a new tab.
|
||||
* +*-m*+, +*--output-messages*+: Show the output as messages.
|
||||
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
|
||||
* +*-d*+, +*--detach*+: Detach the command from qutebrowser so that it continues running when qutebrowser quits.
|
||||
|
||||
|
||||
==== count
|
||||
Given to userscripts as $QUTE_COUNT.
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@ show it.
|
|||
*--backend* '{webkit,webengine}'::
|
||||
Which backend to use.
|
||||
|
||||
*--desktop-file-name* 'DESKTOP_FILE_NAME'::
|
||||
Set the base name of the desktop entry for this application. Used to set the app_id under Wayland. See https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop
|
||||
|
||||
=== debug arguments
|
||||
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
|
||||
Override the configured console loglevel
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ mpv, a simple key binding to something like `:spawn mpv {url}` should suffice.
|
|||
Also note userscripts need to have the executable bit set (`chmod +x`) for
|
||||
qutebrowser to run them.
|
||||
|
||||
To call a userscript, it needs to be stored in your data directory under
|
||||
To call a userscript, it needs to be stored in your config or data directory under
|
||||
`userscripts` (for example: `~/.local/share/qutebrowser/userscripts/myscript`),
|
||||
or just use an absolute path.
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
</content_rating>
|
||||
<releases>
|
||||
<!-- Add new releases here -->
|
||||
<release version="1.14.1" date="2020-12-04"/>
|
||||
<release version="1.14.0" date="2020-10-15"/>
|
||||
<release version="1.13.1" date="2020-07-17"/>
|
||||
<release version="1.13.0" date="2020-06-26"/>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
build==0.1.0
|
||||
check-manifest==0.45
|
||||
packaging==20.4
|
||||
packaging==20.8
|
||||
pep517==0.9.1
|
||||
pyparsing==2.4.7
|
||||
six==1.15.0
|
||||
toml==0.10.2
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
bump2version==1.0.1
|
||||
certifi==2020.11.8
|
||||
certifi==2020.12.5
|
||||
cffi==1.14.4
|
||||
chardet==3.0.4
|
||||
colorama==0.4.4
|
||||
cryptography==3.2.1
|
||||
cryptography==3.3.1
|
||||
github3.py==1.3.0
|
||||
hunter==3.3.1
|
||||
idna==2.10
|
||||
jwcrypto==0.8
|
||||
manhole==1.6.0
|
||||
packaging==20.4
|
||||
packaging==20.8
|
||||
pycparser==2.20
|
||||
Pympler==0.9
|
||||
pyparsing==2.4.7
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ flake8-bugbear==20.11.1
|
|||
flake8-builtins==1.5.3
|
||||
flake8-comprehensions==3.3.0
|
||||
flake8-copyright==0.2.2
|
||||
flake8-debugger==3.2.1
|
||||
flake8-debugger==4.0.0
|
||||
flake8-deprecated==1.3
|
||||
flake8-docstrings==1.5.0
|
||||
flake8-future-import==0.4.6
|
||||
flake8-mock==0.3
|
||||
flake8-polyfill==1.0.2
|
||||
flake8-string-format==0.3.0
|
||||
flake8-tidy-imports==4.1.0
|
||||
flake8-tidy-imports==4.2.0
|
||||
flake8-tuple==0.4.1
|
||||
mccabe==0.6.1
|
||||
pep8-naming==0.11.1
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ diff-cover==4.0.1
|
|||
inflect==5.0.2
|
||||
Jinja2==2.11.2
|
||||
jinja2-pluralize==0.3.0
|
||||
lxml==4.6.1
|
||||
lxml==4.6.2
|
||||
MarkupSafe==1.1.1
|
||||
mypy==0.790
|
||||
mypy-extensions==0.4.3
|
||||
pluggy==0.13.1
|
||||
Pygments==2.7.2
|
||||
Pygments==2.7.3
|
||||
-e git+https://github.com/stlehmann/PyQt5-stubs.git@704207e90bee7b36ec9861dfa6b39f06a27c6718#egg=PyQt5_stubs
|
||||
typed-ast==1.4.1
|
||||
typing-extensions==3.7.4.3
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==2.3.3 # rq.filter: < 2.4
|
||||
certifi==2020.11.8
|
||||
certifi==2020.12.5
|
||||
cffi==1.14.4
|
||||
chardet==3.0.4
|
||||
cryptography==3.2.1
|
||||
cryptography==3.3.1
|
||||
github3.py==1.3.0
|
||||
idna==2.10
|
||||
isort==4.3.21
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.0 # rq.filter: == 5.15.0
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.15.0 # rq.filter: == 5.15.0
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#@ filter: PyQt5 == 5.15.0
|
||||
#@ filter: PyQtWebEngine == 5.15.0
|
||||
PyQt5 == 5.15.0
|
||||
PyQtWebEngine == 5.15.0
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.16
|
||||
Pygments==2.7.2
|
||||
Pygments==2.7.3
|
||||
pyroma==2.6
|
||||
|
|
|
|||
|
|
@ -5,3 +5,6 @@ PyYAML
|
|||
colorama
|
||||
attrs
|
||||
adblock # Optional, for improved adblocking
|
||||
importlib-resources
|
||||
|
||||
#@ markers: importlib-resources python_version<"3.9"
|
||||
|
|
|
|||
|
|
@ -2,19 +2,18 @@
|
|||
|
||||
alabaster==0.7.12
|
||||
Babel==2.9.0
|
||||
certifi==2020.11.8
|
||||
certifi==2020.12.5
|
||||
chardet==3.0.4
|
||||
docutils==0.16
|
||||
idna==2.10
|
||||
imagesize==1.2.0
|
||||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
packaging==20.4
|
||||
Pygments==2.7.2
|
||||
packaging==20.8
|
||||
Pygments==2.7.3
|
||||
pyparsing==2.4.7
|
||||
pytz==2020.4
|
||||
requests==2.25.0
|
||||
six==1.15.0
|
||||
snowballstemmer==2.0.0
|
||||
Sphinx==3.3.1
|
||||
sphinxcontrib-applehelp==1.0.2
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
apipkg==1.5
|
||||
attrs==20.3.0
|
||||
beautifulsoup4==4.9.3
|
||||
certifi==2020.11.8
|
||||
certifi==2020.12.5
|
||||
chardet==3.0.4
|
||||
cheroot==8.4.7
|
||||
cheroot==8.5.1
|
||||
click==7.1.2
|
||||
# colorama==0.4.4
|
||||
coverage==5.3
|
||||
|
|
@ -15,7 +15,7 @@ filelock==3.0.12
|
|||
Flask==1.1.2
|
||||
glob2==0.7
|
||||
hunter==3.3.1
|
||||
hypothesis==5.41.3
|
||||
hypothesis==5.43.3
|
||||
icdiff==1.9.1
|
||||
idna==2.10
|
||||
iniconfig==1.1.1
|
||||
|
|
@ -26,17 +26,17 @@ Mako==1.1.3
|
|||
manhole==1.6.0
|
||||
# MarkupSafe==1.1.1
|
||||
more-itertools==8.6.0
|
||||
packaging==20.4
|
||||
packaging==20.8
|
||||
parse==1.18.0
|
||||
parse-type==0.5.2
|
||||
pluggy==0.13.1
|
||||
pprintpp==0.4.0
|
||||
py==1.9.0
|
||||
py==1.10.0
|
||||
py-cpuinfo==7.0.0
|
||||
Pygments==2.7.2
|
||||
Pygments==2.7.3
|
||||
pyparsing==2.4.7
|
||||
pytest==6.1.2
|
||||
pytest-bdd==4.0.1
|
||||
pytest==6.2.0
|
||||
pytest-bdd==4.0.2
|
||||
pytest-benchmark==3.2.3
|
||||
pytest-clarity==0.3.0a0
|
||||
pytest-cov==2.10.1
|
||||
|
|
@ -54,7 +54,7 @@ requests==2.25.0
|
|||
requests-file==1.5.1
|
||||
six==1.15.0
|
||||
sortedcontainers==2.3.0
|
||||
soupsieve==2.0.1
|
||||
soupsieve==2.1
|
||||
termcolor==1.1.0
|
||||
tldextract==3.1.0
|
||||
toml==0.10.2
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
appdirs==1.4.4
|
||||
distlib==0.3.1
|
||||
filelock==3.0.12
|
||||
packaging==20.4
|
||||
packaging==20.8
|
||||
pluggy==0.13.1
|
||||
py==1.9.0
|
||||
py==1.10.0
|
||||
pyparsing==2.4.7
|
||||
six==1.15.0
|
||||
toml==0.10.2
|
||||
tox==3.20.1
|
||||
virtualenv==20.2.1
|
||||
virtualenv==20.2.2
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ The following userscripts are included in the current directory.
|
|||
- [qutedmenu](./qutedmenu): Handle open -s && open -t with bemenu.
|
||||
- [readability](./readability): Executes python-readability on current page and
|
||||
opens the summary as new tab.
|
||||
- [readability-js](./readability-js): Processes the current page with the readability
|
||||
- [readability-js](./readability-js): Processes the current page with the readability
|
||||
library used in Firefox Reader View and opens the summary as new tab.
|
||||
- [ripbang](./ripbang): Adds DuckDuckGo bang as searchengine.
|
||||
- [rss](./rss): Keeps track of URLs in RSS feeds and opens new ones.
|
||||
|
|
@ -32,6 +32,9 @@ The following userscripts are included in the current directory.
|
|||
- [tor_identity](./tor_identity): Change your tor identity.
|
||||
- [view_in_mpv](./view_in_mpv): Views the current web page in mpv using
|
||||
sensible mpv-flags.
|
||||
- [qr](./qr): Show a QR code for the current webpage via
|
||||
[qrencode](https://fukuchi.org/works/qrencode/).
|
||||
- [kodi](./kodi): Play videos in Kodi.
|
||||
|
||||
[castnow]: https://github.com/xat/castnow
|
||||
[youtube-dl]: https://rg3.github.io/youtube-dl/
|
||||
|
|
@ -67,6 +70,8 @@ The following userscripts can be found on their own repositories.
|
|||
and retrieve they when you want.
|
||||
- [doi](https://github.com/cadadr/configuration/blob/master/qutebrowser/userscripts/doi):
|
||||
Opens DOIs on Sci-Hub.
|
||||
- [1password](https://github.com/tomoakley/dotfiles/blob/master/qutebrowser/userscripts/1password):
|
||||
Integration with 1password on macOS.
|
||||
|
||||
[Zotero]: https://www.zotero.org/
|
||||
[Pocket]: https://getpocket.com/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
#
|
||||
# Behavior:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Behavior:
|
||||
# A qutebrowser userscript that plays Twitch, YouTube or Vimeo videos in Kodi via its
|
||||
# API.
|
||||
#
|
||||
# Requirements:
|
||||
# awk
|
||||
# bash
|
||||
# curl
|
||||
#
|
||||
# Kodi setup:
|
||||
# Settings -> Services -> Control
|
||||
# enable 'Allow remote control via HTTP'
|
||||
# set Username and Password
|
||||
# enable 'Allow remote control from applications on this system'
|
||||
# Optional yet recommended, setup SSL within Kodi over via a proxy webserver
|
||||
#
|
||||
# userscript setup:
|
||||
# create ~/.config/qutebrowser/kodi_rc with host and authentication information like:
|
||||
#
|
||||
# HOST="http://127.0.0.1:8080"
|
||||
# or
|
||||
# HOST="https://kodi.example.com"
|
||||
#
|
||||
# AUTH="user:password"
|
||||
# or
|
||||
# AUTH="bas64authenticationinformation"
|
||||
#
|
||||
# The base64 authentication is the output of
|
||||
# `echo -ne "user:password" |base64 --wrap 0`
|
||||
# reminder base64 is not encryption
|
||||
#
|
||||
# For vim users you might want to add '# vim: set nospell filetype=bash' to the
|
||||
# kodi_rc file.
|
||||
#
|
||||
# qutebrowser setup:
|
||||
# in ~/.config/qutebrowser/config.py add something like
|
||||
#
|
||||
# to send video link via hints:
|
||||
# config.bind('X', 'hint links userscript kodi')
|
||||
# to send current URL:
|
||||
# config.bind('X', 'spawn --userscript kodi')
|
||||
#
|
||||
# troubleshooting:
|
||||
# Errors detected within this userscript with have an exit of 231. All other exit
|
||||
# codes will come from curl or awk. To test that the kodi_rc file is set up
|
||||
# correctly, run the following command. It will display a 'It works!' notification within Kodi.
|
||||
#
|
||||
# source ~/.config/qutebrowser/kodi_rc ; curl --request POST "$HOST"/jsonrpc --header "Authorization: Basic $AUTH" --header "Content-Type: application/json" --data '{"id":1,"jsonrpc":"2.0","method":"GUI.ShowNotification","params":{"title":"It works!","message":"both HOST and AUTH are correct"}}'
|
||||
#
|
||||
# In case you miss the notification in Kodi the successful response is:
|
||||
#
|
||||
# {"id":1,"jsonrpc":"2.0","result":"OK"}
|
||||
#
|
||||
# Note, curl will display errors for some problems, but not all.
|
||||
|
||||
if [[ -z "$QUTE_FIFO" ]] ; then
|
||||
echo "This script is designed to run as a qutebrowser userscript, not as a standalone script."
|
||||
exit 231
|
||||
fi
|
||||
|
||||
# configuration loading adapted from the password_fill userscript
|
||||
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
|
||||
KODI_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/kodi_rc}
|
||||
if [[ -f "$KODI_CONFIG" ]] ; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$KODI_CONFIG"
|
||||
if [[ -z "$HOST" || -z "$AUTH" ]] ; then
|
||||
echo "message-error 'HOST and/or AUTH not set in $KODI_CONFIG'" > "$QUTE_FIFO"
|
||||
exit 231
|
||||
fi
|
||||
else
|
||||
echo "message-error '$KODI_CONFIG not found'" > "$QUTE_FIFO"
|
||||
exit 231
|
||||
fi
|
||||
|
||||
# get real URL from twitter links
|
||||
if [[ "$QUTE_URL" =~ ^https:\/\/t\.co ]] ; then
|
||||
QUTE_URL=$(curl -o /dev/null --silent --head --write-out '%{redirect_url}' "$QUTE_URL" )
|
||||
fi
|
||||
|
||||
# regex from https://github.com/dirkjanm/firefox-send-to-xbmc/blob/master/webextension/main.js
|
||||
if [[ "$QUTE_URL" =~ ^.*twitch.tv\/([a-zA-Z0-9_]+)$ ]] ; then
|
||||
NAME="${BASH_REMATCH[1]}"
|
||||
JSON='{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"plugin://plugin.video.twitch/?mode=play&channel_name='$NAME'"}},"id":"2"}'
|
||||
|
||||
elif [[ "$QUTE_URL" =~ ^.*twitch.tv\/videos\/([0-9]+)$ ]] ; then
|
||||
NAME="${BASH_REMATCH[1]}"
|
||||
JSON='{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"plugin://plugin.video.twitch/?mode=play&video_id='$NAME'"}},"id":"2"}'
|
||||
|
||||
elif [[ "$QUTE_URL" =~ ^.*vimeo.com\/([0-9]+) ]] ; then
|
||||
NAME="${BASH_REMATCH[1]}"
|
||||
JSON='{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"plugin://plugin.video.vimeo/play/?video_id='$NAME'"}},"id":"2"}'
|
||||
|
||||
elif [[ "$QUTE_URL" =~ ^.*youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=([^#\&\?]*).* ]] ; then
|
||||
NAME="${BASH_REMATCH[1]}"
|
||||
JSON='{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"plugin://plugin.video.youtube/play/?video_id='$NAME'"}},"id":"2"}'
|
||||
fi
|
||||
|
||||
if [[ "$JSON" ]] ; then
|
||||
curl \
|
||||
--request POST "$HOST"/jsonrpc \
|
||||
--header "Authorization: Basic $AUTH" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "$JSON" \
|
||||
--silent > /dev/null
|
||||
else
|
||||
URL=$(echo "$QUTE_URL" |awk -F/ '{print $3}')
|
||||
echo "message-warning 'kodi userscript does not support this $URL'" > "$QUTE_FIFO"
|
||||
fi
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
pngfile=$(mktemp --suffix=.png)
|
||||
trap 'rm -f "$pngfile"' EXIT
|
||||
|
||||
qrencode -t PNG -o "$pngfile" -s 10 "$QUTE_URL"
|
||||
echo ":open -t file:///$pngfile" >> "$QUTE_FIFO"
|
||||
sleep 1 # give qutebrowser time to open the file before it gets removed
|
||||
|
|
@ -57,7 +57,7 @@ const HEADER = `
|
|||
table,
|
||||
th,
|
||||
td {
|
||||
border: 1px solid currentColor;
|
||||
border: 1px solid grey;
|
||||
border-collapse: collapse;
|
||||
padding: 6px;
|
||||
vertical-align: top;
|
||||
|
|
@ -77,7 +77,7 @@ const HEADER = `
|
|||
background-color: #dddddd;
|
||||
}
|
||||
blockquote {
|
||||
border-inline-start: 2px solid #333333 !important;
|
||||
border-inline-start: 2px solid grey !important;
|
||||
padding: 0;
|
||||
padding-inline-start: 16px;
|
||||
margin-inline-start: 24px;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2020 Florian Bruhin (The Compiler)"
|
|||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version__ = "1.14.0"
|
||||
__version__ = "1.14.1"
|
||||
__version_info__ = tuple(int(part) for part in __version__.split('.'))
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ def run(args):
|
|||
q_app = Application(args)
|
||||
q_app.setOrganizationName("qutebrowser")
|
||||
q_app.setApplicationName("qutebrowser")
|
||||
q_app.setDesktopFileName("org.qutebrowser.qutebrowser")
|
||||
# Default DesktopFileName is org.qutebrowser.qutebrowser, set in `get_argparser()`
|
||||
q_app.setDesktopFileName(args.desktop_file_name)
|
||||
q_app.setApplicationVersion(qutebrowser.__version__)
|
||||
|
||||
if args.version:
|
||||
|
|
@ -491,8 +492,6 @@ def _init_modules(*, args):
|
|||
log.init.debug("Misc initialization...")
|
||||
macros.init()
|
||||
windowundo.init()
|
||||
# Init backend-specific stuff
|
||||
browsertab.init()
|
||||
|
||||
|
||||
class Application(QApplication):
|
||||
|
|
|
|||
|
|
@ -83,15 +83,6 @@ def create(win_id: int,
|
|||
parent=parent)
|
||||
|
||||
|
||||
def init() -> None:
|
||||
"""Initialize backend-specific modules."""
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
from qutebrowser.browser.webengine import webenginetab
|
||||
webenginetab.init()
|
||||
return
|
||||
assert objects.backend == usertypes.Backend.QtWebKit, objects.backend
|
||||
|
||||
|
||||
class WebTabError(Exception):
|
||||
|
||||
"""Base class for various errors."""
|
||||
|
|
|
|||
|
|
@ -1057,7 +1057,8 @@ class CommandDispatcher:
|
|||
verbose: Show notifications when the command started/exited.
|
||||
output: Show the output in a new tab.
|
||||
output_messages: Show the output as messages.
|
||||
detach: Whether the command should be detached from qutebrowser.
|
||||
detach: Detach the command from qutebrowser so that it continues
|
||||
running when qutebrowser quits.
|
||||
cmdline: The commandline to execute.
|
||||
count: Given to userscripts as $QUTE_COUNT.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -41,9 +41,12 @@ from qutebrowser.misc import objects
|
|||
gm_manager = cast('GreasemonkeyManager', None)
|
||||
|
||||
|
||||
def _scripts_dir():
|
||||
def _scripts_dirs():
|
||||
"""Get the directory of the scripts."""
|
||||
return os.path.join(standarddir.data(), 'greasemonkey')
|
||||
return [
|
||||
os.path.join(standarddir.data(), 'greasemonkey'),
|
||||
os.path.join(standarddir.config(), 'greasemonkey'),
|
||||
]
|
||||
|
||||
|
||||
class GreasemonkeyScript:
|
||||
|
|
@ -277,18 +280,19 @@ class GreasemonkeyManager(QObject):
|
|||
self._run_end = []
|
||||
self._run_idle = []
|
||||
|
||||
scripts_dir = os.path.abspath(_scripts_dir())
|
||||
log.greasemonkey.debug("Reading scripts from: {}".format(scripts_dir))
|
||||
for script_filename in glob.glob(os.path.join(scripts_dir, '*.js')):
|
||||
if not os.path.isfile(script_filename):
|
||||
continue
|
||||
script_path = os.path.join(scripts_dir, script_filename)
|
||||
with open(script_path, encoding='utf-8-sig') as script_file:
|
||||
script = GreasemonkeyScript.parse(script_file.read(),
|
||||
script_filename)
|
||||
if not script.name:
|
||||
script.name = script_filename
|
||||
self.add_script(script, force)
|
||||
for scripts_dir in _scripts_dirs():
|
||||
scripts_dir = os.path.abspath(scripts_dir)
|
||||
log.greasemonkey.debug("Reading scripts from: {}".format(scripts_dir))
|
||||
for script_filename in glob.glob(os.path.join(scripts_dir, '*.js')):
|
||||
if not os.path.isfile(script_filename):
|
||||
continue
|
||||
script_path = os.path.join(scripts_dir, script_filename)
|
||||
with open(script_path, encoding='utf-8-sig') as script_file:
|
||||
script = GreasemonkeyScript.parse(script_file.read(),
|
||||
script_filename)
|
||||
if not script.name:
|
||||
script.name = script_filename
|
||||
self.add_script(script, force)
|
||||
self.scripts_reloaded.emit()
|
||||
|
||||
def add_script(self, script, force=False):
|
||||
|
|
@ -325,7 +329,7 @@ class GreasemonkeyManager(QObject):
|
|||
log.greasemonkey.debug("Loaded script: {}".format(script.name))
|
||||
|
||||
def _required_url_to_file_path(self, url):
|
||||
requires_dir = os.path.join(_scripts_dir(), 'requires')
|
||||
requires_dir = os.path.join(_scripts_dirs()[0], 'requires')
|
||||
if not os.path.exists(requires_dir):
|
||||
os.mkdir(requires_dir)
|
||||
return os.path.join(requires_dir, utils.sanitize_filename(url))
|
||||
|
|
@ -426,7 +430,7 @@ def greasemonkey_reload(force=False):
|
|||
"""Re-read Greasemonkey scripts from disk.
|
||||
|
||||
The scripts are read from a 'greasemonkey' subdirectory in
|
||||
qutebrowser's data directory (see `:version`).
|
||||
qutebrowser's data or config directories (see `:version`).
|
||||
|
||||
Args:
|
||||
force: For any scripts that have required dependencies,
|
||||
|
|
@ -440,7 +444,8 @@ def init():
|
|||
global gm_manager
|
||||
gm_manager = GreasemonkeyManager()
|
||||
|
||||
try:
|
||||
os.mkdir(_scripts_dir())
|
||||
except FileExistsError:
|
||||
pass
|
||||
for scripts_dir in _scripts_dirs():
|
||||
try:
|
||||
os.mkdir(scripts_dir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ def javascript_confirm(url, js_msg, abort_on):
|
|||
raise CallSuper
|
||||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
js_msg)
|
||||
html.escape(js_msg))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
ans = message.ask('Javascript confirm', msg,
|
||||
mode=usertypes.PromptMode.yesno,
|
||||
|
|
@ -99,7 +99,7 @@ def javascript_prompt(url, js_msg, default, abort_on):
|
|||
return (False, "")
|
||||
|
||||
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
js_msg)
|
||||
html.escape(js_msg))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
answer = message.ask('Javascript prompt', msg,
|
||||
mode=usertypes.PromptMode.text,
|
||||
|
|
@ -122,7 +122,7 @@ def javascript_alert(url, js_msg, abort_on):
|
|||
return
|
||||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
js_msg)
|
||||
html.escape(js_msg))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
|
|
|
|||
|
|
@ -264,6 +264,16 @@ def _variant() -> Variant:
|
|||
|
||||
def settings() -> Iterator[Tuple[str, str]]:
|
||||
"""Get necessary blink settings to configure dark mode for QtWebEngine."""
|
||||
if (qtutils.version_check('5.15.2', compiled=False) and
|
||||
config.val.colors.webpage.prefers_color_scheme_dark):
|
||||
# With older Qt versions, this is passed in qtargs.py as --force-dark-mode
|
||||
# instead.
|
||||
#
|
||||
# With Chromium 85 (> Qt 5.15.2), the enumeration has changed in Blink and this
|
||||
# will need to be set to '0' instead:
|
||||
# https://chromium-review.googlesource.com/c/chromium/src/+/2232922
|
||||
yield "preferredColorScheme", "1"
|
||||
|
||||
if not config.val.colors.webpage.darkmode.enabled:
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -26,25 +26,35 @@ Module attributes:
|
|||
|
||||
import os
|
||||
import operator
|
||||
from typing import cast, Any, List, Optional, Tuple, Union
|
||||
from typing import cast, Any, List, Optional, Tuple, Union, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile
|
||||
|
||||
from qutebrowser.browser.webengine import spell, webenginequtescheme
|
||||
from qutebrowser.browser import history
|
||||
from qutebrowser.browser.webengine import (spell, webenginequtescheme, cookies,
|
||||
webenginedownloads)
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.config.websettings import AttributeInfo as Attr
|
||||
from qutebrowser.utils import standarddir, qtutils, message, log, urlmatch, usertypes
|
||||
from qutebrowser.utils import (standarddir, qtutils, message, log,
|
||||
urlmatch, usertypes, objreg)
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.browser.webengine import interceptor
|
||||
|
||||
# The default QWebEngineProfile
|
||||
default_profile = cast(QWebEngineProfile, None)
|
||||
# The QWebEngineProfile used for private (off-the-record) windows
|
||||
private_profile: Optional[QWebEngineProfile] = None
|
||||
# The global WebEngineSettings object
|
||||
global_settings = cast('WebEngineSettings', None)
|
||||
_global_settings = cast('WebEngineSettings', None)
|
||||
|
||||
parsed_user_agent = None
|
||||
|
||||
_qute_scheme_handler = cast(webenginequtescheme.QuteSchemeHandler, None)
|
||||
_req_interceptor = cast('interceptor.RequestInterceptor', None)
|
||||
_download_manager = cast(webenginedownloads.DownloadManager, None)
|
||||
|
||||
|
||||
class _SettingsWrapper:
|
||||
|
||||
|
|
@ -217,6 +227,26 @@ class ProfileSetter:
|
|||
|
||||
def __init__(self, profile):
|
||||
self._profile = profile
|
||||
self._name_to_method = {
|
||||
'content.cache.size': self.set_http_cache_size,
|
||||
'content.cookies.store': self.set_persistent_cookie_policy,
|
||||
'spellcheck.languages': self.set_dictionary_language,
|
||||
}
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
|
||||
# (note this isn't actually fixed properly before Qt 5.15)
|
||||
header_bug_fixed = qtutils.version_check('5.15', compiled=False)
|
||||
if header_bug_fixed:
|
||||
for name in ['user_agent', 'accept_language']:
|
||||
self._name_to_method[f'content.headers.{name}'] = self.set_http_headers
|
||||
|
||||
def update_setting(self, name):
|
||||
"""Update a setting based on its name."""
|
||||
try:
|
||||
meth = self._name_to_method[name]
|
||||
except KeyError:
|
||||
return
|
||||
meth()
|
||||
|
||||
def init_profile(self):
|
||||
"""Initialize settings on the given profile."""
|
||||
|
|
@ -267,20 +297,21 @@ class ProfileSetter:
|
|||
|
||||
def set_persistent_cookie_policy(self):
|
||||
"""Set the HTTP Cookie size for the given profile."""
|
||||
assert not self._profile.isOffTheRecord()
|
||||
if self._profile.isOffTheRecord():
|
||||
return
|
||||
if config.val.content.cookies.store:
|
||||
value = QWebEngineProfile.AllowPersistentCookies
|
||||
else:
|
||||
value = QWebEngineProfile.NoPersistentCookies
|
||||
self._profile.setPersistentCookiesPolicy(value)
|
||||
|
||||
def set_dictionary_language(self, warn=True):
|
||||
def set_dictionary_language(self):
|
||||
"""Load the given dictionaries."""
|
||||
filenames = []
|
||||
for code in config.val.spellcheck.languages or []:
|
||||
local_filename = spell.local_filename(code)
|
||||
if not local_filename:
|
||||
if warn:
|
||||
if not self._profile.isOffTheRecord():
|
||||
message.warning("Language {} is not installed - see "
|
||||
"scripts/dictcli.py in qutebrowser's "
|
||||
"sources".format(code))
|
||||
|
|
@ -295,28 +326,10 @@ class ProfileSetter:
|
|||
|
||||
def _update_settings(option):
|
||||
"""Update global settings when qwebsettings changed."""
|
||||
global_settings.update_setting(option)
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
|
||||
# (note this isn't actually fixed properly before Qt 5.15)
|
||||
header_bug_fixed = qtutils.version_check('5.15', compiled=False)
|
||||
|
||||
if option in ['content.headers.user_agent',
|
||||
'content.headers.accept_language'] and header_bug_fixed:
|
||||
default_profile.setter.set_http_headers()
|
||||
if private_profile:
|
||||
private_profile.setter.set_http_headers()
|
||||
elif option == 'content.cache.size':
|
||||
default_profile.setter.set_http_cache_size()
|
||||
if private_profile:
|
||||
private_profile.setter.set_http_cache_size()
|
||||
elif option == 'content.cookies.store':
|
||||
default_profile.setter.set_persistent_cookie_policy()
|
||||
# We're not touching the private profile's cookie policy.
|
||||
elif option == 'spellcheck.languages':
|
||||
default_profile.setter.set_dictionary_language()
|
||||
if private_profile:
|
||||
private_profile.setter.set_dictionary_language(warn=False)
|
||||
_global_settings.update_setting(option)
|
||||
default_profile.setter.update_setting(option)
|
||||
if private_profile:
|
||||
private_profile.setter.update_setting(option)
|
||||
|
||||
|
||||
def _init_user_agent_str(ua):
|
||||
|
|
@ -328,33 +341,54 @@ def init_user_agent():
|
|||
_init_user_agent_str(QWebEngineProfile.defaultProfile().httpUserAgent())
|
||||
|
||||
|
||||
def _init_profile(profile: QWebEngineProfile) -> None:
|
||||
"""Initialize a new QWebEngineProfile.
|
||||
|
||||
This currently only contains the steps which are shared between a private and a
|
||||
non-private profile (at the moment, only the default profile).
|
||||
"""
|
||||
profile.setter = ProfileSetter(profile) # type: ignore[attr-defined]
|
||||
profile.setter.init_profile()
|
||||
|
||||
_qute_scheme_handler.install(profile)
|
||||
_req_interceptor.install(profile)
|
||||
_download_manager.install(profile)
|
||||
cookies.install_filter(profile)
|
||||
|
||||
# Clear visited links on web history clear
|
||||
history.web_history.history_cleared.connect(profile.clearAllVisitedLinks)
|
||||
history.web_history.url_cleared.connect(
|
||||
lambda url: profile.clearVisitedLinks([url]))
|
||||
|
||||
_global_settings.init_settings()
|
||||
|
||||
|
||||
def _init_default_profile():
|
||||
"""Init the default QWebEngineProfile."""
|
||||
global default_profile
|
||||
|
||||
default_profile = QWebEngineProfile.defaultProfile()
|
||||
|
||||
init_user_agent()
|
||||
|
||||
default_profile.setter = ProfileSetter( # type: ignore[attr-defined]
|
||||
default_profile)
|
||||
default_profile.setCachePath(
|
||||
os.path.join(standarddir.cache(), 'webengine'))
|
||||
default_profile.setPersistentStoragePath(
|
||||
os.path.join(standarddir.data(), 'webengine'))
|
||||
default_profile.setter.init_profile()
|
||||
default_profile.setter.set_persistent_cookie_policy()
|
||||
|
||||
_init_profile(default_profile)
|
||||
|
||||
|
||||
def init_private_profile():
|
||||
"""Init the private QWebEngineProfile."""
|
||||
global private_profile
|
||||
|
||||
if not qtutils.is_single_process():
|
||||
private_profile = QWebEngineProfile()
|
||||
private_profile.setter = ProfileSetter( # type: ignore[attr-defined]
|
||||
private_profile)
|
||||
assert private_profile.isOffTheRecord()
|
||||
private_profile.setter.init_profile()
|
||||
if qtutils.is_single_process():
|
||||
return
|
||||
|
||||
private_profile = QWebEngineProfile()
|
||||
assert private_profile.isOffTheRecord()
|
||||
_init_profile(private_profile)
|
||||
|
||||
|
||||
def _init_site_specific_quirks():
|
||||
|
|
@ -430,14 +464,33 @@ def init():
|
|||
webenginequtescheme.init()
|
||||
spell.init()
|
||||
|
||||
# For some reason we need to keep a reference, otherwise the scheme handler
|
||||
# won't work...
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
|
||||
global _qute_scheme_handler
|
||||
app = QApplication.instance()
|
||||
log.init.debug("Initializing qute://* handler...")
|
||||
_qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app)
|
||||
|
||||
global _req_interceptor
|
||||
log.init.debug("Initializing request interceptor...")
|
||||
from qutebrowser.browser.webengine import interceptor
|
||||
_req_interceptor = interceptor.RequestInterceptor(parent=app)
|
||||
|
||||
global _download_manager
|
||||
log.init.debug("Initializing QtWebEngine downloads...")
|
||||
_download_manager = webenginedownloads.DownloadManager(parent=app)
|
||||
objreg.register('webengine-download-manager', _download_manager)
|
||||
from qutebrowser.misc import quitter
|
||||
quitter.instance.shutting_down.connect(_download_manager.shutdown)
|
||||
|
||||
global _global_settings
|
||||
_global_settings = WebEngineSettings(_SettingsWrapper())
|
||||
|
||||
_init_default_profile()
|
||||
init_private_profile()
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
global global_settings
|
||||
global_settings = WebEngineSettings(_SettingsWrapper())
|
||||
global_settings.init_settings()
|
||||
|
||||
_init_site_specific_quirks()
|
||||
_init_devtools_settings()
|
||||
|
||||
|
|
|
|||
|
|
@ -27,68 +27,19 @@ from typing import cast, Union
|
|||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl, QObject
|
||||
from PyQt5.QtNetwork import QAuthenticator
|
||||
from PyQt5.QtWidgets import QApplication, QWidget
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineHistory
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.browser import (browsertab, eventfilter, shared, webelem,
|
||||
history, greasemonkey)
|
||||
from qutebrowser.browser import browsertab, eventfilter, shared, webelem, greasemonkey
|
||||
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
||||
interceptor, webenginequtescheme,
|
||||
cookies, webenginedownloads,
|
||||
webenginesettings, certificateerror)
|
||||
from qutebrowser.misc import miscwidgets, objects, quitter
|
||||
from qutebrowser.misc import miscwidgets, objects
|
||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||
message, objreg, jinja, debug)
|
||||
message, jinja, debug)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
_qute_scheme_handler = None
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize QtWebEngine-specific modules."""
|
||||
# For some reason we need to keep a reference, otherwise the scheme handler
|
||||
# won't work...
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
|
||||
global _qute_scheme_handler
|
||||
|
||||
app = QApplication.instance()
|
||||
log.init.debug("Initializing qute://* handler...")
|
||||
_qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app)
|
||||
_qute_scheme_handler.install(webenginesettings.default_profile)
|
||||
if webenginesettings.private_profile:
|
||||
_qute_scheme_handler.install(webenginesettings.private_profile)
|
||||
|
||||
log.init.debug("Initializing request interceptor...")
|
||||
req_interceptor = interceptor.RequestInterceptor(parent=app)
|
||||
req_interceptor.install(webenginesettings.default_profile)
|
||||
if webenginesettings.private_profile:
|
||||
req_interceptor.install(webenginesettings.private_profile)
|
||||
|
||||
log.init.debug("Initializing QtWebEngine downloads...")
|
||||
download_manager = webenginedownloads.DownloadManager(parent=app)
|
||||
download_manager.install(webenginesettings.default_profile)
|
||||
if webenginesettings.private_profile:
|
||||
download_manager.install(webenginesettings.private_profile)
|
||||
objreg.register('webengine-download-manager', download_manager)
|
||||
quitter.instance.shutting_down.connect(download_manager.shutdown)
|
||||
|
||||
log.init.debug("Initializing cookie filter...")
|
||||
cookies.install_filter(webenginesettings.default_profile)
|
||||
if webenginesettings.private_profile:
|
||||
cookies.install_filter(webenginesettings.private_profile)
|
||||
|
||||
# Clear visited links on web history clear
|
||||
for p in [webenginesettings.default_profile,
|
||||
webenginesettings.private_profile]:
|
||||
if not p:
|
||||
continue
|
||||
history.web_history.history_cleared.connect(p.clearAllVisitedLinks)
|
||||
history.web_history.url_cleared.connect(
|
||||
lambda url, profile=p: profile.clearVisitedLinks([url]))
|
||||
|
||||
|
||||
# Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs.
|
||||
_JS_WORLD_MAP = {
|
||||
usertypes.JsWorld.main: QWebEngineScript.MainWorld,
|
||||
|
|
|
|||
|
|
@ -395,6 +395,7 @@ def _lookup_path(cmd):
|
|||
directories = [
|
||||
os.path.join(standarddir.data(), "userscripts"),
|
||||
os.path.join(standarddir.data(system=True), "userscripts"),
|
||||
os.path.join(standarddir.config(), "userscripts"),
|
||||
]
|
||||
for directory in directories:
|
||||
cmd_path = os.path.join(directory, cmd)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import re
|
||||
from typing import Iterable, Tuple
|
||||
|
||||
from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp
|
||||
from PyQt5.QtCore import QSortFilterProxyModel, QRegularExpression
|
||||
from PyQt5.QtGui import QStandardItem, QStandardItemModel
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
||||
|
|
@ -63,9 +63,9 @@ class ListCategory(QSortFilterProxyModel):
|
|||
val = re.sub(r' +', r' ', val) # See #1919
|
||||
val = re.escape(val)
|
||||
val = val.replace(r'\ ', '.*')
|
||||
rx = QRegExp(val, Qt.CaseInsensitive)
|
||||
rx = QRegularExpression(val, QRegularExpression.CaseInsensitiveOption)
|
||||
qtutils.ensure_valid(rx)
|
||||
self.setFilterRegExp(rx)
|
||||
self.setFilterRegularExpression(rx)
|
||||
self.invalidate()
|
||||
sortcol = 0
|
||||
self.sort(sortcol)
|
||||
|
|
|
|||
|
|
@ -203,12 +203,18 @@ def _qtwebengine_settings_args() -> Iterator[str]:
|
|||
}
|
||||
}
|
||||
|
||||
referrer_setting = settings['content.headers.referer']
|
||||
if qtutils.version_check('5.14', compiled=False):
|
||||
if (qtutils.version_check('5.14', compiled=False) and
|
||||
not qtutils.version_check('5.15.2', compiled=False)):
|
||||
# In Qt 5.14 to 5.15.1, `--force-dark-mode` is used to set the
|
||||
# preferred colorscheme. In Qt 5.15.2, this is handled by a
|
||||
# blink-setting instead.
|
||||
settings['colors.webpage.prefers_color_scheme_dark'] = {
|
||||
True: '--force-dark-mode',
|
||||
False: None,
|
||||
}
|
||||
|
||||
referrer_setting = settings['content.headers.referer']
|
||||
if qtutils.version_check('5.14', compiled=False):
|
||||
# Starting with Qt 5.14, this is handled via --enable-features
|
||||
referrer_setting['same-domain'] = None
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -211,6 +211,10 @@ def _check_modules(modules):
|
|||
), log.py_warning_filter(
|
||||
category=DeprecationWarning,
|
||||
message=r'the imp module is deprecated',
|
||||
), log.py_warning_filter(
|
||||
# WORKAROUND for https://github.com/pypa/setuptools/issues/2466
|
||||
category=DeprecationWarning,
|
||||
message=r'Creating a LegacyVersion has been deprecated',
|
||||
):
|
||||
# pylint: enable=bad-continuation
|
||||
importlib.import_module(name)
|
||||
|
|
|
|||
|
|
@ -42,15 +42,17 @@ from qutebrowser.qt import sip
|
|||
|
||||
@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True)
|
||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||
def later(ms: int, command: str, win_id: int) -> None:
|
||||
def later(duration: str, command: str, win_id: int) -> None:
|
||||
"""Execute a command after some time.
|
||||
|
||||
Args:
|
||||
ms: How many milliseconds to wait.
|
||||
duration: Duration to wait in format XhYmZs or a number for milliseconds.
|
||||
command: The command to run, with optional args.
|
||||
"""
|
||||
if ms < 0:
|
||||
raise cmdutils.CommandError("I can't run something in the past!")
|
||||
try:
|
||||
ms = utils.parse_duration(duration)
|
||||
except ValueError as e:
|
||||
raise cmdutils.CommandError(e)
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
timer = usertypes.Timer(name='later', parent=QApplication.instance())
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -85,6 +85,11 @@ def get_argparser():
|
|||
|
||||
parser.add_argument('--json-args', help=argparse.SUPPRESS)
|
||||
parser.add_argument('--temp-basedir-restarted', help=argparse.SUPPRESS)
|
||||
parser.add_argument('--desktop-file-name',
|
||||
default="org.qutebrowser.qutebrowser",
|
||||
help="Set the base name of the desktop entry for this "
|
||||
"application. Used to set the app_id under Wayland. See "
|
||||
"https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop")
|
||||
|
||||
debug = parser.add_argument_group('debug arguments')
|
||||
debug.add_argument('-l', '--loglevel', dest='loglevel',
|
||||
|
|
|
|||
|
|
@ -118,8 +118,7 @@ class Environment(jinja2.Environment):
|
|||
def _data_url(self, path: str) -> str:
|
||||
"""Get a data: url for the broken qutebrowser logo."""
|
||||
data = utils.read_file(path, binary=True)
|
||||
filename = utils.resource_filename(path)
|
||||
mimetype = utils.guess_mimetype(filename)
|
||||
mimetype = utils.guess_mimetype(path)
|
||||
return urlutils.data_url(mimetype, data).toString()
|
||||
|
||||
def getattr(self, obj: Any, attribute: str) -> Any:
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ from typing import (Any, Callable, IO, Iterator, Optional, Sequence, Tuple, Type
|
|||
from PyQt5.QtCore import QUrl, QVersionNumber
|
||||
from PyQt5.QtGui import QClipboard, QDesktopServices
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
# We cannot use the stdlib version on 3.7-3.8 because we need the files() API.
|
||||
if sys.version_info >= (3, 9):
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
import pkg_resources
|
||||
import yaml
|
||||
try:
|
||||
|
|
@ -69,7 +74,7 @@ is_posix = os.name == 'posix'
|
|||
|
||||
try:
|
||||
# Protocol was added in Python 3.8
|
||||
from typing import Protocol
|
||||
from typing import Protocol # pylint: disable=ungrouped-imports
|
||||
except ImportError: # pragma: no cover
|
||||
if not TYPE_CHECKING:
|
||||
class Protocol:
|
||||
|
|
@ -216,13 +221,12 @@ def read_file(filename: str, binary: bool = False) -> Any:
|
|||
with open(fn, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
else:
|
||||
data = pkg_resources.resource_string(
|
||||
qutebrowser.__name__, filename)
|
||||
p = importlib_resources.files(qutebrowser) / filename
|
||||
|
||||
if binary:
|
||||
return data
|
||||
return p.read_bytes()
|
||||
|
||||
return data.decode('UTF-8')
|
||||
return p.read_text()
|
||||
|
||||
|
||||
def resource_filename(filename: str) -> str:
|
||||
|
|
@ -773,3 +777,30 @@ def libgl_workaround() -> None:
|
|||
libgl = ctypes.util.find_library("GL")
|
||||
if libgl is not None: # pragma: no branch
|
||||
ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
|
||||
|
||||
|
||||
def parse_duration(duration: str) -> int:
|
||||
"""Parse duration in format XhYmZs into milliseconds duration."""
|
||||
if duration.isdigit():
|
||||
# For backward compatibility return milliseconds
|
||||
return int(duration)
|
||||
|
||||
match = re.fullmatch(
|
||||
r'(?P<hours>[0-9]+(\.[0-9])?h)?\s*'
|
||||
r'(?P<minutes>[0-9]+(\.[0-9])?m)?\s*'
|
||||
r'(?P<seconds>[0-9]+(\.[0-9])?s)?',
|
||||
duration
|
||||
)
|
||||
if not match or not match.group(0):
|
||||
raise ValueError(
|
||||
f"Invalid duration: {duration} - "
|
||||
"expected XhYmZs or a number of milliseconds"
|
||||
)
|
||||
seconds_string = match.group('seconds') if match.group('seconds') else '0'
|
||||
seconds = float(seconds_string.rstrip('s'))
|
||||
minutes_string = match.group('minutes') if match.group('minutes') else '0'
|
||||
minutes = float(minutes_string.rstrip('m'))
|
||||
hours_string = match.group('hours') if match.group('hours') else '0'
|
||||
hours = float(hours_string.rstrip('h'))
|
||||
milliseconds = int((seconds + minutes * 60 + hours * 3600) * 1000)
|
||||
return milliseconds
|
||||
|
|
|
|||
|
|
@ -362,6 +362,7 @@ MODULE_INFO: Mapping[str, ModuleInfo] = collections.OrderedDict([
|
|||
('yaml', ['__version__']),
|
||||
('adblock', ['__version__'], "0.3.2"),
|
||||
('attr', ['__version__']),
|
||||
('importlib_resources', []),
|
||||
('PyQt5.QtWebEngineWidgets', []),
|
||||
('PyQt5.QtWebEngine', ['PYQT_WEBENGINE_VERSION_STR']),
|
||||
('PyQt5.QtWebKitWidgets', []),
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
adblock==0.3.2
|
||||
attrs==20.3.0
|
||||
colorama==0.4.4
|
||||
importlib-resources==3.3.0 ; python_version<"3.9"
|
||||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
Pygments==2.7.2
|
||||
Pygments==2.7.3
|
||||
pyPEG2==2.15.2
|
||||
PyYAML==5.3.1
|
||||
|
|
|
|||
|
|
@ -116,7 +116,13 @@ def smoke_test(executable):
|
|||
(r'\[.*:ERROR:mach_port_broker.mm\(48\)\] bootstrap_look_up '
|
||||
r'org\.chromium\.Chromium\.rohitfork\.1: Permission denied \(1100\)'),
|
||||
(r'\[.*:ERROR:mach_port_broker.mm\(43\)\] bootstrap_look_up: '
|
||||
r'Unknown service name \(1102\)')
|
||||
r'Unknown service name \(1102\)'),
|
||||
|
||||
# Windows N:
|
||||
# https://github.com/microsoft/playwright/issues/2901
|
||||
(r'\[.*:ERROR:dxva_video_decode_accelerator_win.cc\(\d+\)\] '
|
||||
r'DXVAVDA fatal error: could not LoadLibrary: .*: The specified '
|
||||
r'module could not be found. \(0x7E\)'),
|
||||
]
|
||||
|
||||
proc = subprocess.run([executable, '--no-err-windows', '--nowindow',
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@ def main_check():
|
|||
print("or check https://codecov.io/github/qutebrowser/qutebrowser")
|
||||
print()
|
||||
|
||||
if 'CI' in os.environ:
|
||||
if scriptutils.ON_CI:
|
||||
print("Keeping coverage.xml on CI.")
|
||||
else:
|
||||
os.remove('coverage.xml')
|
||||
|
|
|
|||
|
|
@ -24,4 +24,4 @@ WORKDIR /home/user
|
|||
|
||||
CMD git clone /outside qutebrowser.git && \
|
||||
cd qutebrowser.git && \
|
||||
tox -e py38
|
||||
tox -e py
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ MATCHERS = {
|
|||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": r'^([^:]+):(\d+): (Found .*)',
|
||||
"regexp": r'^([^:]+):(\d+): \033\[34m(Found .*)\033\[0m',
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"message": 3,
|
||||
|
|
|
|||
|
|
@ -145,8 +145,8 @@ def _check_spelling_file(path, fobj, patterns):
|
|||
for pattern, explanation in patterns:
|
||||
if pattern.search(line):
|
||||
ok = False
|
||||
print(f'{path}:{num}: Found "{pattern.pattern}" - ', end='')
|
||||
utils.print_col(explanation, 'blue')
|
||||
print(f'{path}:{num}: ', end='')
|
||||
utils.print_col(f'Found "{pattern.pattern}" - {explanation}', 'blue')
|
||||
return ok
|
||||
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ def check_spelling(args: argparse.Namespace) -> Optional[bool]:
|
|||
"'type: ignore[error-code]' instead."),
|
||||
),
|
||||
(
|
||||
re.compile(r'# type: (?!ignore\[)'),
|
||||
re.compile(r'# type: (?!ignore(\[|$))'),
|
||||
"Don't use type comments, use type annotations instead.",
|
||||
),
|
||||
(
|
||||
|
|
@ -274,12 +274,35 @@ def check_userscripts_descriptions(_args: argparse.Namespace = None) -> bool:
|
|||
return ok
|
||||
|
||||
|
||||
def check_userscript_shebangs(_args: argparse.Namespace) -> bool:
|
||||
"""Check that we're using /usr/bin/env in shebangs."""
|
||||
ok = True
|
||||
folder = pathlib.Path('misc/userscripts')
|
||||
|
||||
for sub in folder.iterdir():
|
||||
if sub.is_dir() or sub.name == 'README.md':
|
||||
continue
|
||||
|
||||
with sub.open('r', encoding='utf-8') as f:
|
||||
shebang = f.readline()
|
||||
assert shebang.startswith('#!'), shebang
|
||||
binary = shebang.split()[0][2:]
|
||||
|
||||
if binary not in ['/bin/sh', '/usr/bin/env']:
|
||||
bin_name = pathlib.Path(binary).name
|
||||
print(f"In {sub}, use #!/usr/bin/env {bin_name} instead of #!{binary}")
|
||||
ok = False
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
def main() -> int:
|
||||
checkers = {
|
||||
'git': check_git,
|
||||
'vcs': check_vcs_conflict,
|
||||
'spelling': check_spelling,
|
||||
'userscripts': check_userscripts_descriptions,
|
||||
'userscript-descriptions': check_userscripts_descriptions,
|
||||
'userscript-shebangs': check_userscript_shebangs,
|
||||
'changelog-urls': check_changelog_urls,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ CHANGELOG_URLS = {
|
|||
'pytest-bdd': 'https://github.com/pytest-dev/pytest-bdd/blob/master/CHANGES.rst',
|
||||
'snowballstemmer': 'https://github.com/snowballstem/snowball/blob/master/NEWS',
|
||||
'virtualenv': 'https://virtualenv.pypa.io/en/latest/changelog.html',
|
||||
'packaging': 'https://pypi.org/project/packaging/',
|
||||
'packaging': 'https://packaging.pypa.io/en/latest/changelog.html',
|
||||
'build': 'https://github.com/pypa/build/commits/master',
|
||||
'attrs': 'http://www.attrs.org/en/stable/changelog.html',
|
||||
'Jinja2': 'https://github.com/pallets/jinja/blob/master/CHANGES.rst',
|
||||
|
|
@ -130,7 +130,7 @@ CHANGELOG_URLS = {
|
|||
'six': 'https://github.com/benjaminp/six/blob/master/CHANGES',
|
||||
'altgraph': 'https://github.com/ronaldoussoren/altgraph/blob/master/doc/changelog.rst',
|
||||
'urllib3': 'https://github.com/urllib3/urllib3/blob/master/CHANGES.rst',
|
||||
'lxml': 'https://lxml.de/4.6/changes-4.6.0.html',
|
||||
'lxml': 'https://lxml.de/index.html#old-versions',
|
||||
'jwcrypto': 'https://github.com/latchset/jwcrypto/commits/master',
|
||||
'wrapt': 'https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst',
|
||||
'pep517': 'https://github.com/pypa/pep517/blob/master/doc/changelog.rst',
|
||||
|
|
@ -175,6 +175,7 @@ CHANGELOG_URLS = {
|
|||
'pyroma': 'https://github.com/regebro/pyroma/blob/master/HISTORY.txt',
|
||||
'adblock': 'https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md',
|
||||
'pyPEG2': None,
|
||||
'importlib-resources': 'https://importlib-resources.readthedocs.io/en/latest/changelog%20%28links%29.html',
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -262,13 +263,17 @@ def get_all_names():
|
|||
yield basename[len('requirements-'):-len('.txt-raw')]
|
||||
|
||||
|
||||
def run_pip(venv_dir, *args, **kwargs):
|
||||
def run_pip(venv_dir, *args, quiet=False, **kwargs):
|
||||
"""Run pip inside the virtualenv."""
|
||||
args = list(args)
|
||||
if quiet:
|
||||
args.insert(1, '-q')
|
||||
|
||||
arg_str = ' '.join(str(arg) for arg in args)
|
||||
utils.print_col('venv$ pip {}'.format(arg_str), 'blue')
|
||||
|
||||
venv_python = os.path.join(venv_dir, 'bin', 'python')
|
||||
return subprocess.run([venv_python, '-m', 'pip'] + list(args),
|
||||
check=True, **kwargs)
|
||||
return subprocess.run([venv_python, '-m', 'pip'] + args, check=True, **kwargs)
|
||||
|
||||
|
||||
def init_venv(host_python, venv_dir, requirements, pre=False):
|
||||
|
|
@ -277,8 +282,8 @@ def init_venv(host_python, venv_dir, requirements, pre=False):
|
|||
utils.print_col('$ python3 -m venv {}'.format(venv_dir), 'blue')
|
||||
subprocess.run([host_python, '-m', 'venv', venv_dir], check=True)
|
||||
|
||||
run_pip(venv_dir, 'install', '-U', 'pip')
|
||||
run_pip(venv_dir, 'install', '-U', 'setuptools', 'wheel')
|
||||
run_pip(venv_dir, 'install', '-U', 'pip', quiet=not utils.ON_CI)
|
||||
run_pip(venv_dir, 'install', '-U', 'setuptools', 'wheel', quiet=not utils.ON_CI)
|
||||
|
||||
install_command = ['install', '-r', requirements]
|
||||
if pre:
|
||||
|
|
@ -292,6 +297,8 @@ def init_venv(host_python, venv_dir, requirements, pre=False):
|
|||
def parse_args():
|
||||
"""Parse commandline arguments via argparse."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--force-test', help="Force running environment tests",
|
||||
action='store_true')
|
||||
parser.add_argument('names', nargs='*')
|
||||
return parser.parse_args()
|
||||
|
||||
|
|
@ -358,6 +365,7 @@ def _get_changed_files():
|
|||
def parse_versioned_line(line):
|
||||
"""Parse a requirements.txt line into name/version."""
|
||||
if '==' in line:
|
||||
line = line.rsplit('#', maxsplit=1)[0] # Strip comments
|
||||
name, version = line.split('==')
|
||||
if ';' in version: # pip environment markers
|
||||
version = version.split(';')[0].strip()
|
||||
|
|
@ -412,7 +420,7 @@ def print_changed_files():
|
|||
utils.print_subtitle('Diff')
|
||||
print(diff_text)
|
||||
|
||||
if 'CI' in os.environ:
|
||||
if utils.ON_CI:
|
||||
print()
|
||||
print('::set-output name=changed::' +
|
||||
files_text.replace('\n', '%0A'))
|
||||
|
|
@ -481,7 +489,6 @@ def build_requirements(name):
|
|||
|
||||
def test_tox():
|
||||
"""Test requirements via tox."""
|
||||
utils.print_title('Testing via tox')
|
||||
host_python = get_host_python('tox')
|
||||
req_path = os.path.join(REQ_DIR, 'requirements-tox.txt')
|
||||
|
||||
|
|
@ -506,11 +513,15 @@ def test_tox():
|
|||
check=True)
|
||||
|
||||
|
||||
def test_requirements(name, outfile):
|
||||
def test_requirements(name, outfile, *, force=False):
|
||||
"""Test a resulting requirements file."""
|
||||
print()
|
||||
utils.print_subtitle("Testing")
|
||||
|
||||
if name not in _get_changed_files() and not force:
|
||||
print(f"Skipping test as there were no changes for {name}.")
|
||||
return
|
||||
|
||||
host_python = get_host_python(name)
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
init_venv(host_python, tmpdir, outfile)
|
||||
|
|
@ -528,11 +539,16 @@ def main():
|
|||
for name in names:
|
||||
utils.print_title(name)
|
||||
outfile = build_requirements(name)
|
||||
test_requirements(name, outfile)
|
||||
test_requirements(name, outfile, force=args.force_test)
|
||||
|
||||
if not args.names:
|
||||
utils.print_title('Testing via tox')
|
||||
if args.names and not args.force_test:
|
||||
# If we selected a subset, let's not go through the trouble of testing
|
||||
# via tox.
|
||||
print("Skipping: Selected a subset only")
|
||||
elif not _get_changed_files() and not args.force_test:
|
||||
print("Skipping: No changes")
|
||||
else:
|
||||
test_tox()
|
||||
|
||||
print_changed_files()
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ if __name__ == "__main__":
|
|||
.format(v=version))
|
||||
print("* Windows: git fetch; git checkout v{v}; "
|
||||
"py -3.7 -m tox -e build-release -- --asciidoc "
|
||||
"$env:userprofile\\bin\\asciidoc-9.0.2\\asciidoc.py --upload"
|
||||
"$env:userprofile\\bin\\asciidoc-9.0.4\\asciidoc.py --upload"
|
||||
.format(v=version))
|
||||
print("* macOS: git fetch && git checkout v{v} && "
|
||||
"tox -e build-release -- --upload"
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ def parse_args(argv: List[str] = None) -> argparse.Namespace:
|
|||
parser.add_argument('--skip-docs',
|
||||
action='store_true',
|
||||
help="Skip doc generation.")
|
||||
parser.add_argument('--skip-smoke-test',
|
||||
action='store_true',
|
||||
help="Skip Qt smoke test.")
|
||||
parser.add_argument('--tox-error',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
|
|
@ -296,12 +299,19 @@ def apply_xcb_util_workaround(
|
|||
def _find_libs() -> Dict[Tuple[str, str], List[str]]:
|
||||
"""Find all system-wide .so libraries."""
|
||||
all_libs: Dict[Tuple[str, str], List[str]] = {}
|
||||
|
||||
if pathlib.Path("/sbin/ldconfig").exists():
|
||||
# /sbin might not be in PATH on e.g. Debian
|
||||
ldconfig_bin = "/sbin/ldconfig"
|
||||
else:
|
||||
ldconfig_bin = "ldconfig"
|
||||
ldconfig_proc = subprocess.run(
|
||||
['ldconfig', '-p'],
|
||||
[ldconfig_bin, '-p'],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
encoding=sys.getfilesystemencoding(),
|
||||
)
|
||||
|
||||
pattern = re.compile(r'(?P<name>\S+) \((?P<abi_type>[^)]+)\) => (?P<path>.*)')
|
||||
for line in ldconfig_proc.stdout.splitlines():
|
||||
match = pattern.fullmatch(line.strip())
|
||||
|
|
@ -421,7 +431,7 @@ def run(args) -> None:
|
|||
raise AssertionError
|
||||
|
||||
apply_xcb_util_workaround(venv_dir, args.pyqt_type, args.pyqt_version)
|
||||
if args.pyqt_type != 'skip':
|
||||
if args.pyqt_type != 'skip' and not args.skip_smoke_test:
|
||||
run_qt_smoke_test(venv_dir)
|
||||
|
||||
install_requirements(venv_dir)
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -71,7 +71,8 @@ try:
|
|||
entry_points={'gui_scripts':
|
||||
['qutebrowser = qutebrowser.qutebrowser:main']},
|
||||
zip_safe=True,
|
||||
install_requires=['pypeg2', 'jinja2', 'pygments', 'PyYAML', 'attrs'],
|
||||
install_requires=['pypeg2', 'jinja2', 'pygments', 'PyYAML', 'attrs',
|
||||
'importlib_resources>=1.1.0; python_version < "3.9"'],
|
||||
python_requires='>=3.6',
|
||||
name='qutebrowser',
|
||||
version=_get_constant('version'),
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ def check_yaml_c_exts():
|
|||
Not available yet with a nightly Python, see:
|
||||
https://github.com/yaml/pyyaml/issues/416
|
||||
"""
|
||||
if 'CI' in os.environ and sys.version_info[:2] != (3, 10):
|
||||
if testutils.ON_CI and sys.version_info[:2] != (3, 10):
|
||||
from yaml import CLoader
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>External logo</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<b>NOTE:</> This should never be used in a test where
|
||||
qutebrowser.org isn't blocked, as no network requests should be
|
||||
made while running the testsuite.
|
||||
</p>
|
||||
<img src="https://qutebrowser.org/icons/qutebrowser.svg">
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1 @@
|
|||
qutebrowser.org
|
||||
|
|
@ -199,3 +199,49 @@ Feature: Using private browsing
|
|||
- history:
|
||||
- active: true
|
||||
url: http://localhost:*/data/numbers/5.txt
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/5810
|
||||
|
||||
Scenario: Using qute:// scheme after reiniting private profile
|
||||
When I open about:blank in a private window
|
||||
And I run :close
|
||||
And I open qute://version in a private window
|
||||
Then the page should contain the plaintext "Version info"
|
||||
|
||||
Scenario: Downloading after reiniting private profile
|
||||
When I open about:blank in a private window
|
||||
And I run :close
|
||||
And I open data/downloads/downloads.html in a private window
|
||||
And I run :click-element id download
|
||||
And I wait for "*PromptMode.download*" in the log
|
||||
And I run :leave-mode
|
||||
Then "Removed download *: download.bin *" should be logged
|
||||
|
||||
Scenario: Adblocking after reiniting private profile
|
||||
When I open about:blank in a private window
|
||||
And I run :close
|
||||
And I set content.host_blocking.lists to ["http://localhost:(port)/data/adblock/qutebrowser"]
|
||||
And I run :adblock-update
|
||||
And I wait for the message "adblock: Read 1 hosts from 1 sources."
|
||||
And I open data/adblock/external_logo.html in a private window
|
||||
Then "Request to qutebrowser.org blocked by host blocker." should be logged
|
||||
|
||||
@pyqt!=5.15.0 # cookie filtering is broken on QtWebEngine 5.15.0
|
||||
Scenario: Cookie filtering after reiniting private profile
|
||||
When I open about:blank in a private window
|
||||
And I run :close
|
||||
And I set content.cookies.accept to never
|
||||
And I open data/title.html in a private window
|
||||
And I open cookies/set?unsuccessful-cookie=1 without waiting in a new tab
|
||||
And I wait until cookies is loaded
|
||||
And I open cookies
|
||||
Then the cookie unsuccessful-cookie should not be set
|
||||
|
||||
Scenario: Disabling JS after reiniting private profile
|
||||
When I open about:blank in a new window
|
||||
And I run :window-only
|
||||
And I set content.javascript.enabled to false
|
||||
And I open about:blank in a private window
|
||||
And I run :close
|
||||
And I open data/javascript/enabled.html in a private window
|
||||
Then the page should contain the plaintext "JavaScript is disabled"
|
||||
|
|
|
|||
|
|
@ -691,7 +691,7 @@ class QuteProc(testprocess.Process):
|
|||
is_dl_inconsistency = str(self.captured_log[-1]).endswith(
|
||||
"_dl_allocate_tls_init: Assertion "
|
||||
"`listp->slotinfo[cnt].gen <= GL(dl_tls_generation)' failed!")
|
||||
if 'CI' in os.environ and is_dl_inconsistency:
|
||||
if testutils.ON_CI and is_dl_inconsistency:
|
||||
# WORKAROUND for https://sourceware.org/bugzilla/show_bug.cgi?id=19329
|
||||
self.captured_log = []
|
||||
self._log("NOTE: Restarted after libc DL inconsistency!")
|
||||
|
|
@ -809,7 +809,7 @@ class QuteProc(testprocess.Process):
|
|||
testprocess.WaitForTimeout))
|
||||
|
||||
if timeout is None:
|
||||
if 'CI' in os.environ:
|
||||
if testutils.ON_CI:
|
||||
timeout = 15000
|
||||
else:
|
||||
timeout = 5000
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
"""Base class for a subprocess run for tests."""
|
||||
|
||||
import re
|
||||
import os
|
||||
import time
|
||||
import warnings
|
||||
|
||||
|
|
@ -234,7 +233,7 @@ class Process(QObject):
|
|||
self._started = True
|
||||
verbose = self.request.config.getoption('--verbose')
|
||||
|
||||
timeout = 60 if 'CI' in os.environ else 20
|
||||
timeout = 60 if utils.ON_CI else 20
|
||||
for _ in range(timeout):
|
||||
with self._wait_signal(self.ready, timeout=1000,
|
||||
raising=False) as blocker:
|
||||
|
|
@ -476,7 +475,7 @@ class Process(QObject):
|
|||
if timeout is None:
|
||||
if do_skip:
|
||||
timeout = 2000
|
||||
elif 'CI' in os.environ:
|
||||
elif utils.ON_CI:
|
||||
timeout = 15000
|
||||
else:
|
||||
timeout = 5000
|
||||
|
|
|
|||
|
|
@ -417,3 +417,17 @@ def test_referrer(quteproc_new, server, server2, request, value, expected):
|
|||
expected = expected.replace(key, str(val))
|
||||
|
||||
assert headers.get('Referer') == expected
|
||||
|
||||
|
||||
@pytest.mark.qtwebkit_skip
|
||||
@utils.qt514
|
||||
def test_preferred_colorscheme(request, quteproc_new):
|
||||
"""Make sure the the preferred colorscheme is set."""
|
||||
args = _base_args(request.config) + [
|
||||
'--temp-basedir',
|
||||
'-s', 'colors.webpage.prefers_color_scheme_dark', 'true',
|
||||
]
|
||||
quteproc_new.start(args)
|
||||
|
||||
quteproc_new.send_cmd(':jseval matchMedia("(prefers-color-scheme: dark)").matches')
|
||||
quteproc_new.wait_for(message='True')
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ def fake_web_tab(stubs, tab_registry, mode_manager, qapp):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def greasemonkey_manager(monkeypatch, data_tmpdir):
|
||||
def greasemonkey_manager(monkeypatch, data_tmpdir, config_tmpdir):
|
||||
gm_manager = greasemonkey.GreasemonkeyManager()
|
||||
monkeypatch.setattr(greasemonkey, 'gm_manager', gm_manager)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,26 @@ def patch_backend(monkeypatch):
|
|||
monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebEngine)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('qversion, enabled, expected', [
|
||||
# Disabled or nothing set
|
||||
("5.14", False, []),
|
||||
("5.15.0", False, []),
|
||||
("5.15.1", False, []),
|
||||
("5.15.2", False, []),
|
||||
|
||||
# Enabled in configuration
|
||||
("5.14", True, []),
|
||||
("5.15.0", True, []),
|
||||
("5.15.1", True, []),
|
||||
("5.15.2", True, [("preferredColorScheme", "1")]),
|
||||
])
|
||||
@utils.qt514
|
||||
def test_colorscheme(config_stub, monkeypatch, qversion, enabled, expected):
|
||||
monkeypatch.setattr(darkmode.qtutils, 'qVersion', lambda: qversion)
|
||||
config_stub.val.colors.webpage.prefers_color_scheme_dark = enabled
|
||||
assert list(darkmode.settings()) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('settings, expected', [
|
||||
# Disabled
|
||||
({}, []),
|
||||
|
|
|
|||
|
|
@ -21,31 +21,51 @@ import logging
|
|||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip('PyQt5.QtWebEngineWidgets')
|
||||
QtWebEngineWidgets = pytest.importorskip('PyQt5.QtWebEngineWidgets')
|
||||
|
||||
from qutebrowser.browser.webengine import webenginesettings
|
||||
from qutebrowser.utils import usertypes
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(qapp, config_stub, cache_tmpdir, data_tmpdir, monkeypatch):
|
||||
monkeypatch.setattr(webenginesettings.webenginequtescheme, 'init',
|
||||
lambda: None)
|
||||
monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebEngine)
|
||||
webenginesettings.init()
|
||||
config_stub.changed.disconnect(webenginesettings._update_settings)
|
||||
@pytest.fixture
|
||||
def global_settings(monkeypatch, default_profile):
|
||||
wrapper = webenginesettings._SettingsWrapper()
|
||||
settings = webenginesettings.WebEngineSettings(wrapper)
|
||||
settings.init_settings()
|
||||
monkeypatch.setattr(webenginesettings, '_global_settings', settings)
|
||||
|
||||
|
||||
def test_big_cache_size(config_stub):
|
||||
@pytest.fixture
|
||||
def default_profile(monkeypatch):
|
||||
"""A profile to use which is set as default_profile.
|
||||
|
||||
Note we use a "private" profile here to avoid actually storing data during tests.
|
||||
"""
|
||||
profile = QtWebEngineWidgets.QWebEngineProfile()
|
||||
profile.setter = webenginesettings.ProfileSetter(profile)
|
||||
monkeypatch.setattr(profile, 'isOffTheRecord', lambda: False)
|
||||
monkeypatch.setattr(webenginesettings, 'default_profile', profile)
|
||||
return profile
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def private_profile(monkeypatch):
|
||||
"""A profile to use which is set as private_profile."""
|
||||
profile = QtWebEngineWidgets.QWebEngineProfile()
|
||||
profile.setter = webenginesettings.ProfileSetter(profile)
|
||||
monkeypatch.setattr(webenginesettings, 'private_profile', profile)
|
||||
return profile
|
||||
|
||||
|
||||
def test_big_cache_size(config_stub, default_profile):
|
||||
"""Make sure a too big cache size is handled correctly."""
|
||||
config_stub.val.content.cache.size = 2 ** 63 - 1
|
||||
profile = webenginesettings.default_profile
|
||||
profile.setter.set_http_cache_size()
|
||||
assert profile.httpCacheMaximumSize() == 2 ** 31 - 1
|
||||
default_profile.setter.set_http_cache_size()
|
||||
assert default_profile.httpCacheMaximumSize() == 2 ** 31 - 1
|
||||
|
||||
|
||||
def test_non_existing_dict(config_stub, monkeypatch, message_mock, caplog):
|
||||
def test_non_existing_dict(config_stub, monkeypatch, message_mock, caplog,
|
||||
global_settings):
|
||||
monkeypatch.setattr(webenginesettings.spell, 'local_filename',
|
||||
lambda _code: None)
|
||||
config_stub.val.spellcheck.languages = ['af-ZA']
|
||||
|
|
@ -59,29 +79,25 @@ def test_non_existing_dict(config_stub, monkeypatch, message_mock, caplog):
|
|||
assert msg.text == expected
|
||||
|
||||
|
||||
def test_existing_dict(config_stub, monkeypatch):
|
||||
def test_existing_dict(config_stub, monkeypatch, global_settings,
|
||||
default_profile, private_profile):
|
||||
monkeypatch.setattr(webenginesettings.spell, 'local_filename',
|
||||
lambda _code: 'en-US-8-0')
|
||||
config_stub.val.spellcheck.languages = ['en-US']
|
||||
webenginesettings._update_settings('spellcheck.languages')
|
||||
for profile in [webenginesettings.default_profile,
|
||||
webenginesettings.private_profile]:
|
||||
for profile in [default_profile, private_profile]:
|
||||
assert profile.isSpellCheckEnabled()
|
||||
assert profile.spellCheckLanguages() == ['en-US-8-0']
|
||||
|
||||
|
||||
def test_spell_check_disabled(config_stub, monkeypatch):
|
||||
def test_spell_check_disabled(config_stub, monkeypatch, global_settings,
|
||||
default_profile, private_profile):
|
||||
config_stub.val.spellcheck.languages = []
|
||||
webenginesettings._update_settings('spellcheck.languages')
|
||||
for profile in [webenginesettings.default_profile,
|
||||
webenginesettings.private_profile]:
|
||||
for profile in [default_profile, private_profile]:
|
||||
assert not profile.isSpellCheckEnabled()
|
||||
|
||||
|
||||
def test_default_user_agent_saved():
|
||||
assert webenginesettings.parsed_user_agent is not None
|
||||
|
||||
|
||||
def test_parsed_user_agent(qapp):
|
||||
webenginesettings.init_user_agent()
|
||||
parsed = webenginesettings.parsed_user_agent
|
||||
|
|
|
|||
|
|
@ -289,20 +289,25 @@ class TestQtArgs:
|
|||
else:
|
||||
assert arg in args
|
||||
|
||||
@pytest.mark.parametrize('dark, new_qt, added', [
|
||||
(True, True, True),
|
||||
(True, False, False),
|
||||
(False, True, False),
|
||||
(False, False, False),
|
||||
@pytest.mark.parametrize('dark, qt_version, added', [
|
||||
(True, "5.13", False), # not supported
|
||||
(True, "5.14", True),
|
||||
(True, "5.15.0", True),
|
||||
(True, "5.15.1", True),
|
||||
(True, "5.15.2", False), # handled via blink setting
|
||||
|
||||
(False, "5.13", False),
|
||||
(False, "5.14", False),
|
||||
(False, "5.15.0", False),
|
||||
(False, "5.15.1", False),
|
||||
(False, "5.15.2", False),
|
||||
])
|
||||
@utils.qt514
|
||||
def test_prefers_color_scheme_dark(self, config_stub, monkeypatch, parser,
|
||||
dark, new_qt, added):
|
||||
dark, qt_version, added):
|
||||
monkeypatch.setattr(qtargs.objects, 'backend',
|
||||
usertypes.Backend.QtWebEngine)
|
||||
monkeypatch.setattr(qtargs.qtutils, 'version_check',
|
||||
lambda version, exact=False, compiled=True:
|
||||
new_qt)
|
||||
monkeypatch.setattr(qtargs.qtutils, 'qVersion', lambda: qt_version)
|
||||
|
||||
config_stub.val.colors.webpage.prefers_color_scheme_dark = dark
|
||||
|
||||
|
|
|
|||
|
|
@ -41,12 +41,15 @@ test_gm_script = r"""
|
|||
console.log("Script is running.");
|
||||
"""
|
||||
|
||||
pytestmark = pytest.mark.usefixtures('data_tmpdir')
|
||||
pytestmark = [
|
||||
pytest.mark.usefixtures('data_tmpdir'),
|
||||
pytest.mark.usefixtures('config_tmpdir')
|
||||
]
|
||||
|
||||
|
||||
def _save_script(script_text, filename):
|
||||
# pylint: disable=no-member
|
||||
file_path = py.path.local(greasemonkey._scripts_dir()) / filename
|
||||
file_path = py.path.local(greasemonkey._scripts_dirs()[0]) / filename
|
||||
# pylint: enable=no-member
|
||||
file_path.write_text(script_text, encoding='utf-8', ensure=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-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/>.
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts.dev.ci import problemmatchers
|
||||
|
||||
|
||||
@pytest.mark.parametrize('matcher_name', list(problemmatchers.MATCHERS))
|
||||
def test_patterns(matcher_name):
|
||||
"""Make sure all regexps are valid.
|
||||
|
||||
They aren't actually Python syntax, but hopefully close enough to it to compile with
|
||||
Python's re anyways.
|
||||
"""
|
||||
for matcher in problemmatchers.MATCHERS[matcher_name]:
|
||||
for pattern in matcher['pattern']:
|
||||
regexp = pattern['regexp']
|
||||
print(regexp)
|
||||
re.compile(regexp)
|
||||
|
|
@ -818,3 +818,52 @@ def test_libgl_workaround(monkeypatch, skip):
|
|||
if skip:
|
||||
monkeypatch.setenv('QUTE_SKIP_LIBGL_WORKAROUND', '1')
|
||||
utils.libgl_workaround() # Just make sure it doesn't crash.
|
||||
|
||||
|
||||
@pytest.mark.parametrize('duration, out', [
|
||||
("0", 0),
|
||||
("0s", 0),
|
||||
("0.5s", 500),
|
||||
("59s", 59000),
|
||||
("60", 60),
|
||||
("60.4s", 60400),
|
||||
("1m1s", 61000),
|
||||
("1.5m", 90000),
|
||||
("1m", 60000),
|
||||
("1h", 3_600_000),
|
||||
("0.5h", 1_800_000),
|
||||
("1h1s", 3_601_000),
|
||||
("1h 1s", 3_601_000),
|
||||
("1h1m", 3_660_000),
|
||||
("1h1m1s", 3_661_000),
|
||||
("1h1m10s", 3_670_000),
|
||||
("10h1m10s", 36_070_000),
|
||||
])
|
||||
def test_parse_duration(duration, out):
|
||||
assert utils.parse_duration(duration) == out
|
||||
|
||||
|
||||
@pytest.mark.parametrize('duration', [
|
||||
"-1s", # No sense to wait for negative seconds
|
||||
"-1",
|
||||
"34ss",
|
||||
"",
|
||||
"h",
|
||||
"1.s",
|
||||
"1.1.1s",
|
||||
".1s",
|
||||
".s",
|
||||
"10e5s",
|
||||
"5s10m",
|
||||
])
|
||||
def test_parse_duration_invalid(duration):
|
||||
with pytest.raises(ValueError, match='Invalid duration'):
|
||||
utils.parse_duration(duration)
|
||||
|
||||
|
||||
@hypothesis.given(strategies.text())
|
||||
def test_parse_duration_hypothesis(duration):
|
||||
try:
|
||||
utils.parse_duration(duration)
|
||||
except ValueError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -562,11 +562,13 @@ class ImportFake:
|
|||
('yaml', True),
|
||||
('adblock', True),
|
||||
('attr', True),
|
||||
('importlib_resources', True),
|
||||
('PyQt5.QtWebEngineWidgets', True),
|
||||
('PyQt5.QtWebEngine', True),
|
||||
('PyQt5.QtWebKitWidgets', True),
|
||||
])
|
||||
self.no_version_attribute = ['sip',
|
||||
'importlib_resources',
|
||||
'PyQt5.QtWebEngineWidgets',
|
||||
'PyQt5.QtWebKitWidgets',
|
||||
'PyQt5.QtWebEngine']
|
||||
|
|
|
|||
8
tox.ini
8
tox.ini
|
|
@ -12,11 +12,12 @@ minversion = 3.15
|
|||
[testenv]
|
||||
setenv =
|
||||
PYTEST_QT_API=pyqt5
|
||||
pyqt{,512,513,514,515}: LINK_PYQT_SKIP=true
|
||||
pyqt{,512,513,514,515}: QUTE_BDD_WEBENGINE=true
|
||||
pyqt{,512,513,514,515,5150}: LINK_PYQT_SKIP=true
|
||||
pyqt{,512,513,514,515,5150}: QUTE_BDD_WEBENGINE=true
|
||||
cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report=
|
||||
passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI XDG_* QUTE_* DOCKER QT_QUICK_BACKEND PY_COLORS
|
||||
basepython =
|
||||
py: {env:PYTHON:python3}
|
||||
py3: {env:PYTHON:python3}
|
||||
py36: {env:PYTHON:python3.6}
|
||||
py37: {env:PYTHON:python3.7}
|
||||
|
|
@ -30,6 +31,7 @@ deps =
|
|||
pyqt513: -r{toxinidir}/misc/requirements/requirements-pyqt-5.13.txt
|
||||
pyqt514: -r{toxinidir}/misc/requirements/requirements-pyqt-5.14.txt
|
||||
pyqt515: -r{toxinidir}/misc/requirements/requirements-pyqt-5.15.txt
|
||||
pyqt5150: -r{toxinidir}/misc/requirements/requirements-pyqt-5.15.0.txt
|
||||
commands =
|
||||
{envpython} scripts/link_pyqt.py --tox {envdir}
|
||||
{envpython} -bb -m pytest {posargs:tests}
|
||||
|
|
@ -44,7 +46,7 @@ basepython = {env:PYTHON:python3}
|
|||
passenv = HOME
|
||||
deps =
|
||||
commands =
|
||||
{envpython} scripts/dev/misc_checks.py all
|
||||
{envpython} scripts/dev/misc_checks.py {posargs:all}
|
||||
|
||||
[testenv:vulture]
|
||||
basepython = {env:PYTHON:python3}
|
||||
|
|
|
|||
Loading…
Reference in New Issue