Merge branch 'master' into more-sophisticated-adblock
This commit is contained in:
commit
bf4dbef806
|
|
@ -36,8 +36,7 @@ Changed
|
|||
partially transparent qutebrowser window on a setup which supports doing so.
|
||||
- If QtWebEngine is compiled with PipeWire support and libpipewire is
|
||||
installed, qutebrowser will now support screen sharing on Wayland. Note that
|
||||
QtWebEngine 5.15.1 (planned for August 2020) is needed, though the Archlinux
|
||||
qt5-webengine package backports the patch.
|
||||
QtWebEngine 5.15.1 is needed.
|
||||
- When `:undo` is used with a count, it now reopens the count-th to last tab
|
||||
instead of the last one. The depth can instead be passed as an argument,
|
||||
which is also completed.
|
||||
|
|
@ -52,6 +51,23 @@ Changed
|
|||
- `:completion-item-focus` now understands `next-page` and `prev-page` with
|
||||
corresponding `<PgDown>` / `<PgUp>` default bindings.
|
||||
- When the last private window is closed, all private browsing data is now cleared.
|
||||
- When `config.source(...)` is used with a `--config-py` argument given,
|
||||
qutebrowser used to search relative files in the config basedir, leading to them
|
||||
not being found when using a shared `config.py` for different basedirs. Instead,
|
||||
they are now searched relative to the given `config.py` file.
|
||||
- `navigate prev` (`[[`) and `navigate next` (`]]`) now recognize links with
|
||||
`nav-prev` and `nav-next` classes, such as those used by the Hugo static site
|
||||
generator.
|
||||
- When `tabs.favicons` is disabled but `tabs.tabs_are_windows` is set, the
|
||||
window icon is still set to the page's favicon now.
|
||||
- The `--asciidoc` argument to `src2asciidoc.py` and `build_release.py` now
|
||||
only takes the path to `asciidoc.py`, using the current Python interpreter by
|
||||
default. To configure the Python interpreter as well, use
|
||||
`--asciidoc-python path/to/python --asciidoc path/to/asciidoc.py`
|
||||
instead of the former
|
||||
`--asciidoc path/to/python path/to/asciidoc.py`.
|
||||
- The `readability-js` userscript now adds some CSS to better deal
|
||||
with images, similarly to what Firefox' reader mode does.
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
|
@ -113,6 +129,14 @@ Fixed
|
|||
- Highlighting in the completion now works properly when UTF-16 surrogate pairs (such as
|
||||
emoji) are involved.
|
||||
- When a windowed inspector is clicked, insert mode now isn't entered anymore.
|
||||
- When `:undo` to re-open a tab but `tabs.tabs_are_windows` was set between
|
||||
closing and undoing the close, qutebrowser crashed. This is now fixed.
|
||||
- Fixes for the `qute-pass` userscript:
|
||||
* With newer `gopass` versions, a deprecation notice was copied as
|
||||
password due to `qute-pass` using it in a deprecated way.
|
||||
* The `--password-store` argument didn't actually set
|
||||
`PASSWORD_STORE_DIR` for `pass`, resulting in `qute-pass` finding matches but the
|
||||
underlying `pass` not finding matching passwords. This is now fixed.
|
||||
|
||||
v1.13.1 (2020-07-17)
|
||||
--------------------
|
||||
|
|
@ -1128,7 +1152,7 @@ Fixed
|
|||
|
||||
- `qute://` pages now work properly on Qt 5.11.2
|
||||
- Error when passing a substring with spaces to `:tab-take`.
|
||||
- Greasemonkey scripts which start with an UTF-8 BOM are now handled correctly.
|
||||
- Greasemonkey scripts which start with a UTF-8 BOM are now handled correctly.
|
||||
- When no documentation has been generated, the plaintext documentation now can
|
||||
be shown for more files such as `qute://help/userscripts.html`.
|
||||
- Crash when doing initial run on Wayland without XWayland.
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ Why does it take longer to open a URL in qutebrowser than in chromium?::
|
|||
One workaround is to use this
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/scripts/open_url_in_instance.sh[script]
|
||||
and place it in your $PATH with the name "qutebrowser". This
|
||||
script passes the URL via an unix socket to qutebrowser (if its
|
||||
script passes the URL via a unix socket to qutebrowser (if its
|
||||
running already) using socat which is much faster and starts a new
|
||||
qutebrowser if it is not running already.
|
||||
|
||||
|
|
|
|||
|
|
@ -763,7 +763,16 @@ Evaluate a JavaScript string.
|
|||
|
||||
* +*-u*+, +*--url*+: Interpret js-code as a `javascript:...` URL.
|
||||
* +*-q*+, +*--quiet*+: Don't show resulting JS object.
|
||||
* +*-w*+, +*--world*+: Ignored on QtWebKit. On QtWebEngine, a world ID or name to run the snippet in.
|
||||
* +*-w*+, +*--world*+: Ignored on QtWebKit. On QtWebEngine, a world ID or name to run the snippet in. Predefined world names are:
|
||||
|
||||
|
||||
- `main` (same world as the web page's JavaScript and
|
||||
Greasemonkey, unless overridden via `@qute-js-world`)
|
||||
- `application` (used for internal qutebrowser JS code,
|
||||
should not be used via `:jseval` unless you know what
|
||||
you're doing)
|
||||
- `user` (currently unused)
|
||||
- `jseval` (used for this command by default)
|
||||
|
||||
|
||||
==== note
|
||||
|
|
|
|||
|
|
@ -398,6 +398,7 @@ Pre-built colorschemes
|
|||
- Two implementations of the https://github.com/arcticicestudio/nord[Nord] colorscheme for qutebrowser exist: https://github.com/Linuus/nord-qutebrowser[Linuus], https://github.com/KnownAsDon/QuteBrowser-Nord-Theme[KnownAsDon]
|
||||
- https://github.com/dracula/qutebrowser-dracula-theme[Dracula]
|
||||
- https://gitlab.com/lovetocode999/selenized-qutebrowser[Selenized]
|
||||
- https://github.com/The-Compiler/dotfiles/blob/master/qutebrowser/gruvbox.py[gruvbox]
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -1737,6 +1737,8 @@ On QtWebKit, this setting is unavailable.
|
|||
=== colors.webpage.prefers_color_scheme_dark
|
||||
Force `prefers-color-scheme: dark` colors for websites.
|
||||
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
|
|
|||
|
|
@ -28,10 +28,20 @@ Debian/Ubuntu/...
|
|||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
For Debian based systems (Debian, Ubuntu, Linux Mint, ...), debug information
|
||||
is available in the repositories:
|
||||
is for QtWebEngine is available in a dedicated repository. Enable that repository
|
||||
(https://wiki.debian.org/HowToGetABacktrace#Installing_the_debugging_symbols[Debian],
|
||||
https://wiki.ubuntu.com/Debug%20Symbol%20Packages[Ubuntu],
|
||||
https://www.linuxmint.com/rel_tessa_mate_whatsnew.php[Linux Mint]) and install
|
||||
the debug packages:
|
||||
|
||||
----
|
||||
# apt-get install python3-pyqt5-dbg python3-pyqt5.qtwebkit-dbg python3-dbg libqt5webkit5-dbg
|
||||
# apt install python3-dbg python3-pyqt5-dbg python3-pyqt5.qtwebengine-dbg libqt5webengine5-dbgsym
|
||||
----
|
||||
|
||||
or with the QtWebKit backend:
|
||||
|
||||
----
|
||||
# apt install python3-dbg python3-pyqt5-dbg python3-pyqt5.qtwebkit-dbg libqt5webkit5-dbg
|
||||
----
|
||||
|
||||
Fedora
|
||||
|
|
@ -116,7 +126,7 @@ First install `gdb` on your system if it's not installed already.
|
|||
Then run qutebrowser directly inside gdb like this:
|
||||
|
||||
----
|
||||
$ gdb $(readlink -f $(which python3)) -ex 'run -m qutebrowser --debug'
|
||||
$ gdb -ex r --args $(readlink -f $(which python3)) -m qutebrowser --debug --temp-basedir
|
||||
----
|
||||
|
||||
Note qutebrowser/gdb will take a long time to start. After you reproduce the
|
||||
|
|
@ -131,9 +141,10 @@ Program received signal SIGSEGV, Segmentation fault.
|
|||
Now enter these commands at the gdb prompt:
|
||||
|
||||
----
|
||||
(gdb) set pagination off
|
||||
(gdb) set logging overwrite on
|
||||
(gdb) set logging on
|
||||
(gdb) bt full
|
||||
# you might have to press enter a few times until you get the prompt back
|
||||
(gdb) bt
|
||||
(gdb) quit
|
||||
----
|
||||
|
||||
|
|
@ -176,9 +187,10 @@ Getting the stack trace
|
|||
Now enter these commands at the gdb prompt:
|
||||
|
||||
----
|
||||
(gdb) set pagination off
|
||||
(gdb) set logging overwrite on
|
||||
(gdb) set logging on
|
||||
(gdb) bt
|
||||
# you might have to press enter a few times until you get the prompt back
|
||||
(gdb) quit
|
||||
----
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ install: man
|
|||
install -Dm644 icons/qutebrowser.svg \
|
||||
"$(DESTDIR)$(DATADIR)/icons/hicolor/scalable/apps/qutebrowser.svg"
|
||||
install -Dm755 -t "$(DESTDIR)$(DATADIR)/qutebrowser/userscripts/" \
|
||||
$(wildcard misc/userscripts/*)
|
||||
$(filter-out misc/userscripts/__pycache__,$(wildcard misc/userscripts/*))
|
||||
install -Dm755 -t "$(DESTDIR)$(DATADIR)/qutebrowser/scripts/" \
|
||||
$(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \
|
||||
scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# encoding: iso-8859-1
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ certifi==2020.6.20
|
|||
cffi==1.14.2
|
||||
chardet==3.0.4
|
||||
colorama==0.4.3
|
||||
cryptography==3.0
|
||||
cryptography==3.1
|
||||
cssutils==1.0.2
|
||||
github3.py==1.3.0
|
||||
hunter==3.2.1
|
||||
hunter==3.2.2
|
||||
idna==2.10
|
||||
jwcrypto==0.8
|
||||
manhole==1.6.0
|
||||
|
|
@ -16,10 +16,10 @@ packaging==20.4
|
|||
pycparser==2.20
|
||||
Pympler==0.8
|
||||
pyparsing==2.4.7
|
||||
PyQt-builder==1.4.0
|
||||
PyQt-builder==1.5.0
|
||||
python-dateutil==2.8.1
|
||||
requests==2.24.0
|
||||
sip==5.3.0
|
||||
sip==5.4.0
|
||||
six==1.15.0
|
||||
toml==0.10.1
|
||||
uritemplate==3.0.1
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==20.1.0
|
||||
attrs==20.2.0
|
||||
flake8==3.8.3
|
||||
flake8-bugbear==20.1.4
|
||||
flake8-builtins==1.5.3
|
||||
|
|
@ -18,7 +18,7 @@ flake8-tuple==0.4.1
|
|||
mccabe==0.6.1
|
||||
pep8-naming==0.11.1
|
||||
pycodestyle==2.6.0
|
||||
pydocstyle==5.1.0
|
||||
pydocstyle==5.1.1
|
||||
pyflakes==2.2.0
|
||||
six==1.15.0
|
||||
snowballstemmer==2.0.0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
diff-cover==3.0.1
|
||||
diff-cover==4.0.0
|
||||
inflect==4.1.0
|
||||
Jinja2==2.11.2
|
||||
jinja2-pluralize==0.3.0
|
||||
|
|
@ -9,8 +9,7 @@ MarkupSafe==1.1.1
|
|||
mypy==0.782
|
||||
mypy-extensions==0.4.3
|
||||
pluggy==0.13.1
|
||||
Pygments==2.6.1
|
||||
Pygments==2.7.1
|
||||
-e git+https://github.com/stlehmann/PyQt5-stubs.git@master#egg=PyQt5_stubs
|
||||
six==1.15.0
|
||||
typed-ast==1.4.1
|
||||
typing-extensions==3.7.4.3
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
altgraph==0.17
|
||||
pyinstaller==4.0
|
||||
pyinstaller-hooks-contrib==2020.7
|
||||
pyinstaller-hooks-contrib==2020.8
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ astroid==2.3.3 # rq.filter: < 2.4
|
|||
certifi==2020.6.20
|
||||
cffi==1.14.2
|
||||
chardet==3.0.4
|
||||
cryptography==3.0
|
||||
cryptography==3.1
|
||||
github3.py==1.3.0
|
||||
idna==2.10
|
||||
isort==4.3.21
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.12.3 # rq.filter: < 5.13
|
||||
PyQt5-sip==12.8.0
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.12.1 # rq.filter: < 5.13
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.13.2 # rq.filter: < 5.14
|
||||
PyQt5-sip==12.8.0
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.13.2 # rq.filter: < 5.14
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.14.2 # rq.filter: < 5.15
|
||||
PyQt5-sip==12.8.0
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.14.0 # rq.filter: < 5.15
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.0 # rq.filter: < 6
|
||||
PyQt5-sip==12.8.0
|
||||
PyQtWebEngine==5.15.0 # rq.filter: < 6
|
||||
PyQt5==5.15.1 # rq.filter: < 6
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.15.1 # rq.filter: < 6
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.0
|
||||
PyQt5-sip==12.8.0
|
||||
PyQtWebEngine==5.15.0
|
||||
PyQt5==5.15.1
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.15.1
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.16
|
||||
Pygments==2.6.1
|
||||
Pygments==2.7.1
|
||||
pyroma==2.6
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ imagesize==1.2.0
|
|||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
packaging==20.4
|
||||
Pygments==2.6.1
|
||||
Pygments==2.7.1
|
||||
pyparsing==2.4.7
|
||||
pytz==2020.1
|
||||
requests==2.24.0
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==20.1.0
|
||||
apipkg==1.5
|
||||
attrs==20.2.0
|
||||
beautifulsoup4==4.9.1
|
||||
certifi==2020.6.20
|
||||
chardet==3.0.4
|
||||
cheroot==8.4.5
|
||||
click==7.1.2
|
||||
# colorama==0.4.3
|
||||
coverage==5.2.1
|
||||
coverage==5.3
|
||||
EasyProcess==0.3
|
||||
execnet==1.7.1
|
||||
Flask==1.1.2
|
||||
glob2==0.7
|
||||
hunter==3.2.1
|
||||
hypothesis==5.29.0
|
||||
hunter==3.2.2
|
||||
hypothesis==5.35.3 ; python_version>="3.6"
|
||||
idna==2.10
|
||||
iniconfig==1.0.1
|
||||
itsdangerous==1.1.0
|
||||
|
|
@ -21,24 +23,26 @@ jaraco.functools==3.0.1 ; python_version>="3.6"
|
|||
Mako==1.1.3
|
||||
manhole==1.6.0
|
||||
# MarkupSafe==1.1.1
|
||||
more-itertools==8.4.0
|
||||
more-itertools==8.5.0
|
||||
packaging==20.4
|
||||
parse==1.16.0
|
||||
parse==1.18.0
|
||||
parse-type==0.5.2
|
||||
pluggy==0.13.1
|
||||
py==1.9.0
|
||||
py-cpuinfo==7.0.0
|
||||
Pygments==2.6.1
|
||||
Pygments==2.7.1
|
||||
pyparsing==2.4.7
|
||||
pytest==6.0.1
|
||||
pytest-bdd==3.4.0
|
||||
pytest==6.0.2
|
||||
pytest-bdd==4.0.1
|
||||
pytest-benchmark==3.2.3
|
||||
pytest-cov==2.10.1
|
||||
pytest-forked==1.3.0
|
||||
pytest-instafail==0.4.2
|
||||
pytest-mock==3.3.0
|
||||
pytest-mock==3.3.1
|
||||
pytest-qt==3.3.0
|
||||
pytest-repeat==0.8.0
|
||||
pytest-rerunfailures==9.0
|
||||
pytest-rerunfailures==9.1
|
||||
pytest-xdist==2.1.0
|
||||
pytest-xvfb==2.0.0
|
||||
PyVirtualDisplay==1.3.2
|
||||
requests==2.24.0
|
||||
|
|
@ -53,3 +57,4 @@ vulture==2.1 ; python_version>="3.6"
|
|||
Werkzeug==1.0.1
|
||||
jaraco.functools==2.0; python_version<"3.6"
|
||||
vulture==1.6; python_version<"3.6"
|
||||
hypothesis<5.34.0; python_version<"3.6"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ pytest-cov
|
|||
# To avoid windows from popping up
|
||||
pytest-xvfb
|
||||
PyVirtualDisplay
|
||||
# To run on multiple cores with -n
|
||||
pytest-xdist
|
||||
|
||||
# Needed to test misc/userscripts/qute-lastpass
|
||||
tldextract
|
||||
|
|
@ -35,4 +37,7 @@ tldextract
|
|||
#@ markers: vulture python_version>="3.6"
|
||||
#@ add: vulture==1.6; python_version<"3.6"
|
||||
|
||||
#@ markers: hypothesis python_version>="3.6"
|
||||
#@ add: hypothesis<5.34.0; python_version<"3.6"
|
||||
|
||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ py==1.9.0
|
|||
pyparsing==2.4.7
|
||||
six==1.15.0
|
||||
toml==0.10.1
|
||||
tox==3.19.0
|
||||
tox==3.20.0
|
||||
tox-pip-version==0.0.7
|
||||
tox-venv==0.4.0
|
||||
virtualenv==20.0.31
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ The following userscripts are included in the current directory.
|
|||
|
||||
The following userscripts can be found on their own repositories.
|
||||
|
||||
- [qurlshare](https://github.com/sim590/qurlshare): *secure* sharing of an URL between qutebrowser
|
||||
- [qurlshare](https://github.com/sim590/qurlshare): *secure* sharing of a URL between qutebrowser
|
||||
instances using a distributed hash table.
|
||||
- [qutebrowser-userscripts](https://github.com/cryzed/qutebrowser-userscripts):
|
||||
a small pack of userscripts.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Behaviour
|
||||
# Behavior
|
||||
# Userscript for qutebrowser which casts the url passed in $1 to the default
|
||||
# ChromeCast device in the network using the program `castnow`
|
||||
#
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ def main(arguments):
|
|||
qute_command('enter-mode insert')
|
||||
|
||||
# If it finds a TOTP code, it copies it to the clipboard,
|
||||
# which is the same behaviour as the Firefox add-on.
|
||||
# which is the same behavior as the Firefox add-on.
|
||||
if not arguments.totp_only and totp and arguments.totp:
|
||||
# The import is done here, to make pyperclip an optional dependency
|
||||
import pyperclip
|
||||
|
|
|
|||
|
|
@ -61,11 +61,19 @@ import sys
|
|||
|
||||
import tldextract
|
||||
|
||||
|
||||
def expanded_path(path):
|
||||
# Expand potential ~ in paths, since this script won't be called from a shell that does it for us
|
||||
expanded = os.path.expanduser(path)
|
||||
# Add trailing slash if not present
|
||||
return os.path.join(expanded, '')
|
||||
|
||||
|
||||
argument_parser = argparse.ArgumentParser(description=__doc__, usage=USAGE, epilog=EPILOG)
|
||||
argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
|
||||
argument_parser.add_argument('--password-store', '-p',
|
||||
default=os.getenv('PASSWORD_STORE_DIR', default=os.path.expanduser('~/.password-store')),
|
||||
help='Path to your pass password-store (only used in pass-mode)')
|
||||
default=expanded_path(os.getenv('PASSWORD_STORE_DIR', default='~/.password-store')),
|
||||
help='Path to your pass password-store (only used in pass-mode)', type=expanded_path)
|
||||
argument_parser.add_argument('--mode', '-M', choices=['pass', 'gopass'], default="pass",
|
||||
help='Select mode [gopass] to use gopass instead of the standard pass.')
|
||||
argument_parser.add_argument('--username-pattern', '-u', default=r'.*/(.+)',
|
||||
|
|
@ -107,7 +115,7 @@ def qute_command(command):
|
|||
fifo.flush()
|
||||
|
||||
|
||||
def find_pass_candidates(domain, password_store_path):
|
||||
def find_pass_candidates(domain):
|
||||
candidates = []
|
||||
|
||||
if arguments.mode == "gopass":
|
||||
|
|
@ -117,13 +125,13 @@ def find_pass_candidates(domain, password_store_path):
|
|||
if domain in password:
|
||||
candidates.append(password)
|
||||
else:
|
||||
for path, directories, file_names in os.walk(password_store_path, followlinks=True):
|
||||
for path, directories, file_names in os.walk(arguments.password_store, followlinks=True):
|
||||
secrets = fnmatch.filter(file_names, '*.gpg')
|
||||
if not secrets:
|
||||
continue
|
||||
|
||||
# Strip password store path prefix to get the relative pass path
|
||||
pass_path = path[len(password_store_path):]
|
||||
pass_path = path[len(arguments.password_store):]
|
||||
split_path = pass_path.split(os.path.sep)
|
||||
for secret in secrets:
|
||||
secret_base = os.path.splitext(secret)[0]
|
||||
|
|
@ -134,25 +142,27 @@ def find_pass_candidates(domain, password_store_path):
|
|||
return candidates
|
||||
|
||||
|
||||
def _run_pass(pass_arguments, encoding):
|
||||
def _run_pass(pass_arguments):
|
||||
# The executable is conveniently named after it's mode [pass|gopass].
|
||||
pass_command = [arguments.mode]
|
||||
process = subprocess.run(pass_command + pass_arguments, stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(encoding).strip()
|
||||
env = os.environ.copy()
|
||||
env['PASSWORD_STORE_DIR'] = arguments.password_store
|
||||
process = subprocess.run(pass_command + pass_arguments, env=env, stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(arguments.io_encoding).strip()
|
||||
|
||||
|
||||
def pass_(path, encoding):
|
||||
return _run_pass([path], encoding)
|
||||
def pass_(path):
|
||||
return _run_pass(['show', path])
|
||||
|
||||
|
||||
def pass_otp(path, encoding):
|
||||
return _run_pass(['otp', path], encoding)
|
||||
def pass_otp(path):
|
||||
return _run_pass(['otp', path])
|
||||
|
||||
|
||||
def dmenu(items, invocation, encoding):
|
||||
def dmenu(items, invocation):
|
||||
command = shlex.split(invocation)
|
||||
process = subprocess.run(command, input='\n'.join(items).encode(encoding), stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(encoding).strip()
|
||||
process = subprocess.run(command, input='\n'.join(items).encode(arguments.io_encoding), stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(arguments.io_encoding).strip()
|
||||
|
||||
|
||||
def fake_key_raw(text):
|
||||
|
|
@ -170,11 +180,6 @@ def main(arguments):
|
|||
extractor = tldextract.TLDExtract(extra_suffixes=arguments.extra_url_suffixes.split(','))
|
||||
extract_result = extractor(arguments.url)
|
||||
|
||||
# Expand potential ~ in paths, since this script won't be called from a shell that does it for us
|
||||
password_store_path = os.path.expanduser(arguments.password_store)
|
||||
# Add trailing slash if not present
|
||||
password_store_path = os.path.join(password_store_path, '')
|
||||
|
||||
# Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains),
|
||||
# the registered domain name, the IPv4 address if that's what the URL represents and finally the private domain
|
||||
# (if a non-public suffix was used).
|
||||
|
|
@ -188,7 +193,7 @@ def main(arguments):
|
|||
|
||||
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4, private_domain]):
|
||||
attempted_targets.append(target)
|
||||
target_candidates = find_pass_candidates(target, password_store_path)
|
||||
target_candidates = find_pass_candidates(target)
|
||||
if not target_candidates:
|
||||
continue
|
||||
|
||||
|
|
@ -200,8 +205,7 @@ def main(arguments):
|
|||
stderr('No pass candidates for URL {!r} found! (I tried {!r})'.format(arguments.url, attempted_targets))
|
||||
return ExitCodes.NO_PASS_CANDIDATES
|
||||
|
||||
selection = candidates.pop() if len(candidates) == 1 else dmenu(sorted(candidates), arguments.dmenu_invocation,
|
||||
arguments.io_encoding)
|
||||
selection = candidates.pop() if len(candidates) == 1 else dmenu(sorted(candidates), arguments.dmenu_invocation)
|
||||
# Nothing was selected, simply return
|
||||
if not selection:
|
||||
return ExitCodes.SUCCESS
|
||||
|
|
@ -209,7 +213,7 @@ def main(arguments):
|
|||
# If username-target is path and user asked for username-only, we don't need to run pass
|
||||
secret = None
|
||||
if not (arguments.username_target == 'path' and arguments.username_only):
|
||||
secret = pass_(selection, arguments.io_encoding)
|
||||
secret = pass_(selection)
|
||||
|
||||
# Match password
|
||||
match = re.match(arguments.password_pattern, secret)
|
||||
|
|
@ -231,7 +235,7 @@ def main(arguments):
|
|||
elif arguments.password_only:
|
||||
fake_key_raw(password)
|
||||
elif arguments.otp_only:
|
||||
otp = pass_otp(selection, arguments.io_encoding)
|
||||
otp = pass_otp(selection)
|
||||
fake_key_raw(otp)
|
||||
else:
|
||||
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
|
||||
|
|
|
|||
|
|
@ -44,6 +44,16 @@ const HEADER = `
|
|||
h1, h2, h3 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
img {
|
||||
max-width:100%;
|
||||
height:auto;
|
||||
}
|
||||
p > img:only-child,
|
||||
p > a:only-child > img:only-child,
|
||||
.wp-caption img,
|
||||
figure img {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<!-- This icon is licensed under the Mozilla Public License 2.0 (available at: https://www.mozilla.org/en-US/MPL/2.0/).
|
||||
The original icon can be found here: https://dxr.mozilla.org/mozilla-central/source/browser/themes/shared/reader/readerMode.svg -->
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ markers =
|
|||
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
|
||||
unicode_locale: Tests which need an 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
|
||||
js_headers: Sets JS headers dynamically on QtWebEngine (unsupported on some versions)
|
||||
qtwebkit_pdf_imageformat_skip: Broken on QtWebKit with PDF image format plugin installed
|
||||
|
|
|
|||
|
|
@ -897,6 +897,8 @@ class AbstractTab(QWidget):
|
|||
icon_changed = pyqtSignal(QIcon)
|
||||
#: Signal emitted when a page's title changed (new title as str)
|
||||
title_changed = pyqtSignal(str)
|
||||
#: Signal emitted when this tab was pinned/unpinned (new pinned state as bool)
|
||||
pinned_changed = pyqtSignal(bool)
|
||||
#: Signal emitted when a new tab should be opened (url as QUrl)
|
||||
new_tab_requested = pyqtSignal(QUrl)
|
||||
#: Signal emitted when a page's URL changed (url as QUrl)
|
||||
|
|
@ -1191,6 +1193,10 @@ class AbstractTab(QWidget):
|
|||
def set_html(self, html: str, base_url: QUrl = QUrl()) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def set_pinned(self, pinned: bool) -> None:
|
||||
self.data.pinned = pinned
|
||||
self.pinned_changed.emit(pinned)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
try:
|
||||
qurl = self.url()
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ class CommandDispatcher:
|
|||
return
|
||||
|
||||
to_pin = not tab.data.pinned
|
||||
self._tabbed_browser.widget.set_tab_pinned(tab, to_pin)
|
||||
tab.set_pinned(to_pin)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='open',
|
||||
maxsplit=0, scope='window')
|
||||
|
|
@ -421,7 +421,8 @@ class CommandDispatcher:
|
|||
newtab.data.keep_icon = True
|
||||
newtab.history.private_api.deserialize(history)
|
||||
newtab.zoom.set_factor(curtab.zoom.factor())
|
||||
new_tabbed_browser.widget.set_tab_pinned(newtab, curtab.data.pinned)
|
||||
|
||||
newtab.set_pinned(curtab.data.pinned)
|
||||
return newtab
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
|
|
@ -1663,7 +1664,15 @@ class CommandDispatcher:
|
|||
url: Interpret js-code as a `javascript:...` URL.
|
||||
quiet: Don't show resulting JS object.
|
||||
world: Ignored on QtWebKit. On QtWebEngine, a world ID or name to
|
||||
run the snippet in.
|
||||
run the snippet in. Predefined world names are:
|
||||
|
||||
- `main` (same world as the web page's JavaScript and
|
||||
Greasemonkey, unless overridden via `@qute-js-world`)
|
||||
- `application` (used for internal qutebrowser JS code,
|
||||
should not be used via `:jseval` unless you know what
|
||||
you're doing)
|
||||
- `user` (currently unused)
|
||||
- `jseval` (used for this command by default)
|
||||
"""
|
||||
cmdutils.check_exclusive((file, url), 'fu')
|
||||
|
||||
|
|
|
|||
|
|
@ -154,14 +154,19 @@ def strip(url, count):
|
|||
|
||||
def _find_prevnext(prev, elems):
|
||||
"""Find a prev/next element in the given list of elements."""
|
||||
# First check for <link rel="prev(ious)|next">
|
||||
# First check for <link rel="prev(ious)|next"> as well as
|
||||
# e.g. <a class="nav-(prev|next)"> (Hugo)
|
||||
rel_values = {'prev', 'previous'} if prev else {'next'}
|
||||
classes = {'nav-prev'} if prev else {'nav-next'}
|
||||
for e in elems:
|
||||
if e.tag_name() not in ['link', 'a'] or 'rel' not in e:
|
||||
if e.tag_name() not in ['link', 'a']:
|
||||
continue
|
||||
if set(e['rel'].split(' ')) & rel_values:
|
||||
if 'rel' in e and set(e['rel'].split(' ')) & rel_values:
|
||||
log.hints.debug("Found {!r} with rel={}".format(e, e['rel']))
|
||||
return e
|
||||
elif e.classes() & classes:
|
||||
log.hints.debug("Found {!r} with class={}".format(e, e.classes()))
|
||||
return e
|
||||
|
||||
# Then check for regular links/buttons.
|
||||
elems = [e for e in elems if e.tag_name() != 'link']
|
||||
|
|
|
|||
|
|
@ -102,8 +102,8 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||
"""Get the geometry for this element."""
|
||||
raise NotImplementedError
|
||||
|
||||
def classes(self) -> typing.List[str]:
|
||||
"""Get a list of classes assigned to this element."""
|
||||
def classes(self) -> typing.Set[str]:
|
||||
"""Get a set of classes assigned to this element."""
|
||||
raise NotImplementedError
|
||||
|
||||
def tag_name(self) -> str:
|
||||
|
|
|
|||
|
|
@ -118,9 +118,9 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||
log.stub()
|
||||
return QRect()
|
||||
|
||||
def classes(self) -> typing.List[str]:
|
||||
def classes(self) -> typing.Set[str]:
|
||||
"""Get a list of classes assigned to this element."""
|
||||
return self._js_dict['class_name'].split()
|
||||
return set(self._js_dict['class_name'].split())
|
||||
|
||||
def tag_name(self) -> str:
|
||||
"""Get the tag name of this element.
|
||||
|
|
|
|||
|
|
@ -390,9 +390,14 @@ def init_private_profile():
|
|||
|
||||
|
||||
def _init_site_specific_quirks():
|
||||
"""Add custom user-agent settings for problematic sites.
|
||||
|
||||
See https://github.com/qutebrowser/qutebrowser/issues/4810
|
||||
"""
|
||||
if not config.val.content.site_specific_quirks:
|
||||
return
|
||||
|
||||
# Please leave this here as a template for new UAs.
|
||||
# default_ua = ("Mozilla/5.0 ({os_info}) "
|
||||
# "AppleWebKit/{webkit_version} (KHTML, like Gecko) "
|
||||
# "{qt_key}/{qt_version} "
|
||||
|
|
@ -402,7 +407,6 @@ def _init_site_specific_quirks():
|
|||
"AppleWebKit/{webkit_version} (KHTML, like Gecko) "
|
||||
"{upstream_browser_key}/{upstream_browser_version} "
|
||||
"Safari/{webkit_version}")
|
||||
firefox_ua = "Mozilla/5.0 ({os_info}; rv:71.0) Gecko/20100101 Firefox/71.0"
|
||||
new_chrome_ua = ("Mozilla/5.0 ({os_info}) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/99 "
|
||||
|
|
@ -414,14 +418,26 @@ def _init_site_specific_quirks():
|
|||
"Edg/{upstream_browser_version}")
|
||||
|
||||
user_agents = {
|
||||
# Needed to avoid a ""WhatsApp works with Google Chrome 36+" error
|
||||
# page which doesn't allow to use WhatsApp Web at all. Also see the
|
||||
# additional JS quirk: qutebrowser/javascript/whatsapp_web_quirk.user.js
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/4445
|
||||
'https://web.whatsapp.com/': no_qtwe_ua,
|
||||
|
||||
# Needed to avoid a "you're using a browser [...] that doesn't allow us
|
||||
# to keep your account secure" error.
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/5182
|
||||
'https://accounts.google.com/*': edge_ua,
|
||||
|
||||
# Needed because Slack adds an error which prevents using it relatively
|
||||
# aggressively, despite things actually working fine.
|
||||
# September 2020: Qt 5.12 works, but Qt <= 5.11 shows the error.
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/4669
|
||||
'https://*.slack.com/*': new_chrome_ua,
|
||||
'https://docs.google.com/*': firefox_ua,
|
||||
'https://drive.google.com/*': firefox_ua,
|
||||
}
|
||||
|
||||
if not qtutils.version_check('5.9'):
|
||||
# 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():
|
||||
|
|
|
|||
|
|
@ -101,9 +101,9 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||
self._check_vanished()
|
||||
return self._elem.geometry()
|
||||
|
||||
def classes(self) -> typing.List[str]:
|
||||
def classes(self) -> typing.Set[str]:
|
||||
self._check_vanished()
|
||||
return self._elem.classes()
|
||||
return set(self._elem.classes())
|
||||
|
||||
def tag_name(self) -> str:
|
||||
"""Get the tag name for the current element."""
|
||||
|
|
|
|||
|
|
@ -181,9 +181,9 @@ class WebView(QWebView):
|
|||
This is not needed for QtWebEngine, so it's in here.
|
||||
"""
|
||||
menu = self.page().createStandardContextMenu()
|
||||
self.shutting_down.connect(menu.close) # type: ignore[arg-type]
|
||||
self.shutting_down.connect(menu.close)
|
||||
mm = modeman.instance(self.win_id)
|
||||
mm.entered.connect(menu.close) # type: ignore[arg-type]
|
||||
mm.entered.connect(menu.close)
|
||||
menu.exec_(e.globalPos())
|
||||
|
||||
def showEvent(self, e):
|
||||
|
|
|
|||
|
|
@ -39,11 +39,11 @@ def command(*, info):
|
|||
|
||||
def helptopic(*, info):
|
||||
"""A CompletionModel filled with help topics."""
|
||||
model = completionmodel.CompletionModel()
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
|
||||
|
||||
cmdlist = util.get_cmd_completions(info, include_aliases=False,
|
||||
include_hidden=True, prefix=':')
|
||||
settings = ((opt.name, opt.description)
|
||||
settings = ((opt.name, opt.description, info.config.get_str(opt.name))
|
||||
for opt in configdata.DATA.values())
|
||||
|
||||
model.add_category(listcategory.ListCategory("Commands", cmdlist))
|
||||
|
|
|
|||
|
|
@ -2714,6 +2714,7 @@ colors.webpage.prefers_color_scheme_dark:
|
|||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
restart: true
|
||||
|
||||
## dark mode
|
||||
|
||||
|
|
|
|||
|
|
@ -606,7 +606,9 @@ class ConfigAPI:
|
|||
def source(self, filename: str) -> None:
|
||||
"""Read the given config file from disk."""
|
||||
if not os.path.isabs(filename):
|
||||
filename = str(self.configdir / filename)
|
||||
# We don't use self.configdir here so we get the proper file when starting
|
||||
# with a --config-py argument given.
|
||||
filename = os.path.join(os.path.dirname(standarddir.config_py()), filename)
|
||||
|
||||
try:
|
||||
read_config_py(filename)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ qute://warning/sessions</span> to show it again at a later time.</span>
|
|||
|
||||
<p>Since Qt doesn't provide an API to load the history of a tab, qutebrowser relies on a reverse-engineered binary serialization format to load tab history from session files. With Qt 5.15, unfortunately that format changed (due to the underlying Chromium upgrade), in a way which makes it impossible for qutebrowser to load tab history from existing session data.</p>
|
||||
|
||||
<p>At the time of writing (April 2020), a new session format which stores part of the needed binary data in saved sessions is <a href="https://github.com/qutebrowser/qutebrowser/issues/5359">in development</a> and is expected to be released with qutebrowser v1.14.0.</p>
|
||||
<p>At the time of writing (September 2020), a new session format which stores part of the needed binary data in saved sessions is <a href="https://github.com/qutebrowser/qutebrowser/issues/5359">in development</a> and is expected to be released with qutebrowser v1.15.0.</p>
|
||||
|
||||
<p>As a stop-gap measure:</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import datetime
|
|||
import attr
|
||||
from PyQt5.QtWidgets import QSizePolicy, QWidget, QApplication
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl
|
||||
from PyQt5.QtGui import QIcon
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
|
@ -212,8 +211,7 @@ class TabbedBrowser(QWidget):
|
|||
self._tab_insert_idx_right = -1
|
||||
self.is_shutting_down = False
|
||||
self.widget.tabCloseRequested.connect(self.on_tab_close_requested)
|
||||
self.widget.new_tab_requested.connect(
|
||||
self.tabopen) # type: ignore[arg-type]
|
||||
self.widget.new_tab_requested.connect(self.tabopen)
|
||||
self.widget.currentChanged.connect(self._on_current_changed)
|
||||
self.cur_fullscreen_requested.connect(self.widget.tabBar().maybe_hide)
|
||||
self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
|
@ -351,6 +349,8 @@ class TabbedBrowser(QWidget):
|
|||
functools.partial(self._on_title_changed, tab))
|
||||
tab.icon_changed.connect(
|
||||
functools.partial(self._on_icon_changed, tab))
|
||||
tab.pinned_changed.connect(
|
||||
functools.partial(self._on_pinned_changed, tab))
|
||||
tab.load_progress.connect(
|
||||
functools.partial(self._on_load_progress, tab))
|
||||
tab.load_finished.connect(
|
||||
|
|
@ -530,7 +530,7 @@ class TabbedBrowser(QWidget):
|
|||
newtab = self.tabopen(background=False, idx=entry.index)
|
||||
|
||||
newtab.history.private_api.deserialize(entry.history)
|
||||
self.widget.set_tab_pinned(newtab, entry.pinned)
|
||||
newtab.set_pinned(entry.pinned)
|
||||
|
||||
@pyqtSlot('QUrl', bool)
|
||||
def load_url(self, url, newtab):
|
||||
|
|
@ -788,26 +788,21 @@ class TabbedBrowser(QWidget):
|
|||
if not self.widget.page_title(idx):
|
||||
self.widget.set_page_title(idx, url.toDisplayString())
|
||||
|
||||
@pyqtSlot(browsertab.AbstractTab, QIcon)
|
||||
def _on_icon_changed(self, tab, icon):
|
||||
@pyqtSlot(browsertab.AbstractTab)
|
||||
def _on_icon_changed(self, tab):
|
||||
"""Set the icon of a tab.
|
||||
|
||||
Slot for the iconChanged signal of any tab.
|
||||
|
||||
Args:
|
||||
tab: The WebView where the title was changed.
|
||||
icon: The new icon
|
||||
"""
|
||||
if not tab.data.should_show_icon():
|
||||
return
|
||||
try:
|
||||
idx = self._tab_index(tab)
|
||||
self._tab_index(tab)
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
self.widget.setTabIcon(idx, icon)
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
self.widget.window().setWindowIcon(icon)
|
||||
self.widget.update_tab_favicon(tab)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_entered(self, mode):
|
||||
|
|
@ -922,6 +917,12 @@ class TabbedBrowser(QWidget):
|
|||
self._update_window_title('scroll_pos')
|
||||
self.widget.update_tab_title(idx, 'scroll_pos')
|
||||
|
||||
def _on_pinned_changed(self, tab):
|
||||
"""Update the tab's pinned status."""
|
||||
idx = self.widget.indexOf(tab)
|
||||
self.widget.update_tab_favicon(tab)
|
||||
self.widget.update_tab_title(idx)
|
||||
|
||||
def _on_audio_changed(self, tab, _muted):
|
||||
"""Update audio field in tab when mute or recentlyAudible changed."""
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -99,19 +99,6 @@ class TabWidget(QTabWidget):
|
|||
bar.set_tab_data(idx, 'indicator-color', color)
|
||||
bar.update(bar.tabRect(idx))
|
||||
|
||||
def set_tab_pinned(self, tab: QWidget,
|
||||
pinned: bool) -> None:
|
||||
"""Set the tab status as pinned.
|
||||
|
||||
Args:
|
||||
tab: The tab to pin
|
||||
pinned: Pinned tab state to set.
|
||||
"""
|
||||
idx = self.indexOf(tab)
|
||||
tab.data.pinned = pinned
|
||||
self.update_tab_favicon(tab)
|
||||
self.update_tab_title(idx)
|
||||
|
||||
def tab_indicator_color(self, idx):
|
||||
"""Get the tab indicator color for the given index."""
|
||||
return self.tabBar().tab_indicator_color(idx)
|
||||
|
|
@ -139,6 +126,7 @@ class TabWidget(QTabWidget):
|
|||
field: A field name which was updated. If given, the title
|
||||
is only set if the given field is in the template.
|
||||
"""
|
||||
assert idx != -1
|
||||
tab = self.widget(idx)
|
||||
if tab.data.pinned:
|
||||
fmt = config.cache['tabs.title.format_pinned']
|
||||
|
|
@ -344,14 +332,11 @@ class TabWidget(QTabWidget):
|
|||
"""Update favicon of the given tab."""
|
||||
idx = self.indexOf(tab)
|
||||
|
||||
if tab.data.should_show_icon():
|
||||
self.setTabIcon(idx, tab.icon())
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
self.window().setWindowIcon(tab.icon())
|
||||
else:
|
||||
self.setTabIcon(idx, QIcon())
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
self.window().setWindowIcon(self.window().windowIcon())
|
||||
icon = tab.icon() if tab.data.should_show_icon() else QIcon()
|
||||
self.setTabIcon(idx, icon)
|
||||
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
self.window().setWindowIcon(tab.icon())
|
||||
|
||||
def setTabIcon(self, idx: int, icon: QIcon) -> None:
|
||||
"""Always show tab icons for pinned tabs in some circumstances."""
|
||||
|
|
|
|||
|
|
@ -635,7 +635,7 @@ class ReportErrorDialog(QDialog):
|
|||
hbox = QHBoxLayout()
|
||||
hbox.addStretch()
|
||||
btn = QPushButton("Close")
|
||||
btn.clicked.connect(self.close) # type: ignore[arg-type]
|
||||
btn.clicked.connect(self.close)
|
||||
hbox.addWidget(btn)
|
||||
vbox.addLayout(hbox)
|
||||
|
||||
|
|
|
|||
|
|
@ -470,8 +470,7 @@ class SessionManager(QObject):
|
|||
if tab.get('active', False):
|
||||
tab_to_focus = i
|
||||
if new_tab.data.pinned:
|
||||
tabbed_browser.widget.set_tab_pinned(new_tab,
|
||||
new_tab.data.pinned)
|
||||
new_tab.set_pinned(True)
|
||||
if tab_to_focus is not None:
|
||||
tabbed_browser.widget.setCurrentIndex(tab_to_focus)
|
||||
if win.get('active', False):
|
||||
|
|
|
|||
|
|
@ -644,12 +644,10 @@ def parse_javascript_url(url: QUrl) -> str:
|
|||
raise Error("URL contains unexpected components: {}"
|
||||
.format(url.authority()))
|
||||
|
||||
code = url.path(QUrl.FullyDecoded)
|
||||
if url.hasQuery():
|
||||
code += '?' + url.query(QUrl.FullyDecoded)
|
||||
if url.hasFragment():
|
||||
code += '#' + url.fragment(QUrl.FullyDecoded)
|
||||
urlstr = url.toString(QUrl.FullyEncoded) # type: ignore[arg-type]
|
||||
urlstr = urllib.parse.unquote(urlstr)
|
||||
|
||||
code = urlstr[len('javascript:'):]
|
||||
if not code:
|
||||
raise Error("Resulted in empty JavaScript code")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
adblock==0.3.1
|
||||
attrs==20.1.0
|
||||
attrs==20.2.0
|
||||
colorama==0.4.3
|
||||
cssutils==1.0.2
|
||||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
Pygments==2.6.1
|
||||
Pygments==2.7.1
|
||||
pyPEG2==2.15.2
|
||||
PyYAML==5.3.1
|
||||
|
|
|
|||
|
|
@ -46,10 +46,12 @@ class AsciiDoc:
|
|||
FILES = ['faq', 'changelog', 'contributing', 'quickstart', 'userscripts']
|
||||
|
||||
def __init__(self,
|
||||
asciidoc: Optional[List[str]],
|
||||
asciidoc: Optional[str],
|
||||
asciidoc_python: Optional[str],
|
||||
website: Optional[str]) -> None:
|
||||
self._cmd = None # type: Optional[List[str]]
|
||||
self._asciidoc = asciidoc
|
||||
self._asciidoc_python = asciidoc_python
|
||||
self._website = website
|
||||
self._homedir = None # type: Optional[pathlib.Path]
|
||||
self._themedir = None # type: Optional[pathlib.Path]
|
||||
|
|
@ -218,7 +220,9 @@ class AsciiDoc:
|
|||
def _get_asciidoc_cmd(self) -> List[str]:
|
||||
"""Try to find out what commandline to use to invoke asciidoc."""
|
||||
if self._asciidoc is not None:
|
||||
return self._asciidoc
|
||||
python = (sys.executable if self._asciidoc_python is None
|
||||
else self._asciidoc_python)
|
||||
return [python, self._asciidoc]
|
||||
|
||||
for executable in ['asciidoc', 'asciidoc.py']:
|
||||
try:
|
||||
|
|
@ -270,10 +274,12 @@ def parse_args() -> argparse.Namespace:
|
|||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--website', help="Build website into a given "
|
||||
"directory.")
|
||||
parser.add_argument('--asciidoc', help="Full path to python and "
|
||||
"asciidoc.py. If not given, it's searched in PATH.",
|
||||
nargs=2, required=False,
|
||||
metavar=('PYTHON', 'ASCIIDOC'))
|
||||
parser.add_argument('--asciidoc', help="Full path to asciidoc.py. "
|
||||
"If not given, it's searched in PATH.",
|
||||
nargs='?')
|
||||
parser.add_argument('--asciidoc-python', help="Python to use for asciidoc."
|
||||
"If not given, the current Python interpreter is used.",
|
||||
nargs='?')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
|
|
@ -301,7 +307,8 @@ def main(colors: bool = False) -> None:
|
|||
utils.change_cwd()
|
||||
utils.use_color = colors
|
||||
args = parse_args()
|
||||
run(asciidoc=args.asciidoc, website=args.website)
|
||||
run(asciidoc=args.asciidoc, asciidoc_python=args.asciidoc_python,
|
||||
website=args.website)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -78,10 +78,11 @@ def call_tox(toxenv, *args, python=sys.executable):
|
|||
def run_asciidoc2html(args):
|
||||
"""Common buildsteps used for all OS'."""
|
||||
utils.print_title("Running asciidoc2html.py")
|
||||
a2h_args = []
|
||||
if args.asciidoc is not None:
|
||||
a2h_args = ['--asciidoc'] + args.asciidoc
|
||||
else:
|
||||
a2h_args = []
|
||||
a2h_args += ['--asciidoc', args.asciidoc]
|
||||
if args.asciidoc_python is not None:
|
||||
a2h_args += ['--asciidoc-python', args.asciidoc_python]
|
||||
call_script('asciidoc2html.py', *a2h_args)
|
||||
|
||||
|
||||
|
|
@ -201,7 +202,7 @@ def build_mac():
|
|||
utils.print_title("Updating 3rdparty content")
|
||||
update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False)
|
||||
utils.print_title("Building .app via pyinstaller")
|
||||
call_tox('pyinstaller', '-r')
|
||||
call_tox('pyinstaller-64', '-r')
|
||||
utils.print_title("Patching .app")
|
||||
patch_mac_app()
|
||||
utils.print_title("Building .dmg")
|
||||
|
|
@ -457,10 +458,12 @@ def main():
|
|||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--no-asciidoc', action='store_true',
|
||||
help="Don't generate docs")
|
||||
parser.add_argument('--asciidoc', help="Full path to python and "
|
||||
"asciidoc.py. If not given, it's searched in PATH.",
|
||||
nargs=2, required=False,
|
||||
metavar=('PYTHON', 'ASCIIDOC'))
|
||||
parser.add_argument('--asciidoc', help="Full path to asciidoc.py. "
|
||||
"If not given, it's searched in PATH.",
|
||||
nargs='?')
|
||||
parser.add_argument('--asciidoc-python', help="Python to use for asciidoc."
|
||||
"If not given, the current Python interpreter is used.",
|
||||
nargs='?')
|
||||
parser.add_argument('--upload', action='store_true', required=False,
|
||||
help="Toggle to upload the release to GitHub")
|
||||
args = parser.parse_args()
|
||||
|
|
|
|||
|
|
@ -30,33 +30,55 @@ import tokenize
|
|||
import traceback
|
||||
import collections
|
||||
import pathlib
|
||||
from typing import List, Iterator, Optional
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
|
||||
os.pardir))
|
||||
|
||||
from scripts import utils
|
||||
|
||||
|
||||
def _get_files(only_py=False):
|
||||
"""Iterate over all python files and yield filenames."""
|
||||
for (dirpath, _dirnames, filenames) in os.walk('.'):
|
||||
parts = dirpath.split(os.sep)
|
||||
if len(parts) >= 2:
|
||||
rootdir = parts[1]
|
||||
if rootdir.startswith('.') or rootdir == 'htmlcov':
|
||||
# ignore hidden dirs and htmlcov
|
||||
continue
|
||||
|
||||
if only_py:
|
||||
endings = {'.py'}
|
||||
else:
|
||||
endings = {'.py', '.asciidoc', '.js', '.feature'}
|
||||
files = (e for e in filenames if os.path.splitext(e)[1] in endings)
|
||||
for name in files:
|
||||
yield os.path.join(dirpath, name)
|
||||
BINARY_EXTS = {'.png', '.icns', '.ico', '.bmp', '.gz', '.bin', '.pdf',
|
||||
'.sqlite', '.woff2', '.whl'}
|
||||
|
||||
|
||||
def check_git():
|
||||
def _get_files(
|
||||
*,
|
||||
verbose: bool,
|
||||
ignored: List[pathlib.Path] = None
|
||||
) -> Iterator[pathlib.Path]:
|
||||
"""Iterate over all files and yield filenames."""
|
||||
filenames = subprocess.run(
|
||||
['git', 'ls-files', '--cached', '--others', '--exclude-standard', '-z'],
|
||||
stdout=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
check=True,
|
||||
)
|
||||
all_ignored = ignored or []
|
||||
all_ignored.append(
|
||||
pathlib.Path('tests', 'unit', 'scripts', 'importer_sample', 'chrome'))
|
||||
|
||||
for filename in filenames.stdout.split('\0'):
|
||||
path = pathlib.Path(filename)
|
||||
is_ignored = any(path == p or p in path.parents for p in all_ignored)
|
||||
if not filename or path.suffix in BINARY_EXTS or is_ignored:
|
||||
continue
|
||||
|
||||
try:
|
||||
with tokenize.open(str(path)):
|
||||
pass
|
||||
except SyntaxError as e:
|
||||
# Could not find encoding
|
||||
utils.print_col("{} - maybe {} should be added to BINARY_EXTS?".format(
|
||||
str(e).capitalize(), path.suffix), 'yellow')
|
||||
continue
|
||||
|
||||
if verbose:
|
||||
print(path)
|
||||
|
||||
yield path
|
||||
|
||||
|
||||
def check_git(_args: argparse.Namespace = None) -> bool:
|
||||
"""Check for uncommitted git files.."""
|
||||
if not os.path.isdir(".git"):
|
||||
print("No .git dir, ignoring")
|
||||
|
|
@ -79,7 +101,7 @@ def check_git():
|
|||
return status
|
||||
|
||||
|
||||
def check_spelling():
|
||||
def check_spelling(args: argparse.Namespace) -> Optional[bool]:
|
||||
"""Check commonly misspelled words."""
|
||||
# Words which I often misspell
|
||||
words = {'behaviour', 'quitted', 'likelyhood', 'sucessfully',
|
||||
|
|
@ -90,37 +112,36 @@ def check_spelling():
|
|||
'exitted', 'mininum', 'resett?ed', 'recieved', 'regularily',
|
||||
'underlaying', 'inexistant', 'elipsis', 'commiting', 'existant',
|
||||
'resetted', 'similarily', 'informations', 'an url', 'treshold',
|
||||
'artefact'}
|
||||
'artefact', 'an unix', 'an utf', 'an unicode'}
|
||||
|
||||
# Words which look better when splitted, but might need some fine tuning.
|
||||
words |= {'webelements', 'mouseevent', 'keysequence', 'normalmode',
|
||||
'eventloops', 'sizehint', 'statemachine', 'metaobject',
|
||||
'logrecord', 'filetype'}
|
||||
'logrecord'}
|
||||
|
||||
# Files which should be ignored, e.g. because they come from another
|
||||
# package
|
||||
hint_data = pathlib.Path('tests', 'end2end', 'data', 'hints')
|
||||
ignored = [
|
||||
os.path.join('.', 'scripts', 'dev', 'misc_checks.py'),
|
||||
os.path.join('.', 'qutebrowser', '3rdparty', 'pdfjs'),
|
||||
os.path.join('.', 'tests', 'end2end', 'data', 'hints', 'ace',
|
||||
'ace.js'),
|
||||
pathlib.Path('scripts', 'dev', 'misc_checks.py'),
|
||||
pathlib.Path('qutebrowser', '3rdparty', 'pdfjs'),
|
||||
hint_data / 'ace' / 'ace.js',
|
||||
hint_data / 'bootstrap' / 'bootstrap.css',
|
||||
]
|
||||
|
||||
seen = collections.defaultdict(list)
|
||||
try:
|
||||
ok = True
|
||||
for fn in _get_files():
|
||||
with tokenize.open(fn) as f:
|
||||
if any(fn.startswith(i) for i in ignored):
|
||||
continue
|
||||
for path in _get_files(verbose=args.verbose, ignored=ignored):
|
||||
with tokenize.open(str(path)) as f:
|
||||
for line in f:
|
||||
for w in words:
|
||||
pattern = '[{}{}]{}'.format(w[0], w[0].upper(), w[1:])
|
||||
if (re.search(pattern, line) and
|
||||
fn not in seen[w] and
|
||||
path not in seen[w] and
|
||||
'# pragma: no spellcheck' not in line):
|
||||
print('Found "{}" in {}!'.format(w, fn))
|
||||
seen[w].append(fn)
|
||||
print('Found "{}" in {}!'.format(w, path))
|
||||
seen[w].append(path)
|
||||
ok = False
|
||||
print()
|
||||
return ok
|
||||
|
|
@ -129,15 +150,18 @@ def check_spelling():
|
|||
return None
|
||||
|
||||
|
||||
def check_vcs_conflict():
|
||||
def check_vcs_conflict(args: argparse.Namespace) -> Optional[bool]:
|
||||
"""Check VCS conflict markers."""
|
||||
try:
|
||||
ok = True
|
||||
for fn in _get_files(only_py=True):
|
||||
with tokenize.open(fn) as f:
|
||||
for path in _get_files(verbose=args.verbose):
|
||||
if path.suffix in {'.rst', '.asciidoc'}:
|
||||
# False positives
|
||||
continue
|
||||
with tokenize.open(str(path)) as f:
|
||||
for line in f:
|
||||
if any(line.startswith(c * 7) for c in '<>=|'):
|
||||
print("Found conflict marker in {}".format(fn))
|
||||
print("Found conflict marker in {}".format(path))
|
||||
ok = False
|
||||
print()
|
||||
return ok
|
||||
|
|
@ -146,7 +170,7 @@ def check_vcs_conflict():
|
|||
return None
|
||||
|
||||
|
||||
def check_userscripts_descriptions():
|
||||
def check_userscripts_descriptions(_args: argparse.Namespace = None) -> bool:
|
||||
"""Make sure all userscripts are described properly."""
|
||||
folder = pathlib.Path('misc/userscripts')
|
||||
readme = folder / 'README.md'
|
||||
|
|
@ -178,20 +202,21 @@ def check_userscripts_descriptions():
|
|||
return ok
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--verbose', action='store_true', help='Show checked filenames')
|
||||
parser.add_argument('checker',
|
||||
choices=('git', 'vcs', 'spelling', 'userscripts'),
|
||||
help="Which checker to run.")
|
||||
args = parser.parse_args()
|
||||
if args.checker == 'git':
|
||||
ok = check_git()
|
||||
ok = check_git(args)
|
||||
elif args.checker == 'vcs':
|
||||
ok = check_vcs_conflict()
|
||||
ok = check_vcs_conflict(args)
|
||||
elif args.checker == 'spelling':
|
||||
ok = check_spelling()
|
||||
ok = check_spelling(args)
|
||||
elif args.checker == 'userscripts':
|
||||
ok = check_userscripts_descriptions()
|
||||
ok = check_userscripts_descriptions(args)
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ CHANGELOG_URLS = {
|
|||
'pylint': 'http://pylint.pycqa.org/en/latest/whatsnew/changelog.html',
|
||||
'setuptools': 'https://github.com/pypa/setuptools/blob/master/CHANGES.rst',
|
||||
'pytest-cov': 'https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst',
|
||||
'pytest-xdist': 'https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst',
|
||||
'pytest-forked': 'https://github.com/pytest-dev/pytest-forked/blob/master/CHANGELOG',
|
||||
'execnet': 'https://execnet.readthedocs.io/en/latest/changelog.html',
|
||||
'apipkg': 'https://github.com/pytest-dev/apipkg/blob/master/CHANGELOG',
|
||||
'pytest-rerunfailures': 'https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst',
|
||||
'requests': 'https://github.com/psf/requests/blob/master/HISTORY.md',
|
||||
'requests-file': 'https://github.com/dashea/requests-file/blob/master/CHANGES.rst',
|
||||
'werkzeug': 'https://github.com/pallets/werkzeug/blob/master/CHANGES.rst',
|
||||
|
|
@ -101,7 +106,12 @@ CHANGELOG_URLS = {
|
|||
'pep517': 'https://github.com/pypa/pep517/commits/master',
|
||||
'cryptography': 'https://cryptography.io/en/latest/changelog/',
|
||||
'toml': 'https://github.com/uiri/toml/releases',
|
||||
'pyqt': 'https://www.riverbankcomputing.com/',
|
||||
'PyQt5': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQtWebEngine': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQt-builder': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQt5-sip': 'https://www.riverbankcomputing.com/news',
|
||||
'sip': 'https://www.riverbankcomputing.com/news',
|
||||
'Pygments': 'https://pygments.org/docs/changelog/',
|
||||
'vulture': 'https://github.com/jendrikseipp/vulture/blob/master/CHANGELOG.md',
|
||||
'distlib': 'https://bitbucket.org/pypa/distlib/src/master/CHANGES.rst',
|
||||
'py-cpuinfo': 'https://github.com/workhorsy/py-cpuinfo/blob/master/ChangeLog',
|
||||
|
|
@ -111,6 +121,7 @@ CHANGELOG_URLS = {
|
|||
'idna': 'https://github.com/kjd/idna/blob/master/HISTORY.rst',
|
||||
'tldextract': 'https://github.com/john-kurkowski/tldextract/blob/master/CHANGELOG.md',
|
||||
'typing_extensions': 'https://github.com/python/typing/commits/master/typing_extensions',
|
||||
'diff_cover': 'https://github.com/Bachmann1234/diff_cover/blob/master/CHANGELOG',
|
||||
}
|
||||
|
||||
# PyQt versions which need SIP v4
|
||||
|
|
@ -265,8 +276,8 @@ class Change:
|
|||
self.name = name
|
||||
self.old = None
|
||||
self.new = None
|
||||
if name.lower() in CHANGELOG_URLS:
|
||||
self.url = CHANGELOG_URLS[name.lower()]
|
||||
if name in CHANGELOG_URLS:
|
||||
self.url = CHANGELOG_URLS[name]
|
||||
self.link = '[{}]({})'.format(self.name, self.url)
|
||||
else:
|
||||
self.url = '(no changelog)'
|
||||
|
|
@ -313,6 +324,8 @@ def print_changed_files():
|
|||
|
||||
if '==' in line:
|
||||
name, version = line[1:].split('==')
|
||||
if ';' in version: # pip environment markers
|
||||
version = version.split(';')[0].strip()
|
||||
else:
|
||||
name = line[1:]
|
||||
version = '?'
|
||||
|
|
|
|||
|
|
@ -78,13 +78,12 @@ if __name__ == "__main__":
|
|||
print("* Create new release via GitHub (required to upload release "
|
||||
"artifacts)")
|
||||
print("* Linux: git fetch && git checkout v{v} && "
|
||||
"./.venv/bin/python3 scripts/dev/build_release.py --upload"
|
||||
"tox -e build-release -- --upload"
|
||||
.format(v=version))
|
||||
print("* Windows: git fetch; git checkout v{v}; "
|
||||
"py -3 scripts\\dev\\build_release.py --asciidoc "
|
||||
"C:\\Python27\\python "
|
||||
"$env:userprofile\\bin\\asciidoc-8.6.10\\asciidoc.py --upload"
|
||||
"py -3.7 -m tox -e build-release -- --asciidoc "
|
||||
"$env:userprofile\\bin\\asciidoc-9.9.2\\asciidoc.py --upload"
|
||||
.format(v=version))
|
||||
print("* macOS: git fetch && git checkout v{v} && "
|
||||
"python3 scripts/dev/build_release.py --upload"
|
||||
"tox -e build-release -- --upload"
|
||||
.format(v=version))
|
||||
|
|
|
|||
|
|
@ -1613,3 +1613,12 @@ Feature: Tab management
|
|||
And I open data/hello.txt in a new tab
|
||||
And I run :fake-key -g hello-world<enter>
|
||||
Then the message "hello-world" should be shown
|
||||
|
||||
Scenario: Undo after changing tabs_are_windows
|
||||
When I open data/hello.txt
|
||||
And I open data/hello.txt in a new tab
|
||||
And I set tabs.tabs_are_windows to true
|
||||
And I run :tab-close
|
||||
And I run :undo
|
||||
And I run :message-info "Still alive!"
|
||||
Then the message "Still alive!" should be shown
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<body>
|
||||
<p>When <code>hints.hide_unmatched_rapid_hints</code> is set to true (default), rapid hints behave like normal hints, i.e. unmatched hints will be hidden as you type. Setting the option to false will disable hiding in rapid mode, which is sometimes useful (see <a href="https://github.com/qutebrowser/qutebrowser/issues/1799">#1799</a>).</p>
|
||||
<p>Note that when hinting in number mode, the <code>hints.hide_unmatched_rapid_hints</code> option affects typing the hint string (number), but not the filter (letters).</p>
|
||||
<p>Here is couple of invalid links to test the behaviour:</p>
|
||||
<p>Here is couple of invalid links to test the behavior:</p>
|
||||
<p><a href="#foo">one</a></p>
|
||||
<p><a href="#foo">two</a></p>
|
||||
<p><a href="#foo">three</a></p>
|
||||
|
|
|
|||
|
|
@ -52,8 +52,9 @@ class TestFixedDataNetworkReply:
|
|||
b'Hello World! This is a test.'])
|
||||
def test_data(self, qtbot, req, data):
|
||||
reply = networkreply.FixedDataNetworkReply(req, data, 'test/foo')
|
||||
with qtbot.waitSignals([reply.metaDataChanged, reply.readyRead,
|
||||
reply.finished], order='strict'):
|
||||
with qtbot.waitSignal(reply.metaDataChanged), \
|
||||
qtbot.waitSignal(reply.readyRead), \
|
||||
qtbot.waitSignal(reply.finished):
|
||||
pass
|
||||
|
||||
assert reply.bytesAvailable() == len(data)
|
||||
|
|
@ -78,7 +79,7 @@ def test_error_network_reply(qtbot, req):
|
|||
reply = networkreply.ErrorNetworkReply(
|
||||
req, "This is an error", QNetworkReply.UnknownNetworkError)
|
||||
|
||||
with qtbot.waitSignals([reply.error, reply.finished], order='strict'):
|
||||
with qtbot.waitSignal(reply.error), qtbot.waitSignal(reply.finished):
|
||||
pass
|
||||
|
||||
reply.abort() # shouldn't do anything
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ def test_set_pattern(pat, qtbot):
|
|||
for c in cats:
|
||||
c.set_pattern = mock.Mock(spec=[])
|
||||
model.add_category(c)
|
||||
with qtbot.waitSignals([model.layoutAboutToBeChanged, model.layoutChanged],
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(model.layoutAboutToBeChanged), \
|
||||
qtbot.waitSignal(model.layoutChanged):
|
||||
model.set_pattern(pat)
|
||||
for c in cats:
|
||||
c.set_pattern.assert_called_with(pat)
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ def configdata_stub(config_stub, monkeypatch, configdata_init):
|
|||
no_autoconfig=True)),
|
||||
('bindings.commands', configdata.Option(
|
||||
name='bindings.commands',
|
||||
description='Default keybindings',
|
||||
description='Custom keybindings',
|
||||
typ=configtypes.Dict(
|
||||
keytype=configtypes.String(),
|
||||
valtype=configtypes.Dict(
|
||||
|
|
@ -269,13 +269,38 @@ def test_help_completion(qtmodeltester, cmdutils_stub, key_config_stub,
|
|||
(':tab-close', 'Close the current tab.', ''),
|
||||
],
|
||||
"Settings": [
|
||||
('aliases', 'Aliases for commands.', None),
|
||||
('bindings.commands', 'Default keybindings', None),
|
||||
('bindings.default', 'Default keybindings', None),
|
||||
('completion.open_categories', 'Which categories to show (in '
|
||||
'which order) in the :open completion.', None),
|
||||
('content.javascript.enabled', 'Enable/Disable JavaScript', None),
|
||||
('url.searchengines', 'searchengines list', None),
|
||||
(
|
||||
'aliases',
|
||||
'Aliases for commands.',
|
||||
'{"q": "quit"}',
|
||||
),
|
||||
(
|
||||
'bindings.commands',
|
||||
'Custom keybindings',
|
||||
('{"normal": {"<Ctrl+q>": "quit", "I": "invalid", "ZQ": "quit", '
|
||||
'"d": "scroll down"}}'),
|
||||
),
|
||||
(
|
||||
'bindings.default',
|
||||
'Default keybindings',
|
||||
'{"normal": {"<Ctrl+q>": "quit", "d": "tab-close"}}',
|
||||
),
|
||||
(
|
||||
'completion.open_categories',
|
||||
'Which categories to show (in which order) in the :open completion.',
|
||||
'["searchengines", "quickmarks", "bookmarks", "history"]',
|
||||
),
|
||||
(
|
||||
'content.javascript.enabled',
|
||||
'Enable/Disable JavaScript',
|
||||
'true'
|
||||
),
|
||||
(
|
||||
'url.searchengines',
|
||||
'searchengines list',
|
||||
('{"DEFAULT": "https://duckduckgo.com/?q={}", '
|
||||
'"google": "https://google.com/?q={}"}'),
|
||||
),
|
||||
],
|
||||
})
|
||||
|
||||
|
|
@ -909,7 +934,7 @@ def test_setting_option_completion(qtmodeltester, config_stub,
|
|||
_check_completions(model, {
|
||||
"Options": [
|
||||
('aliases', 'Aliases for commands.', '{"q": "quit"}'),
|
||||
('bindings.commands', 'Default keybindings', (
|
||||
('bindings.commands', 'Custom keybindings', (
|
||||
'{"normal": {"<Ctrl+q>": "quit", "I": "invalid", '
|
||||
'"ZQ": "quit", "d": "scroll down"}}')),
|
||||
('completion.open_categories', 'Which categories to show (in '
|
||||
|
|
@ -933,7 +958,7 @@ def test_setting_dict_option_completion(qtmodeltester, config_stub,
|
|||
_check_completions(model, {
|
||||
"Dict options": [
|
||||
('aliases', 'Aliases for commands.', '{"q": "quit"}'),
|
||||
('bindings.commands', 'Default keybindings', (
|
||||
('bindings.commands', 'Custom keybindings', (
|
||||
'{"normal": {"<Ctrl+q>": "quit", "I": "invalid", '
|
||||
'"ZQ": "quit", "d": "scroll down"}}')),
|
||||
('url.searchengines', 'searchengines list',
|
||||
|
|
|
|||
|
|
@ -432,11 +432,12 @@ class TestConfig:
|
|||
assert conf.get_obj(name1) == 'never'
|
||||
assert conf.get_obj(name2) is True
|
||||
|
||||
with qtbot.waitSignals([conf.changed, conf.changed]) as blocker:
|
||||
with qtbot.waitSignal(conf.changed), qtbot.waitSignal(conf.changed):
|
||||
conf.clear(save_yaml=save_yaml)
|
||||
|
||||
options = {e.args[0] for e in blocker.all_signals_and_args}
|
||||
assert options == {name1, name2}
|
||||
# Doesn't work with PyQt 5.15.1 workaround
|
||||
# options = {blocker1.args[0], blocker2.args[0]}
|
||||
# assert options == {name1, name2}
|
||||
|
||||
if save_yaml:
|
||||
assert yaml_value(name1) is usertypes.UNSET
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from PyQt5.QtCore import QSettings
|
|||
|
||||
from qutebrowser.config import (config, configfiles, configexc, configdata,
|
||||
configtypes)
|
||||
from qutebrowser.utils import utils, usertypes, urlmatch
|
||||
from qutebrowser.utils import utils, usertypes, urlmatch, standarddir
|
||||
from qutebrowser.keyinput import keyutils
|
||||
|
||||
|
||||
|
|
@ -1064,6 +1064,24 @@ class TestConfigPy:
|
|||
|
||||
assert not config.instance.get_obj('content.javascript.enabled')
|
||||
|
||||
def test_source_configpy_arg(self, tmpdir, data_tmpdir, monkeypatch):
|
||||
alt_filename = 'alt-config.py'
|
||||
|
||||
alt_confpy_dir = tmpdir / 'alt-confpy-dir'
|
||||
alt_confpy_dir.ensure(dir=True)
|
||||
monkeypatch.setattr(standarddir, 'config_py',
|
||||
lambda: str(alt_confpy_dir / alt_filename))
|
||||
|
||||
subfile = alt_confpy_dir / 'subfile.py'
|
||||
subfile.write_text("c.content.javascript.enabled = False",
|
||||
encoding='utf-8')
|
||||
|
||||
alt_confpy = ConfPy(alt_confpy_dir, alt_filename)
|
||||
alt_confpy.write("config.source('subfile.py')")
|
||||
alt_confpy.read()
|
||||
|
||||
assert not config.instance.get_obj('content.javascript.enabled')
|
||||
|
||||
def test_source_errors(self, tmpdir, confpy):
|
||||
subfile = tmpdir / 'config' / 'subfile.py'
|
||||
subfile.write_text("c.foo = 42", encoding='utf-8')
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from unittest import mock
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtCore import Qt, PYQT_VERSION
|
||||
import pytest
|
||||
|
||||
from qutebrowser.keyinput import basekeyparser, keyutils
|
||||
|
|
@ -309,6 +309,8 @@ class TestCount:
|
|||
# https://github.com/qutebrowser/qutebrowser/issues/3743
|
||||
handle_text(prompt_keyparser, Qt.Key_twosuperior, Qt.Key_B, Qt.Key_A)
|
||||
|
||||
@pytest.mark.skipif(PYQT_VERSION == 0x050F01,
|
||||
reason='waitSignals is broken in PyQt 5.15.1')
|
||||
def test_count_keystring_update(self, qtbot,
|
||||
handle_text, prompt_keyparser):
|
||||
"""Make sure the keystring is updated correctly when entering count."""
|
||||
|
|
|
|||
|
|
@ -94,8 +94,9 @@ class TestTabWidget:
|
|||
config_stub.val.tabs.position = "left"
|
||||
|
||||
pinned_num = [1, num_tabs - 1]
|
||||
for tab in pinned_num:
|
||||
widget.set_tab_pinned(widget.widget(tab), True)
|
||||
for num in pinned_num:
|
||||
tab = widget.widget(num)
|
||||
tab.set_pinned(True)
|
||||
|
||||
first_size = widget.tabBar().tabSizeHint(0)
|
||||
first_size_min = widget.tabBar().minimumTabSizeHint(0)
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ def fake_proc(monkeypatch, stubs):
|
|||
|
||||
def test_start(proc, qtbot, message_mock, py_proc):
|
||||
"""Test simply starting a process."""
|
||||
with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(proc.started, timeout=10000), \
|
||||
qtbot.waitSignal(proc.finished, timeout=10000):
|
||||
argv = py_proc("import sys; print('test'); sys.exit(0)")
|
||||
proc.start(*argv)
|
||||
|
||||
|
|
@ -70,8 +70,8 @@ def test_start_verbose(proc, qtbot, message_mock, py_proc):
|
|||
"""Test starting a process verbosely."""
|
||||
proc.verbose = True
|
||||
|
||||
with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(proc.started, timeout=10000), \
|
||||
qtbot.waitSignal(proc.finished, timeout=10000):
|
||||
argv = py_proc("import sys; print('test'); sys.exit(0)")
|
||||
proc.start(*argv)
|
||||
|
||||
|
|
@ -99,9 +99,8 @@ def test_start_output_message(proc, qtbot, caplog, message_mock, py_proc,
|
|||
code.append("sys.exit(0)")
|
||||
|
||||
with caplog.at_level(logging.ERROR, 'message'):
|
||||
with qtbot.waitSignals([proc.started, proc.finished],
|
||||
timeout=10000,
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(proc.started, timeout=10000), \
|
||||
qtbot.waitSignal(proc.finished, timeout=10000):
|
||||
argv = py_proc(';'.join(code))
|
||||
proc.start(*argv)
|
||||
|
||||
|
|
@ -147,8 +146,8 @@ def test_start_env(monkeypatch, qtbot, py_proc):
|
|||
sys.exit(0)
|
||||
""")
|
||||
|
||||
with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(proc.started, timeout=10000), \
|
||||
qtbot.waitSignal(proc.finished, timeout=10000):
|
||||
proc.start(*argv)
|
||||
|
||||
data = qutescheme.spawn_output
|
||||
|
|
@ -187,12 +186,12 @@ def test_double_start(qtbot, proc, py_proc):
|
|||
|
||||
def test_double_start_finished(qtbot, proc, py_proc):
|
||||
"""Test starting a GUIProcess twice (with the first call finished)."""
|
||||
with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(proc.started, timeout=10000), \
|
||||
qtbot.waitSignal(proc.finished, timeout=10000):
|
||||
argv = py_proc("import sys; sys.exit(0)")
|
||||
proc.start(*argv)
|
||||
with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(proc.started, timeout=10000), \
|
||||
qtbot.waitSignal(proc.finished, timeout=10000):
|
||||
argv = py_proc("import sys; sys.exit(0)")
|
||||
proc.start(*argv)
|
||||
|
||||
|
|
@ -267,8 +266,8 @@ def test_exit_successful_output(qtbot, proc, py_proc, stream):
|
|||
|
||||
def test_stdout_not_decodable(proc, qtbot, message_mock, py_proc):
|
||||
"""Test handling malformed utf-8 in stdout."""
|
||||
with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(proc.started, timeout=10000), \
|
||||
qtbot.waitSignal(proc.finished, timeout=10000):
|
||||
argv = py_proc(r"""
|
||||
import sys
|
||||
# Using \x81 because it's invalid in UTF-8 and CP1252
|
||||
|
|
|
|||
|
|
@ -493,10 +493,10 @@ NEW_VERSION = str(ipc.PROTOCOL_VERSION + 1).encode('utf-8')
|
|||
(b'{"args": [], "target_arg": null}\n', 'invalid version'),
|
||||
])
|
||||
def test_invalid_data(qtbot, ipc_server, connected_socket, caplog, data, msg):
|
||||
signals = [ipc_server.got_invalid_data, connected_socket.disconnected]
|
||||
with caplog.at_level(logging.ERROR):
|
||||
with qtbot.assertNotEmitted(ipc_server.got_args):
|
||||
with qtbot.waitSignals(signals, order='strict'):
|
||||
with qtbot.waitSignal(ipc_server.got_invalid_data), \
|
||||
qtbot.waitSignal(connected_socket.disconnected):
|
||||
connected_socket.write(data)
|
||||
|
||||
invalid_msg = 'Ignoring invalid IPC data from socket '
|
||||
|
|
@ -514,8 +514,8 @@ def test_multiline(qtbot, ipc_server, connected_socket):
|
|||
version=ipc.PROTOCOL_VERSION))
|
||||
|
||||
with qtbot.assertNotEmitted(ipc_server.got_invalid_data):
|
||||
with qtbot.waitSignals([ipc_server.got_args, ipc_server.got_args],
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(ipc_server.got_args), \
|
||||
qtbot.waitSignal(ipc_server.got_args):
|
||||
connected_socket.write(data.encode('utf-8'))
|
||||
|
||||
assert len(spy) == 2
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ def test_not_found(caplog):
|
|||
|
||||
|
||||
def test_utf8():
|
||||
"""Test rendering with an UTF8 template.
|
||||
"""Test rendering with a UTF8 template.
|
||||
|
||||
This was an attempt to get a failing test case for #127 but it seems
|
||||
the issue is elsewhere.
|
||||
|
|
|
|||
|
|
@ -606,9 +606,9 @@ URL_TEXT = hst.text(alphabet=string.ascii_letters)
|
|||
@hypothesis.given(pattern=hst.builds(
|
||||
lambda *a: ''.join(a),
|
||||
# Scheme
|
||||
hst.one_of(hst.just('*'), hst.just('http'), hst.just('file')),
|
||||
hst.sampled_from(['*', 'http', 'file']),
|
||||
# Separator
|
||||
hst.one_of(hst.just(':'), hst.just('://')),
|
||||
hst.sampled_from([':', '://']),
|
||||
# Host
|
||||
hst.one_of(hst.just('*'),
|
||||
hst.builds(lambda *a: ''.join(a), hst.just('*.'), URL_TEXT),
|
||||
|
|
|
|||
|
|
@ -781,6 +781,8 @@ class TestParseJavascriptUrl:
|
|||
|
||||
@pytest.mark.parametrize('url, source', [
|
||||
(QUrl('javascript:"hello" %0a "world"'), '"hello" \n "world"'),
|
||||
(QUrl('javascript:/'), '/'),
|
||||
(QUrl('javascript:///'), '///'),
|
||||
# https://github.com/web-platform-tests/wpt/blob/master/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-query-fragment-components.html
|
||||
(QUrl('javascript:"nope" ? "yep" : "what";'), '"nope" ? "yep" : "what";'),
|
||||
(QUrl('javascript:"wrong"; // # %0a "ok";'), '"wrong"; // # \n "ok";'),
|
||||
|
|
|
|||
|
|
@ -53,23 +53,25 @@ def test_done(mode, answer, signal_names, question, qtbot):
|
|||
question.mode = mode
|
||||
question.answer = answer
|
||||
signals = [getattr(question, name) for name in signal_names]
|
||||
with qtbot.waitSignals(signals, order='strict'):
|
||||
question.done()
|
||||
blockers = [qtbot.waitSignal(signal) for signal in signals]
|
||||
|
||||
question.done()
|
||||
for blocker in blockers:
|
||||
blocker.wait()
|
||||
|
||||
assert not question.is_aborted
|
||||
|
||||
|
||||
def test_cancel(question, qtbot):
|
||||
"""Test Question.cancel()."""
|
||||
with qtbot.waitSignals([question.cancelled, question.completed],
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(question.cancelled), qtbot.waitSignal(question.completed):
|
||||
question.cancel()
|
||||
assert not question.is_aborted
|
||||
|
||||
|
||||
def test_abort(question, qtbot):
|
||||
"""Test Question.abort()."""
|
||||
with qtbot.waitSignals([question.aborted, question.completed],
|
||||
order='strict'):
|
||||
with qtbot.waitSignal(question.aborted), qtbot.waitSignal(question.completed):
|
||||
question.abort()
|
||||
assert question.is_aborted
|
||||
|
||||
|
|
|
|||
14
tox.ini
14
tox.ini
|
|
@ -224,3 +224,17 @@ deps =
|
|||
-r{toxinidir}/misc/requirements/requirements-sphinx.txt
|
||||
commands =
|
||||
{envpython} -m sphinx -jauto -W --color {posargs} {toxinidir}/doc/extapi/ {toxinidir}/doc/extapi/_build/
|
||||
|
||||
[testenv:build-release]
|
||||
basepython = {env:PYTHON:python3}
|
||||
pip_version = pip
|
||||
passenv = *
|
||||
usedevelop = true
|
||||
deps =
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/misc/requirements/requirements-tox.txt
|
||||
-r{toxinidir}/misc/requirements/requirements-pyqt.txt
|
||||
-r{toxinidir}/misc/requirements/requirements-dev.txt
|
||||
-r{toxinidir}/misc/requirements/requirements-pyinstaller.txt
|
||||
commands =
|
||||
{envpython} {toxinidir}/scripts/dev/build_release.py {posargs}
|
||||
|
|
|
|||
Loading…
Reference in New Issue