Merge remote-tracking branch 'upstream/master' into perdomainstylesheets
This commit is contained in:
commit
b4dd94b6e9
@ -5,15 +5,15 @@ cache:
|
|||||||
build: off
|
build: off
|
||||||
environment:
|
environment:
|
||||||
PYTHONUNBUFFERED: 1
|
PYTHONUNBUFFERED: 1
|
||||||
PYTHON: C:\Python36\python.exe
|
PYTHON: C:\Python36-x64\python.exe
|
||||||
matrix:
|
matrix:
|
||||||
- TESTENV: py36-pyqt510
|
- TESTENV: py36-pyqt511
|
||||||
- TESTENV: pylint
|
- TESTENV: pylint
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- '%PYTHON% -m pip install -U pip'
|
- '%PYTHON% -m pip install -U pip'
|
||||||
- '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
|
- '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
|
||||||
- 'set PATH=%PATH%;C:\Python36'
|
- 'set PATH=C:\Python36-x64;%PATH'
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- '%PYTHON% -m tox -e %TESTENV%'
|
- '%PYTHON% -m tox -e %TESTENV%'
|
||||||
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
doc/changelog.asciidoc merge=union
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -1,5 +1,5 @@
|
|||||||
qutebrowser/browser/history.py @rcorre
|
qutebrowser/browser/history.py @rcorre
|
||||||
qutebrowser/completion/* @rcorre
|
qutebrowser/completion/** @rcorre
|
||||||
qutebrowser/misc/sql.py @rcorre
|
qutebrowser/misc/sql.py @rcorre
|
||||||
tests/end2end/features/completion.feature @rcorre
|
tests/end2end/features/completion.feature @rcorre
|
||||||
tests/end2end/features/test_completion_bdd.py @rcorre
|
tests/end2end/features/test_completion_bdd.py @rcorre
|
||||||
|
13
.github/CONTRIBUTING.asciidoc
vendored
13
.github/CONTRIBUTING.asciidoc
vendored
@ -1,3 +1,11 @@
|
|||||||
|
IMPORTANT: *Currently, bigger changes are going on in qutebrowser, as
|
||||||
|
part of a
|
||||||
|
https://lists.schokokeks.org/pipermail/qutebrowser-announce/2018-September/000051.html[student research project]
|
||||||
|
about adding a plugin API to qutebrowser and moving a lot of code from the code
|
||||||
|
into plugins.* Due to that, bandwidth for pull request review is currently
|
||||||
|
very limited, and contributions might lead to merge conflicts due to
|
||||||
|
ongoing refactorings.
|
||||||
|
|
||||||
- Before you start to work on something, please leave a comment on the relevant
|
- Before you start to work on something, please leave a comment on the relevant
|
||||||
issue (or open one). This makes sure there is no duplicate work done.
|
issue (or open one). This makes sure there is no duplicate work done.
|
||||||
|
|
||||||
@ -7,6 +15,5 @@
|
|||||||
- If you are stuck somewhere or have questions,
|
- If you are stuck somewhere or have questions,
|
||||||
https://github.com/qutebrowser/qutebrowser#getting-help[please ask]!
|
https://github.com/qutebrowser/qutebrowser#getting-help[please ask]!
|
||||||
|
|
||||||
See the full contribution documentation for details and other useful hints:
|
See the link:../doc/contributing.asciidoc[full contribution documentation] for
|
||||||
|
details and other useful hints.
|
||||||
include::../doc/contributing.asciidoc[]
|
|
||||||
|
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@ -1,2 +0,0 @@
|
|||||||
<!-- If this is a bug report, please remember to mention your version info from
|
|
||||||
`:open qute:version` or `qutebrowser --version` -->
|
|
15
.github/ISSUE_TEMPLATE/1_Bug_report.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE/1_Bug_report.md
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
name: 🐛 Bug Report
|
||||||
|
about: Report errors and problems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version info (see `:version`)**:
|
||||||
|
|
||||||
|
**Does the bug happen if you start with `--temp-basedir`?** (if applicable):
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
|
||||||
|
**How to reproduce**
|
||||||
|
<!-- Link to the affected site, or steps to reproduce the issue
|
||||||
|
(if possible/applicable). -->
|
5
.github/ISSUE_TEMPLATE/2_Feature_request.md
vendored
Normal file
5
.github/ISSUE_TEMPLATE/2_Feature_request.md
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
name: 🚀 Feature Request
|
||||||
|
about: Ideas for new features and improvements
|
||||||
|
|
||||||
|
---
|
12
.github/ISSUE_TEMPLATE/3_Support_question.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/3_Support_question.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: ❓ Support Question
|
||||||
|
about: It's okay to ask questions via GitHub, but IRC/Reddit/Mailinglist might be better.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
While it's fine to ask questions here, check the documentation for better
|
||||||
|
ways to get help:
|
||||||
|
|
||||||
|
https://github.com/qutebrowser/qutebrowser#getting-help
|
||||||
|
-->
|
11
.github/ISSUE_TEMPLATE/4_Security_issue.md
vendored
Normal file
11
.github/ISSUE_TEMPLATE/4_Security_issue.md
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
name: ⛔ Security Issue
|
||||||
|
about: Contact mail@qutebrowser.org for security issues.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
⚠ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW.
|
||||||
|
|
||||||
|
If you have found a security issue in qutebrowser, please send the details to
|
||||||
|
mail [at] qutebrowser.org and don't disclose it publicly until we can provide a
|
||||||
|
fix for it
|
BIN
.github/img/macstadium.png
vendored
Normal file
BIN
.github/img/macstadium.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,7 +16,6 @@ __pycache__
|
|||||||
/doc/*.html
|
/doc/*.html
|
||||||
/README.html
|
/README.html
|
||||||
/qutebrowser/html/doc/
|
/qutebrowser/html/doc/
|
||||||
/qutebrowser/html/*.html
|
|
||||||
/.venv*
|
/.venv*
|
||||||
/.coverage
|
/.coverage
|
||||||
/htmlcov
|
/htmlcov
|
||||||
|
@ -54,7 +54,7 @@ no-docstring-rgx=(^_|^main$)
|
|||||||
|
|
||||||
[FORMAT]
|
[FORMAT]
|
||||||
max-line-length=79
|
max-line-length=79
|
||||||
ignore-long-lines=(<?https?://|^# Copyright 201\d)
|
ignore-long-lines=(<?https?://|^# Copyright 201\d|link:)
|
||||||
expected-line-ending-format=LF
|
expected-line-ending-format=LF
|
||||||
|
|
||||||
[VARIABLES]
|
[VARIABLES]
|
||||||
|
27
.travis.yml
27
.travis.yml
@ -20,18 +20,21 @@ matrix:
|
|||||||
- os: linux
|
- os: linux
|
||||||
env: TESTENV=py36-pyqt59
|
env: TESTENV=py36-pyqt59
|
||||||
- os: linux
|
- os: linux
|
||||||
env: TESTENV=py36-pyqt510-cov
|
env: TESTENV=py36-pyqt510
|
||||||
# We need a newer Xvfb as a WORKAROUND for:
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-64928
|
|
||||||
sudo: required
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
packages:
|
||||||
- sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe"
|
- xfonts-base
|
||||||
packages:
|
- os: linux
|
||||||
- xvfb
|
env: TESTENV=py36-pyqt511-cov
|
||||||
|
# https://github.com/travis-ci/travis-ci/issues/9069
|
||||||
|
- os: linux
|
||||||
|
python: 3.7
|
||||||
|
sudo: required
|
||||||
|
dist: xenial
|
||||||
|
env: TESTENV=py37-pyqt511
|
||||||
- os: osx
|
- os: osx
|
||||||
env: TESTENV=py36 OSX=sierra
|
env: TESTENV=py37 OSX=sierra
|
||||||
osx_image: xcode9.2
|
osx_image: xcode9.2
|
||||||
language: generic
|
language: generic
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
||||||
@ -66,6 +69,10 @@ matrix:
|
|||||||
env: TESTENV=shellcheck
|
env: TESTENV=shellcheck
|
||||||
services: docker
|
services: docker
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
allow_failures:
|
||||||
|
# https://github.com/qutebrowser/qutebrowser/issues/4055
|
||||||
|
- os: linux
|
||||||
|
env: TESTENV=py36-pyqt510
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
@ -59,7 +59,7 @@ Getting help
|
|||||||
|
|
||||||
You can get help in the IRC channel
|
You can get help in the IRC channel
|
||||||
irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on
|
irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on
|
||||||
http://freenode.net/[Freenode]
|
https://freenode.net/[Freenode]
|
||||||
(https://webchat.freenode.net/?channels=#qutebrowser[webchat]), or by writing a
|
(https://webchat.freenode.net/?channels=#qutebrowser[webchat]), or by writing a
|
||||||
message to the
|
message to the
|
||||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||||
@ -96,24 +96,25 @@ Requirements
|
|||||||
|
|
||||||
The following software and libraries are required to run qutebrowser:
|
The following software and libraries are required to run qutebrowser:
|
||||||
|
|
||||||
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
|
* https://www.python.org/[Python] 3.5 or newer (3.6 recommended)
|
||||||
* http://qt.io/[Qt] 5.7.1 or newer (5.10 recommended) with the following modules:
|
* https://www.qt.io/[Qt] 5.7.1 or newer (5.11 recommended, support for < 5.9
|
||||||
|
will be dropped soon) with the following modules:
|
||||||
- QtCore / qtbase
|
- QtCore / qtbase
|
||||||
- QtQuick (part of qtbase in some distributions)
|
- QtQuick (part of qtbase in some distributions)
|
||||||
- QtSQL (part of qtbase in some distributions)
|
- QtSQL (part of qtbase in some distributions)
|
||||||
- QtOpenGL
|
- QtOpenGL
|
||||||
- QtWebEngine, or
|
- QtWebEngine, or
|
||||||
- QtWebKit - only the
|
- alternatively QtWebKit - support for QtWebKit will be dropped soon, and
|
||||||
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
|
only the link:https://github.com/annulen/webkit/wiki[updated fork] (5.212)
|
||||||
supported
|
is supported
|
||||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
||||||
(5.10 recommended) for Python 3
|
(5.11 recommended, support for < 5.9 will be dropped soon) for Python 3
|
||||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||||
* http://fdik.org/pyPEG/[pyPEG2]
|
* https://fdik.org/pyPEG/[pyPEG2]
|
||||||
* http://jinja.pocoo.org/[jinja2]
|
* http://jinja.pocoo.org/[jinja2]
|
||||||
* http://pygments.org/[pygments]
|
* http://pygments.org/[pygments]
|
||||||
* https://github.com/yaml/pyyaml[PyYAML]
|
* https://github.com/yaml/pyyaml[PyYAML]
|
||||||
* http://www.attrs.org/[attrs]
|
* https://www.attrs.org/[attrs]
|
||||||
|
|
||||||
The following libraries are optional:
|
The following libraries are optional:
|
||||||
|
|
||||||
@ -143,6 +144,18 @@ get in touch!
|
|||||||
* PayPal: me@the-compiler.org
|
* PayPal: me@the-compiler.org
|
||||||
* Bitcoin: link:bitcoin:1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE[1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE]
|
* Bitcoin: link:bitcoin:1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE[1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE]
|
||||||
|
|
||||||
|
Sponsors
|
||||||
|
--------
|
||||||
|
|
||||||
|
Thanks a lot to https://www.macstadium.com/[MacStadium] for supporting
|
||||||
|
qutebrowser with a free hosted Mac Mini via their
|
||||||
|
https://www.macstadium.com/opensource[Open Source Project].
|
||||||
|
|
||||||
|
(They don't require including this here - I've just been very happy with their
|
||||||
|
offer, and without them, no macOS releases or tests would exist)
|
||||||
|
|
||||||
|
image:.github/img/macstadium.png["powered by MacStadium",width=200,link="https://www.macstadium.com/"]
|
||||||
|
|
||||||
Authors
|
Authors
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@ -152,7 +165,7 @@ https://github.com/qutebrowser/qutebrowser/graphs/contributors[hundreds of contr
|
|||||||
|
|
||||||
Additionally, the following people have contributed graphics:
|
Additionally, the following people have contributed graphics:
|
||||||
|
|
||||||
* Jad/link:http://yelostudio.com[yelo] (new icon)
|
* Jad/link:https://yelostudio.com[yelo] (new icon)
|
||||||
* WOFall (original icon)
|
* WOFall (original icon)
|
||||||
* regines (key binding cheatsheet)
|
* regines (key binding cheatsheet)
|
||||||
|
|
||||||
@ -170,18 +183,16 @@ Active
|
|||||||
|
|
||||||
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
|
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
|
||||||
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
|
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
|
||||||
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
|
* https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
|
||||||
* http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
|
* https://github.com/next-browser/next/[next] (Lisp, Emacs-like, GTK+ with WebKit)
|
||||||
|
* https://github.com/parkouss/webmacs/[webmacs] (Python, Emacs-like with QtWebEngine)
|
||||||
* Chrome/Chromium addons:
|
* Chrome/Chromium addons:
|
||||||
https://github.com/1995eaton/chromium-vim[cVim],
|
https://vimium.github.io/[Vimium],
|
||||||
http://vimium.github.io/[Vimium],
|
|
||||||
https://github.com/brookhong/Surfingkeys[Surfingkeys],
|
https://github.com/brookhong/Surfingkeys[Surfingkeys],
|
||||||
https://key.saka.io/[Saka Key]
|
|
||||||
* Firefox addons (based on WebExtensions):
|
* Firefox addons (based on WebExtensions):
|
||||||
https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental),
|
https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental),
|
||||||
https://key.saka.io[Saka Key],
|
|
||||||
https://github.com/ueokande/vim-vixen[Vim Vixen],
|
https://github.com/ueokande/vim-vixen[Vim Vixen],
|
||||||
https://github.com/shinglyu/QuantumVim[QuantumVim],
|
https://github.com/amedama41/vvimpulation[VVimpulation],
|
||||||
https://github.com/cmcaine/tridactyl[Tridactyl] (working
|
https://github.com/cmcaine/tridactyl[Tridactyl] (working
|
||||||
on a https://bugzilla.mozilla.org/show_bug.cgi?id=1215061[better API] for
|
on a https://bugzilla.mozilla.org/show_bug.cgi?id=1215061[better API] for
|
||||||
keyboard integration in Firefox).
|
keyboard integration in Firefox).
|
||||||
@ -192,17 +203,23 @@ Inactive
|
|||||||
* https://bitbucket.org/portix/dwb[dwb] (C, GTK+ with WebKit1,
|
* https://bitbucket.org/portix/dwb[dwb] (C, GTK+ with WebKit1,
|
||||||
https://bitbucket.org/portix/dwb/pull-requests/22/several-cleanups-to-increase-portability/diff[unmaintained] -
|
https://bitbucket.org/portix/dwb/pull-requests/22/several-cleanups-to-increase-portability/diff[unmaintained] -
|
||||||
main inspiration for qutebrowser)
|
main inspiration for qutebrowser)
|
||||||
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
|
* https://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
|
||||||
WebKit1)
|
WebKit1)
|
||||||
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1)
|
* https://wiki.archlinux.org/index.php?title=Jumanji[jumanji] (C, GTK+ with WebKit1,
|
||||||
|
original site is gone but Arch Linux has some data)
|
||||||
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko)
|
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko)
|
||||||
|
* https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
|
||||||
* Firefox addons (not based on WebExtensions or no recent activity):
|
* Firefox addons (not based on WebExtensions or no recent activity):
|
||||||
http://www.vimperator.org/[Vimperator],
|
http://www.vimperator.org/[Vimperator],
|
||||||
http://5digits.org/pentadactyl/[Pentadactyl],
|
http://bug.5digits.org/pentadactyl/index[Pentadactyl],
|
||||||
https://github.com/akhodakivskiy/VimFx[VimFx],
|
https://github.com/akhodakivskiy/VimFx[VimFx],
|
||||||
|
https://key.saka.io[Saka Key],
|
||||||
|
https://github.com/shinglyu/QuantumVim[QuantumVim],
|
||||||
* Chrome/Chromium addons:
|
* Chrome/Chromium addons:
|
||||||
https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome],
|
https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome],
|
||||||
https://github.com/jinzhu/vrome[Vrome]
|
https://github.com/jinzhu/vrome[Vrome]
|
||||||
|
https://key.saka.io[Saka Key],
|
||||||
|
https://github.com/1995eaton/chromium-vim[cVim],
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
@ -229,4 +246,4 @@ display PDF files in the browser. Windows releases come with a bundled pdf.js.
|
|||||||
pdf.js is distributed under the terms of the Apache License. You can
|
pdf.js is distributed under the terms of the Apache License. You can
|
||||||
find a copy of the license in `qutebrowser/3rdparty/pdfjs/LICENSE` (in the
|
find a copy of the license in `qutebrowser/3rdparty/pdfjs/LICENSE` (in the
|
||||||
Windows release or after running `scripts/dev/update_3rdparty.py`), or online
|
Windows release or after running `scripts/dev/update_3rdparty.py`), or online
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.html[here].
|
https://www.apache.org/licenses/LICENSE-2.0.html[here].
|
||||||
|
@ -15,12 +15,219 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
|||||||
// `Fixed` for any bug fixes.
|
// `Fixed` for any bug fixes.
|
||||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||||
|
|
||||||
v1.4.0 (unreleased)
|
v1.6.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Added
|
Added
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
- New `tabs.new_position.stacking` setting which controls whether new tabs
|
||||||
|
opened from a page should stack on each other or not.
|
||||||
|
- New `completion.open_categories` setting which allows to configure which
|
||||||
|
categories are shown in the `:open` completion, and how they are ordered.
|
||||||
|
- New config manipulation commands:
|
||||||
|
* `:config-dict-add` and `:config-list-add` to a new element to a dict/list
|
||||||
|
setting.
|
||||||
|
* `:config-dict-remove` and `:config-list-remove` to remove an element from a
|
||||||
|
dict/list setting.
|
||||||
|
- New `hints.selectors` setting which allows to configure what CSS selectors
|
||||||
|
are used for hints, and also allows adding custom hint groups.
|
||||||
|
|
||||||
|
Changed
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
- `:q` now closes current window instead of quitting qutebrowser completely
|
||||||
|
(`:close`), while `:qa` quits (`:quit`). The behavior of `:wq` remains
|
||||||
|
unchanged (`:quit --save`), as closing a window while saving the session
|
||||||
|
doesn't make sense.
|
||||||
|
- Completion highlighting is now done differently (using QSyntaxHighlither),
|
||||||
|
which should fix some highlighting corner-cases.
|
||||||
|
- The `QtColor` config type now also understands colors like `rgb(...)`.
|
||||||
|
- `:yank` now has a `--quiet` option which causes it to not display a message.
|
||||||
|
- The `:open` completion now also shows search engines by default.
|
||||||
|
- The `content.host_blocking.enabled` setting now supports URL patterns, so the
|
||||||
|
adblocker can be disabled on a given page.
|
||||||
|
- Elements with a `tabindex` attribute now also get hints by default.
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Invalid world IDs now get rejected for `:jseval` and GreaseMonkey scripts.
|
||||||
|
- When websites suggest download filenames with invalid characters, those are
|
||||||
|
now correctly replaced.
|
||||||
|
|
||||||
|
v1.5.1
|
||||||
|
------
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Flickering when opening/closing tabs (as soon as more than 10 are open) on
|
||||||
|
some pages.
|
||||||
|
- PDF.js is now bundled again with the macOS/Windows release.
|
||||||
|
- PDF.js is now searched in the correct path (if not installed system-wide)
|
||||||
|
instead of hardcoding `~/.local/share/qutebrowser`.
|
||||||
|
- Improved logging for PDF.js resources which fail to load.
|
||||||
|
- Crash when closing a tab after doing a search.
|
||||||
|
- Tabs appearing when hidden after e.g. closing tabs.
|
||||||
|
|
||||||
|
v1.5.0
|
||||||
|
------
|
||||||
|
|
||||||
|
Added
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Rewritten PDF.js support:
|
||||||
|
* PDF.js support and the `content.pdfjs` setting are now also available with
|
||||||
|
QtWebEngine.
|
||||||
|
* Opening a PDF file now doesn't start a second request anymore.
|
||||||
|
* Opening PDFs on https:// sites now works properly.
|
||||||
|
* New `--pdfjs` flag for `prompt-open-download`, so PDFs can be opened in
|
||||||
|
PDF.js with `<Ctrl-P>` in the download prompt.
|
||||||
|
- New settings:
|
||||||
|
* `content.mouse_lock` to handle HTML5 pointer locking.
|
||||||
|
* `completion.web_history.exclude` which hides a list of URL patterns from
|
||||||
|
the completion.
|
||||||
|
* `qt.process_model` which can be used to change Chromium's process model.
|
||||||
|
* `qt.low_end_device_mode` which turns on Chromium's low-end device mode.
|
||||||
|
This mode uses less RAM, but the expense of performance.
|
||||||
|
* `content.webrtc_ip_handling_policy`, which allows more
|
||||||
|
fine-grained/restrictive control about which IPs are exposed via WebRTC.
|
||||||
|
* `tabs.max_width` which allows to have a more "normal" look for tabs.
|
||||||
|
* `content.mute` which allows to mute pages (or all tabs) by default.
|
||||||
|
- Running qutebrowser with QtWebKit or Qt < 5.9 now shows a warning (only
|
||||||
|
once), as support for those is going to be removed in a future release.
|
||||||
|
- New t[iI][hHu] default bindings (similar to `tsh` etc.) to toggle images.
|
||||||
|
- The qute-pass userscript now has optional OTP support.
|
||||||
|
- When `:spawn --userscript` is called with a count, that count is now
|
||||||
|
passed to userscripts as `$QUTE_COUNT`.
|
||||||
|
|
||||||
|
Changed
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
- Windows and macOS releases now bundle Python 3.7, PyQt 5.11.3 and Qt 5.11.2.
|
||||||
|
QtWebEngine includes security fixes up to Chromium 68.0.3440.75 and
|
||||||
|
http://code.qt.io/cgit/qt/qtwebengine.git/tree/dist/changes-5.11.2/?h=v5.11.2[various other fixes].
|
||||||
|
- Various performance improvements when many tabs are opened.
|
||||||
|
- The `content.headers.referer` setting now works on QtWebEngine.
|
||||||
|
- The `:repeat` command now takes a count which is multiplied with the given
|
||||||
|
"times" argument.
|
||||||
|
- The default keybinding to leave passthrough mode was changed from `<Ctrl-V>`
|
||||||
|
to `<Shift-Escape>`, which makes pasting from the clipboard easier in
|
||||||
|
passthrough mode and is also unlikely to conflict with webpage bindings.
|
||||||
|
- The `app_id` is now set to `qutebrowser` for Wayland.
|
||||||
|
- `Command` or `Cmd` can now be used (instead of `Meta`) to map the Command key
|
||||||
|
on macOS.
|
||||||
|
- Using `:set option` now shows the value of the setting (like `:set option?`
|
||||||
|
already did).
|
||||||
|
- The `completion.web_history_max_items` setting got renamed to
|
||||||
|
`completion.web_history.max_items`.
|
||||||
|
- The Makefile shipped with qutebrowser now supports overriding variables
|
||||||
|
`DATADIR` and `MANDIR`.
|
||||||
|
- Regenerating completion history now shows a progress dialog.
|
||||||
|
- The `content.autoplay` setting now supports URL patterns on Qt >= 5.11.
|
||||||
|
- The `content.host_blocking.whitelist` setting now takes a list of URL
|
||||||
|
patterns instead of globs.
|
||||||
|
- In passthrough mode, Ctrl + Mousewheel now also gets passed through to the
|
||||||
|
page instead of zooming.
|
||||||
|
- Editing text in an external editor now simulates a JS "input" event, which
|
||||||
|
improves compatibility with websites reacting via JS to input.
|
||||||
|
- The `qute://settings` page is now properly sorted on Python 3.5.
|
||||||
|
- `:zoom`, `:zoom-in` and `:zoom-out` now have a `--quiet` switch which causes
|
||||||
|
them to not display a message.
|
||||||
|
- The `scrolling.bar` setting now takes three values instead of being a
|
||||||
|
boolean: `always`, `never`, and `when-searching` (which only displays it
|
||||||
|
while a search is active).
|
||||||
|
- '@@' now repeats the last run macro.
|
||||||
|
- The `content.host_blocking.lists` setting now accepts a `file://` URL to a
|
||||||
|
directory, and reads all files in that directory.
|
||||||
|
- The `:tab-give` and `:tab-take` command now have a new flag `--keep` which
|
||||||
|
causes them to keep the old tab around.
|
||||||
|
- `:navigate` now clears the URL query.
|
||||||
|
|
||||||
|
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.
|
||||||
|
- 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.
|
||||||
|
- Crash when trying to load an empty session file.
|
||||||
|
- `:hint` with an invalid `--mode=` value now shows a proper error.
|
||||||
|
- Rare crash on Qt 5.11.2 when clicking on `<select>` elements.
|
||||||
|
- Rare crash related to the completion.
|
||||||
|
|
||||||
|
Removed
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
- Support for importing pre-v1.0.0 history files has been removed.
|
||||||
|
- The `content.webrtc_public_interfaces_only` setting has been removed and
|
||||||
|
replaced by `content.webrtc_ip_handling_policy`.
|
||||||
|
|
||||||
|
v1.4.2
|
||||||
|
------
|
||||||
|
|
||||||
|
Changed
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
- The `content.xss_auditing` setting is now enabled by default, to mirror
|
||||||
|
Chromium's rather than Qt's default behavior.
|
||||||
|
- Long URLs in the statusbar are now elided at the end rather than in the
|
||||||
|
middle, to make sure the hostname is completely visible whenever possible.
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Crash in Qt 5.7.1 when a website uses `window.print()`.
|
||||||
|
- The workaround for Nouveau graphic drivers now works properly again.
|
||||||
|
- Crash when using `:follow-selected` with a link which is outside of the view.
|
||||||
|
- Workaround for windows not showing as urgent with some window managers
|
||||||
|
(like i3).
|
||||||
|
- Crash when opening URLs with some unicode characters (IDNA 2008). Those URLs
|
||||||
|
still won't open though, due to missing support in Qt.
|
||||||
|
- Crash when a download directory which can't be created is configured.
|
||||||
|
- Crash in the `importer.py` script when importing Chrome bookmarks from newer Chrome versions.
|
||||||
|
- The `content.webrtc_public_interfaces_only` option didn't work on Qt 5.11 previously (it now does).
|
||||||
|
Note it still does not work on Qt 5.10 (due to a Qt bug) and Qt < 5.9.2.
|
||||||
|
- Repeated escaping of entries in `qute://log` when refreshing page.
|
||||||
|
- The host blocker doesn't block 0.0.0.0 anymore.
|
||||||
|
- Crash when using :// as URL pattern.
|
||||||
|
- The `:buffer` completion now sorts tabs with indices >= 10 correctly again.
|
||||||
|
|
||||||
|
v1.4.1
|
||||||
|
------
|
||||||
|
|
||||||
|
Security
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
- CVE-2018-10895: Fix CSRF issue on the qute://settings page, leading to
|
||||||
|
possible arbitrary code execution. See the related GitHub issue for details:
|
||||||
|
https://github.com/qutebrowser/qutebrowser/issues/4060
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Rare crash when an error occurs in downloads.
|
||||||
|
- Newlines are now stripped from the :version pastebin URL.
|
||||||
|
- There's a new `mkvenv-pypi-old` environment in `tox.ini` which installs an
|
||||||
|
older Qt, which is needed on Ubuntu 16.04.
|
||||||
|
- Worked around a Qt issue which redirects to a `chrome-error://` page when
|
||||||
|
trying to use U2F.
|
||||||
|
- The `link_pyqt.py` script now works correctly with PyQt 5.11.
|
||||||
|
- The Windows installer now uninstalls the old version before installing the
|
||||||
|
new one, fixing issues with qutebrowser not starting after installing v1.4.0
|
||||||
|
over v1.3.3.
|
||||||
|
|
||||||
|
v1.4.0
|
||||||
|
------
|
||||||
|
|
||||||
|
Added
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Support for the bundled `sip` module in PyQt 5.11 and other changes in
|
||||||
|
Qt/PyQt 5.11.x.
|
||||||
- New `--debug-flag log-requests` to log requests to the debug log for
|
- New `--debug-flag log-requests` to log requests to the debug log for
|
||||||
debugging.
|
debugging.
|
||||||
- New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically
|
- New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically
|
||||||
@ -40,11 +247,14 @@ Added
|
|||||||
* Support for requesting persistent storage via
|
* Support for requesting persistent storage via
|
||||||
`navigator.webkitPersistentStorage.requestQuota` with a new
|
`navigator.webkitPersistentStorage.requestQuota` with a new
|
||||||
`content.persistent_storage` setting (requires Qt 5.11).
|
`content.persistent_storage` setting (requires Qt 5.11).
|
||||||
|
This setting also supports URL patterns.
|
||||||
* Support for registering custom protocol handlers via
|
* Support for registering custom protocol handlers via
|
||||||
`navigator.registerProtocolHandler` with a new
|
`navigator.registerProtocolHandler` with a new
|
||||||
`content.register_protocol_handler` setting (requires Qt 5.11).
|
`content.register_protocol_handler` setting (requires Qt 5.11).
|
||||||
|
This setting also supports URL patterns.
|
||||||
* Support for WebRTC screen sharing with a new `content.desktop_capture`
|
* Support for WebRTC screen sharing with a new `content.desktop_capture`
|
||||||
setting (requires Qt 5.10).
|
setting (requires Qt 5.10).
|
||||||
|
This setting also supports URL patterns.
|
||||||
* New `content.autoplay` setting to enable/disable automatic video playback
|
* New `content.autoplay` setting to enable/disable automatic video playback
|
||||||
(requires Qt 5.10).
|
(requires Qt 5.10).
|
||||||
* New `content.webrtc_public_interfaces_only` setting to only expose public
|
* New `content.webrtc_public_interfaces_only` setting to only expose public
|
||||||
@ -55,8 +265,17 @@ Added
|
|||||||
Changed
|
Changed
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
|
- The following settings now support URL patterns:
|
||||||
|
* `content.headers.do_not_track`
|
||||||
|
* `content.headers.custom`
|
||||||
|
* `content.headers.accept_language`
|
||||||
|
* `content.headers.user_agent`
|
||||||
|
* `content.ssl_strict`
|
||||||
|
* `content.geolocation`
|
||||||
|
* `content.notifications`
|
||||||
|
* `content.media_capture`
|
||||||
- The Windows/macOS releases now bundle Qt 5.11.1 which is based on
|
- The Windows/macOS releases now bundle Qt 5.11.1 which is based on
|
||||||
Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.79.
|
Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87.
|
||||||
- New short flags for commandline arguments: `-B` and `-T` for `--basedir` and
|
- New short flags for commandline arguments: `-B` and `-T` for `--basedir` and
|
||||||
`--temp-basedir`; `-d` and `-D` for `--debug` and `--debug-flag`.
|
`--temp-basedir`; `-d` and `-D` for `--debug` and `--debug-flag`.
|
||||||
- Deleting history items via `:history-clear` or `:completion-item-del` now
|
- Deleting history items via `:history-clear` or `:completion-item-del` now
|
||||||
@ -85,21 +304,60 @@ Changed
|
|||||||
- Improved error messages when a setting needs a newer Qt version.
|
- Improved error messages when a setting needs a newer Qt version.
|
||||||
- QtWebEngine: Various improvements to make the cursor more visible in caret
|
- QtWebEngine: Various improvements to make the cursor more visible in caret
|
||||||
browsing.
|
browsing.
|
||||||
|
- When a prompt is opened in insert/passthrough mode, the mode is restored
|
||||||
|
after closing the prompt.
|
||||||
|
- On Qt 5.10 or newer, dictionaries are now read from the qutebrowser data
|
||||||
|
directory (e.g. `~/.local/share/qutebrowser`) instead of `/usr/share/qt`.
|
||||||
|
Existing dictionaries are copied over.
|
||||||
|
- If an error while parsing `~/.netrc` occurs, the cause of the error is now
|
||||||
|
logged.
|
||||||
|
- On Qt 5.9 or newer, certificate errors now show Chromium's detailed error
|
||||||
|
page.
|
||||||
|
- Greasemonkey scripts now support a "@qute-js-world" tag to run them in a
|
||||||
|
different JavaScript context.
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Various subtle keyboard focus issues.
|
||||||
|
- The security fix in v1.3.3 caused URLs with ampersands
|
||||||
|
(`www.example.com?one=1&two=2`) to send the wrong arguments when clicked on
|
||||||
|
the `qute://history` page.
|
||||||
|
- Crash when opening a PDF page with PDF.js enabled (on QtWebKit), but no
|
||||||
|
PDF.js installed.
|
||||||
|
- Crash when closing a tab shortly after opening it.
|
||||||
|
|
||||||
Removed
|
Removed
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
|
- No prebuilt binaries for 32-bit Windows are supplied anymore. This is due to
|
||||||
|
Qt removing QtWebEngine support for those upstream. It might be possible to
|
||||||
|
distribute 32-bit binaries again with Qt 5.12 in December, but that will only
|
||||||
|
happen if it turns out enough people actually need 32-bit support.
|
||||||
- `:tab-detach` which has been deprecated in v1.1.0 has been removed.
|
- `:tab-detach` which has been deprecated in v1.1.0 has been removed.
|
||||||
- The `content.developer_extras` setting got removed. On QtWebKit, developer
|
- The `content.developer_extras` setting got removed. On QtWebKit, developer
|
||||||
extras are now automatically enabled when opening the inspector.
|
extras are now automatically enabled when opening the inspector.
|
||||||
|
|
||||||
v1.3.3 (unreleased)
|
v1.3.3
|
||||||
-------------------
|
------
|
||||||
|
|
||||||
|
Security
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
- An XSS vulnerability on the `qute://history` page allowed websites to inject
|
||||||
|
HTML into the page via a crafted title tag. This could allow them to steal
|
||||||
|
your browsing history. If you're currently unable to upgrade, avoid using
|
||||||
|
`:history`. A CVE request for this issue is pending, see
|
||||||
|
https://github.com/qutebrowser/qutebrowser/issues/4011[#4011] for updates.
|
||||||
|
|
||||||
Fixed
|
Fixed
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
- Crash in a workaround for a Qt 5.11 bug in rare circumstances.
|
- Crash in a workaround for a Qt 5.11 bug in rare circumstances.
|
||||||
|
- Workaround for a Qt bug which preserves searches between page loads.
|
||||||
|
- In v1.3.2 a dependency on the `PyQt5.QtQuickWidgets` module was accidentally
|
||||||
|
introduced. Since that module isn't packaged everywhere, it's been removed
|
||||||
|
again.
|
||||||
|
|
||||||
v1.3.2
|
v1.3.2
|
||||||
------
|
------
|
||||||
@ -1479,7 +1737,7 @@ Changed
|
|||||||
`tabs.bg/fg.selected.odd/even`.
|
`tabs.bg/fg.selected.odd/even`.
|
||||||
- `:spawn --userscript` and `:hint` with the `userscript` target now look up
|
- `:spawn --userscript` and `:hint` with the `userscript` target now look up
|
||||||
relative paths in `~/.local/share/qutebrowser/userscripts` or
|
relative paths in `~/.local/share/qutebrowser/userscripts` or
|
||||||
`$XDG_DATA_DIR`. Using a binary in `$PATH` won't work anymore with
|
`$XDG_DATA_HOME`. Using a binary in `$PATH` won't work anymore with
|
||||||
`--userscript`.
|
`--userscript`.
|
||||||
- New design for error pages
|
- New design for error pages
|
||||||
- Link filtering for hints now checks if the text is contained anywhere in
|
- Link filtering for hints now checks if the text is contained anywhere in
|
||||||
|
@ -5,6 +5,14 @@ The Compiler <mail@qutebrowser.org>
|
|||||||
:data-uri:
|
:data-uri:
|
||||||
:toc:
|
:toc:
|
||||||
|
|
||||||
|
IMPORTANT: *Currently, bigger changes are going on in qutebrowser, as
|
||||||
|
part of a
|
||||||
|
https://lists.schokokeks.org/pipermail/qutebrowser-announce/2018-September/000051.html[student research project]
|
||||||
|
about adding a plugin API to qutebrowser and moving a lot of code from the code
|
||||||
|
into plugins.* Due to that, bandwidth for pull request review is currently
|
||||||
|
very limited, and contributions might lead to merge conflicts due to
|
||||||
|
ongoing refactorings.
|
||||||
|
|
||||||
I `<3` footnote:[Of course, that says `<3` in HTML.] contributors!
|
I `<3` footnote:[Of course, that says `<3` in HTML.] contributors!
|
||||||
|
|
||||||
This document contains guidelines for contributing to qutebrowser, as well as
|
This document contains guidelines for contributing to qutebrowser, as well as
|
||||||
@ -88,7 +96,7 @@ git format-patch origin/master <1>
|
|||||||
Running qutebrowser
|
Running qutebrowser
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
After link:install.asciidoc#tox[installing qutebrowser via tox], you can run
|
After link:install{outfilesuffix}#tox[installing qutebrowser via tox], you can run
|
||||||
`.venv/bin/qutebrowser --debug --temp-basedir` to test your changes with debug
|
`.venv/bin/qutebrowser --debug --temp-basedir` to test your changes with debug
|
||||||
logging enabled and without affecting existing running instances.
|
logging enabled and without affecting existing running instances.
|
||||||
|
|
||||||
@ -689,8 +697,6 @@ New PyQt release
|
|||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* See above.
|
* See above.
|
||||||
* Install new PyQt in Windows VM (32- and 64-bit).
|
|
||||||
* Download new installer and update PyQt installer path in `ci_install.py`.
|
|
||||||
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions.
|
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions.
|
||||||
|
|
||||||
qutebrowser release
|
qutebrowser release
|
||||||
@ -712,7 +718,7 @@ qutebrowser release
|
|||||||
as closed.
|
as closed.
|
||||||
|
|
||||||
* Linux: Run `git checkout v1.$x.$y && ./.venv/bin/python3 scripts/dev/build_release.py --upload v1.$x.$y`.
|
* Linux: Run `git checkout v1.$x.$y && ./.venv/bin/python3 scripts/dev/build_release.py --upload v1.$x.$y`.
|
||||||
* Windows: Run `git checkout v1.X.Y; C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
* Windows: Run `git checkout v1.X.Y; py -3 scripts\dev\build_release.py --asciidoc C:\Python27\python %userprofile%\bin\asciidoc-8.6.10\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||||
* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||||
* On server:
|
* On server:
|
||||||
- Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
|
- Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
|
||||||
|
@ -5,10 +5,10 @@ The Compiler <mail@qutebrowser.org>
|
|||||||
|
|
||||||
[qanda]
|
[qanda]
|
||||||
What is qutebrowser based on?::
|
What is qutebrowser based on?::
|
||||||
qutebrowser uses http://www.python.org/[Python], http://qt.io/[Qt] and
|
qutebrowser uses https://www.python.org/[Python], https://www.qt.io/[Qt] and
|
||||||
http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
https://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||||
+
|
+
|
||||||
The concept of it is largely inspired by http://portix.bitbucket.org/dwb/[dwb]
|
The concept of it is largely inspired by https://bitbucket.org/portix/dwb/[dwb]
|
||||||
and http://www.vimperator.org/vimperator[Vimperator]. Many actions and
|
and http://www.vimperator.org/vimperator[Vimperator]. Many actions and
|
||||||
key bindings are similar to dwb.
|
key bindings are similar to dwb.
|
||||||
|
|
||||||
@ -16,34 +16,34 @@ Why another browser?::
|
|||||||
It might be hard to believe, but I didn't find any browser which I was
|
It might be hard to believe, but I didn't find any browser which I was
|
||||||
happy with, so I started to write my own. Also, I needed a project to get
|
happy with, so I started to write my own. Also, I needed a project to get
|
||||||
into writing GUI applications with Python and
|
into writing GUI applications with Python and
|
||||||
link:http://qt.io/[Qt]/link:http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
link:https://www.qt.io/[Qt]/link:https://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||||
+
|
+
|
||||||
Read the next few questions to find out why I was unhappy with existing
|
Read the next few questions to find out why I was unhappy with existing
|
||||||
software.
|
software.
|
||||||
|
|
||||||
What's wrong with link:http://portix.bitbucket.org/dwb/[dwb]/link:http://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://mason-larobina.github.io/luakit/[luakit]/link:http://pwmt.org/projects/jumanji/[jumanji]/... (projects based on WebKitGTK)?::
|
What's wrong with link:https://bitbucket.org/portix/dwb/[dwb]/link:https://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://mason-larobina.github.io/luakit/[luakit]/jumanji/... (projects based on WebKitGTK)?::
|
||||||
Most of them are based on the http://webkitgtk.org/[WebKitGTK+]
|
Most of them are based on the https://webkitgtk.org/[WebKitGTK+]
|
||||||
http://webkitgtk.org/reference/webkitgtk/stable/index.html[WebKit1] API,
|
https://webkitgtk.org/reference/webkitgtk/stable/index.html[WebKit1] API,
|
||||||
which causes a lot of crashes. As the GTK API using WebKit1 is
|
which causes a lot of crashes. As the GTK API using WebKit1 is
|
||||||
https://lists.webkit.org/pipermail/webkit-gtk/2014-March/001821.html[deprecated],
|
https://lists.webkit.org/pipermail/webkit-gtk/2014-March/001821.html[deprecated],
|
||||||
these bugs are never going to be fixed.
|
these bugs are never going to be fixed.
|
||||||
+
|
+
|
||||||
When qutebrowser was created, the newer
|
When qutebrowser was created, the newer
|
||||||
http://webkitgtk.org/reference/webkit2gtk/stable/index.html[WebKit2 API] lacked
|
https://webkitgtk.org/reference/webkit2gtk/stable/index.html[WebKit2 API] lacked
|
||||||
basic features like proxy support, and almost no projects have started porting
|
basic features like proxy support, and almost no projects have started porting
|
||||||
to WebKit2. In the meantime, this situation has improved a bit, but there are
|
to WebKit2. In the meantime, this situation has improved a bit, but there are
|
||||||
still only a few projects which have some kind of WebKit2 support (see the
|
still only a few projects which have some kind of WebKit2 support (see the
|
||||||
https://github.com/qutebrowser/qutebrowser#similar-projects[list of
|
https://github.com/qutebrowser/qutebrowser#similar-projects[list of
|
||||||
alternatives]).
|
alternatives]).
|
||||||
+
|
+
|
||||||
qutebrowser uses http://qt.io/[Qt] and
|
qutebrowser uses https://www.qt.io/[Qt] and
|
||||||
http://wiki.qt.io/QtWebEngine[QtWebEngine] by default (and supports
|
https://wiki.qt.io/QtWebEngine[QtWebEngine] by default (and supports
|
||||||
http://wiki.qt.io/QtWebKit[QtWebKit] optionally). QtWebEngine is based on
|
https://wiki.qt.io/QtWebKit[QtWebKit] optionally). QtWebEngine is based on
|
||||||
Google's https://www.chromium.org/Home[Chromium]. With an up-to-date Qt, it has
|
Google's https://www.chromium.org/Home[Chromium]. With an up-to-date Qt, it has
|
||||||
much more man-power behind it than WebKitGTK+ has, and thus supports more modern
|
much more man-power behind it than WebKitGTK+ has, and thus supports more modern
|
||||||
web features - it's also arguably more secure.
|
web features - it's also arguably more secure.
|
||||||
|
|
||||||
What's wrong with https://www.mozilla.org/en-US/firefox/new/[Firefox] and link:http://5digits.org/pentadactyl/[Pentadactyl]/link:http://www.vimperator.org/vimperator[Vimperator]?::
|
What's wrong with https://www.mozilla.org/en-US/firefox/new/[Firefox] and link:http://bug.5digits.org/pentadactyl/[Pentadactyl]/link:http://www.vimperator.org/vimperator[Vimperator]?::
|
||||||
Firefox likes to break compatibility with addons on each upgrade, gets
|
Firefox likes to break compatibility with addons on each upgrade, gets
|
||||||
slower and more bloated with every upgrade, and has some
|
slower and more bloated with every upgrade, and has some
|
||||||
https://blog.mozilla.org/advancingcontent/2014/02/11/publisher-transformation-with-users-at-the-center/[horrible
|
https://blog.mozilla.org/advancingcontent/2014/02/11/publisher-transformation-with-users-at-the-center/[horrible
|
||||||
@ -51,20 +51,20 @@ What's wrong with https://www.mozilla.org/en-US/firefox/new/[Firefox] and link:h
|
|||||||
+
|
+
|
||||||
Also, developing addons for it is a nightmare.
|
Also, developing addons for it is a nightmare.
|
||||||
|
|
||||||
What's wrong with http://www.chromium.org/Home[Chromium] and https://vimium.github.io/[Vimium]?::
|
What's wrong with https://www.chromium.org/Home[Chromium] and https://vimium.github.io/[Vimium]?::
|
||||||
The Chrome plugin API doesn't seem to allow much freedom for plugin
|
The Chrome plugin API doesn't seem to allow much freedom for plugin
|
||||||
writers, which results in Vimium not really having all the features you'd
|
writers, which results in Vimium not really having all the features you'd
|
||||||
expect from a proper minimal, vim-like browser.
|
expect from a proper minimal, vim-like browser.
|
||||||
|
|
||||||
Why Python?::
|
Why Python?::
|
||||||
I enjoy writing Python since 2011, which made it one of the possible
|
I enjoy writing Python since 2011, which made it one of the possible
|
||||||
choices. I wanted to use http://qt.io/[Qt] because of
|
choices. I wanted to use https://www.qt.io/[Qt] because of
|
||||||
http://wiki.qt.io/QtWebKit[QtWebKit] so I didn't have
|
https://wiki.qt.io/QtWebKit[QtWebKit] so I didn't have
|
||||||
http://wiki.qt.io/Category:LanguageBindings[many other choices]. I don't
|
https://wiki.qt.io/Category:LanguageBindings[many other choices]. I don't
|
||||||
like C++ and can't write it very well, so that wasn't an alternative.
|
like C++ and can't write it very well, so that wasn't an alternative.
|
||||||
|
|
||||||
But isn't Python too slow for a browser?::
|
But isn't Python too slow for a browser?::
|
||||||
http://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[No.]
|
https://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[No.]
|
||||||
I believe efficiency while coding is a lot more important than efficiency
|
I believe efficiency while coding is a lot more important than efficiency
|
||||||
while running. Also, most of the heavy lifting of qutebrowser is done by Qt
|
while running. Also, most of the heavy lifting of qutebrowser is done by Qt
|
||||||
and WebKit in C++, with the
|
and WebKit in C++, with the
|
||||||
@ -74,7 +74,7 @@ Is qutebrowser secure?::
|
|||||||
Most security issues are in the backend (which handles networking,
|
Most security issues are in the backend (which handles networking,
|
||||||
rendering, JavaScript, etc.) and not qutebrowser itself.
|
rendering, JavaScript, etc.) and not qutebrowser itself.
|
||||||
+
|
+
|
||||||
qutebrowser uses http://wiki.qt.io/QtWebEngine[QtWebEngine] by default.
|
qutebrowser uses https://wiki.qt.io/QtWebEngine[QtWebEngine] by default.
|
||||||
QtWebEngine is based on Google's https://www.chromium.org/Home[Chromium]. While
|
QtWebEngine is based on Google's https://www.chromium.org/Home[Chromium]. While
|
||||||
Qt only updates to a new Chromium release on every minor Qt release (all ~6
|
Qt only updates to a new Chromium release on every minor Qt release (all ~6
|
||||||
months), every patch release backports security fixes from newer Chromium
|
months), every patch release backports security fixes from newer Chromium
|
||||||
@ -84,26 +84,41 @@ do anything. Chromium's process isolation and
|
|||||||
https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md[sandboxing]
|
https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md[sandboxing]
|
||||||
features are also enabled as a second line of defense.
|
features are also enabled as a second line of defense.
|
||||||
+
|
+
|
||||||
http://wiki.qt.io/QtWebKit[QtWebKit] is also supported as an alternative
|
https://wiki.qt.io/QtWebKit[QtWebKit] is also supported as an alternative
|
||||||
backend, but hasn't seen new releases
|
backend, but hasn't seen new releases
|
||||||
https://github.com/annulen/webkit/releases[in a while]. It also doesn't have any
|
https://github.com/annulen/webkit/releases[in a while]. It also doesn't have any
|
||||||
process isolation or sandboxing.
|
process isolation or sandboxing. See
|
||||||
|
https://github.com/qutebrowser/qutebrowser/issues/4039[#4039] for more details.
|
||||||
+
|
+
|
||||||
Security issues in qutebrowser's code happen very rarely (as per March 2018,
|
Security issues in qutebrowser's code happen very rarely (as per July 2018,
|
||||||
there has been one security issue caused by qutebrowser in over four years) and
|
there have been three security issues caused by qutebrowser in over 4.5 years).
|
||||||
are fixed timely. To report security bugs, please contact me directly at
|
Those were handled appropriately
|
||||||
mail@qutebrowser.org, GPG ID
|
(http://seclists.org/oss-sec/2018/q3/29[example]) and fixed timely. To report
|
||||||
|
security bugs, please contact me directly at mail@qutebrowser.org, GPG ID
|
||||||
https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072].
|
https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072].
|
||||||
|
|
||||||
Is there an adblocker?::
|
Is there an adblocker?::
|
||||||
There is a host-based adblocker which takes /etc/hosts-like lists. A "real"
|
There is a host-based adblocker which takes /etc/hosts-like lists. A "real"
|
||||||
adblocker has a
|
adblocker has a
|
||||||
http://www.reddit.com/r/programming/comments/25j41u/adblock_pluss_effect_on_firefoxs_memory_usage/chhpomw[big
|
https://www.reddit.com/r/programming/comments/25j41u/adblock_pluss_effect_on_firefoxs_memory_usage/chhpomw[big
|
||||||
impact] on browsing speed and
|
impact] on browsing speed and
|
||||||
https://blog.mozilla.org/nnethercote/2014/05/14/adblock-pluss-effect-on-firefoxs-memory-usage/[RAM
|
https://blog.mozilla.org/nnethercote/2014/05/14/adblock-pluss-effect-on-firefoxs-memory-usage/[RAM
|
||||||
usage], so implementing support for AdBlockPlus-like lists is currently not
|
usage], so implementing support for AdBlockPlus-like lists is currently not
|
||||||
a priority.
|
a priority.
|
||||||
|
|
||||||
|
How can I get No-Script-like behavior?::
|
||||||
|
To disable JavaScript by default:
|
||||||
|
+
|
||||||
|
----
|
||||||
|
:set content.javascript.enabled false
|
||||||
|
----
|
||||||
|
+
|
||||||
|
The basic command for enabling JavaScript for the current host is `tsh`.
|
||||||
|
This will allow JavaScript execution for the current session.
|
||||||
|
Use `S` instead of `s` to make the exception permanent.
|
||||||
|
With `H` instead of `h`, subdomains are included.
|
||||||
|
With `u` instead of `h`, only the current URL is whitelisted (not the whole host).
|
||||||
|
|
||||||
How do I play Youtube videos with mpv?::
|
How do I play Youtube videos with mpv?::
|
||||||
You can easily add a key binding to play youtube videos inside a real video
|
You can easily add a key binding to play youtube videos inside a real video
|
||||||
player - optionally even with hinting for links:
|
player - optionally even with hinting for links:
|
||||||
@ -132,24 +147,25 @@ It also works nicely with rapid hints:
|
|||||||
----
|
----
|
||||||
|
|
||||||
How do I use qutebrowser with mutt?::
|
How do I use qutebrowser with mutt?::
|
||||||
Due to a Qt limitation, local files without `.html` extensions are
|
For security reasons, local files without `.html` extensions aren't
|
||||||
"downloaded" instead of displayed, see
|
rendered as HTML, see
|
||||||
https://github.com/qutebrowser/qutebrowser/issues/566[#566]. You can work
|
https://bugs.chromium.org/p/chromium/issues/detail?id=777737[this Chromium issue]
|
||||||
around this by using this in your `mailcap`:
|
for details. You can do this in your `mailcap` file to get a proper
|
||||||
|
extension:
|
||||||
+
|
+
|
||||||
----
|
----
|
||||||
text/html; mv %s %s.html && qutebrowser %s.html >/dev/null 2>/dev/null; needsterminal;
|
text/html; qutebrowser %s; nametemplate=%s.html
|
||||||
----
|
----
|
||||||
|
|
||||||
What is the difference between bookmarks and quickmarks?::
|
What is the difference between bookmarks and quickmarks?::
|
||||||
Bookmarks will always use the title of the website as their name, but with quickmarks
|
Bookmarks will always use the title of the website as their name, but with quickmarks
|
||||||
you can set your own title.
|
you can set your own title.
|
||||||
+
|
+
|
||||||
For example, if you bookmark multiple food recipe websites and use `:open`,
|
For example, if you bookmark multiple food recipe websites and use `:open`,
|
||||||
you have to type the title or address of the website.
|
you have to type the title or address of the website.
|
||||||
+
|
+
|
||||||
When using quickmark, you can give them all names, like
|
When using quickmark, you can give them all names, like
|
||||||
`foodrecipes1`, `foodrecipes2` and so on. When you type
|
`foodrecipes1`, `foodrecipes2` and so on. When you type
|
||||||
`:open foodrecipes`, you will see a list of all the food recipe sites,
|
`:open foodrecipes`, you will see a list of all the food recipe sites,
|
||||||
without having to remember the exact website title or address.
|
without having to remember the exact website title or address.
|
||||||
|
|
||||||
@ -243,7 +259,7 @@ Note that there are some missing features which you may run into:
|
|||||||
See https://wiki.greasespot.net/Metadata_Block[the wiki] for more info.
|
See https://wiki.greasespot.net/Metadata_Block[the wiki] for more info.
|
||||||
. Any greasemonkey API function to do with adding UI elements is not currently
|
. Any greasemonkey API function to do with adding UI elements is not currently
|
||||||
supported. That means context menu extentensions and background pages.
|
supported. That means context menu extentensions and background pages.
|
||||||
|
|
||||||
== Troubleshooting
|
== Troubleshooting
|
||||||
|
|
||||||
Unable to view flash content.::
|
Unable to view flash content.::
|
||||||
@ -251,7 +267,7 @@ Unable to view flash content.::
|
|||||||
to use the flash plugin. Using the command `:set content.plugins true`
|
to use the flash plugin. Using the command `:set content.plugins true`
|
||||||
in qutebrowser will enable plugins. Packages for flash should
|
in qutebrowser will enable plugins. Packages for flash should
|
||||||
be provided for your platform or it can be obtained from
|
be provided for your platform or it can be obtained from
|
||||||
http://get.adobe.com/flashplayer/[Adobe].
|
https://get.adobe.com/flashplayer/[Adobe].
|
||||||
|
|
||||||
Experiencing freezing on sites like duckduckgo and youtube.::
|
Experiencing freezing on sites like duckduckgo and youtube.::
|
||||||
This issue could be caused by stale plugin files installed by `mozplugger`
|
This issue could be caused by stale plugin files installed by `mozplugger`
|
||||||
@ -264,12 +280,12 @@ When using QtWebEngine, qutebrowser reports "Render Process Crashed" and the con
|
|||||||
As stated in https://gcc.gnu.org/gcc-6/changes.html[GCC's Website] GCC 6 has introduced some optimizations that could break non-conforming codebases, like QtWebEngine. +
|
As stated in https://gcc.gnu.org/gcc-6/changes.html[GCC's Website] GCC 6 has introduced some optimizations that could break non-conforming codebases, like QtWebEngine. +
|
||||||
As a workaround, you can disable the nullpointer check optimization by adding the -fno-delete-null-pointer-checks flag while compiling. +
|
As a workaround, you can disable the nullpointer check optimization by adding the -fno-delete-null-pointer-checks flag while compiling. +
|
||||||
On gentoo, you just need to add it into your make.conf, like this: +
|
On gentoo, you just need to add it into your make.conf, like this: +
|
||||||
|
|
||||||
CFLAGS="... -fno-delete-null-pointer-checks"
|
CFLAGS="... -fno-delete-null-pointer-checks"
|
||||||
CXXFLAGS="... -fno-delete-null-pointer-checks"
|
CXXFLAGS="... -fno-delete-null-pointer-checks"
|
||||||
+
|
+
|
||||||
And then re-emerging qtwebengine with: +
|
And then re-emerging qtwebengine with: +
|
||||||
|
|
||||||
emerge -1 qtwebengine
|
emerge -1 qtwebengine
|
||||||
|
|
||||||
Unable to view DRM content (Netflix, Spotify, etc.).::
|
Unable to view DRM content (Netflix, Spotify, etc.).::
|
||||||
@ -295,10 +311,19 @@ Lastly, set your `qt.args` to point to that directory and restart qutebrowser:
|
|||||||
:restart
|
:restart
|
||||||
----
|
----
|
||||||
|
|
||||||
|
Unable to use `spawn` on MacOS.::
|
||||||
|
When running qutebrowser from the prebuilt binary (`qutebrowser.app`) it *will
|
||||||
|
not* read any files that would alter your `$PATH` (e.g. `.profile`, `.bashrc`,
|
||||||
|
etc). This is not a bug, just that `.profile` is not propogated to GUI
|
||||||
|
applications in MacOS.
|
||||||
|
+
|
||||||
|
See https://github.com/qutebrowser/qutebrowser/issues/4273[Issue #4273] for
|
||||||
|
details and potential workarounds.
|
||||||
|
|
||||||
My issue is not listed.::
|
My issue is not listed.::
|
||||||
If you experience any segfaults or crashes, you can report the issue in
|
If you experience any segfaults or crashes, you can report the issue in
|
||||||
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
|
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
|
||||||
using the `:report` command.
|
using the `:report` command.
|
||||||
If you are reporting a segfault, make sure you read the
|
If you are reporting a segfault, make sure you read the
|
||||||
link:stacktrace.asciidoc[guide] on how to report them with all needed
|
link:stacktrace{outfilesuffix}[guide] on how to report them with all needed
|
||||||
information.
|
information.
|
||||||
|
@ -38,7 +38,11 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
|||||||
|<<close,close>>|Close the current window.
|
|<<close,close>>|Close the current window.
|
||||||
|<<config-clear,config-clear>>|Set all settings back to their default.
|
|<<config-clear,config-clear>>|Set all settings back to their default.
|
||||||
|<<config-cycle,config-cycle>>|Cycle an option between multiple values.
|
|<<config-cycle,config-cycle>>|Cycle an option between multiple values.
|
||||||
|
|<<config-dict-add,config-dict-add>>|Add a key/value pair to a dictionary option.
|
||||||
|
|<<config-dict-remove,config-dict-remove>>|Remove a key from a dict.
|
||||||
|<<config-edit,config-edit>>|Open the config.py file in the editor.
|
|<<config-edit,config-edit>>|Open the config.py file in the editor.
|
||||||
|
|<<config-list-add,config-list-add>>|Append a value to a config option that is a list.
|
||||||
|
|<<config-list-remove,config-list-remove>>|Remove a value from a list.
|
||||||
|<<config-source,config-source>>|Read a config.py file.
|
|<<config-source,config-source>>|Read a config.py file.
|
||||||
|<<config-unset,config-unset>>|Unset an option.
|
|<<config-unset,config-unset>>|Unset an option.
|
||||||
|<<config-write-py,config-write-py>>|Write the current configuration to a config.py file.
|
|<<config-write-py,config-write-py>>|Write the current configuration to a config.py file.
|
||||||
@ -292,6 +296,35 @@ Cycle an option between multiple values.
|
|||||||
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
|
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
|
||||||
* +*-p*+, +*--print*+: Print the value after setting.
|
* +*-p*+, +*--print*+: Print the value after setting.
|
||||||
|
|
||||||
|
[[config-dict-add]]
|
||||||
|
=== config-dict-add
|
||||||
|
Syntax: +:config-dict-add [*--temp*] [*--replace*] 'option' 'key' 'value'+
|
||||||
|
|
||||||
|
Add a key/value pair to a dictionary option.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'option'+: The name of the option.
|
||||||
|
* +'key'+: The key to use.
|
||||||
|
* +'value'+: The value to place in the dictionary.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-t*+, +*--temp*+: Add value temporarily until qutebrowser is closed.
|
||||||
|
* +*-r*+, +*--replace*+: Replace existing values. By default, existing values are not overwritten.
|
||||||
|
|
||||||
|
|
||||||
|
[[config-dict-remove]]
|
||||||
|
=== config-dict-remove
|
||||||
|
Syntax: +:config-dict-remove [*--temp*] 'option' 'key'+
|
||||||
|
|
||||||
|
Remove a key from a dict.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'option'+: The name of the option.
|
||||||
|
* +'key'+: The key to remove from the dict.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-t*+, +*--temp*+: Remove value temporarily until qutebrowser is closed.
|
||||||
|
|
||||||
[[config-edit]]
|
[[config-edit]]
|
||||||
=== config-edit
|
=== config-edit
|
||||||
Syntax: +:config-edit [*--no-source*]+
|
Syntax: +:config-edit [*--no-source*]+
|
||||||
@ -301,6 +334,32 @@ Open the config.py file in the editor.
|
|||||||
==== optional arguments
|
==== optional arguments
|
||||||
* +*-n*+, +*--no-source*+: Don't re-source the config file after editing.
|
* +*-n*+, +*--no-source*+: Don't re-source the config file after editing.
|
||||||
|
|
||||||
|
[[config-list-add]]
|
||||||
|
=== config-list-add
|
||||||
|
Syntax: +:config-list-add [*--temp*] 'option' 'value'+
|
||||||
|
|
||||||
|
Append a value to a config option that is a list.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'option'+: The name of the option.
|
||||||
|
* +'value'+: The value to append to the end of the list.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-t*+, +*--temp*+: Add value temporarily until qutebrowser is closed.
|
||||||
|
|
||||||
|
[[config-list-remove]]
|
||||||
|
=== config-list-remove
|
||||||
|
Syntax: +:config-list-remove [*--temp*] 'option' 'value'+
|
||||||
|
|
||||||
|
Remove a value from a list.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'option'+: The name of the option.
|
||||||
|
* +'value'+: The value to remove from the list.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-t*+, +*--temp*+: Remove value temporarily until qutebrowser is closed.
|
||||||
|
|
||||||
[[config-source]]
|
[[config-source]]
|
||||||
=== config-source
|
=== config-source
|
||||||
Syntax: +:config-source [*--clear*] ['filename']+
|
Syntax: +:config-source [*--clear*] ['filename']+
|
||||||
@ -326,7 +385,7 @@ This sets an option back to its default and removes it from autoconfig.yml.
|
|||||||
* +'option'+: The name of the option.
|
* +'option'+: The name of the option.
|
||||||
|
|
||||||
==== optional arguments
|
==== optional arguments
|
||||||
* +*-t*+, +*--temp*+: Don't touch autoconfig.yml.
|
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
|
||||||
|
|
||||||
[[config-write-py]]
|
[[config-write-py]]
|
||||||
=== config-write-py
|
=== config-write-py
|
||||||
@ -545,6 +604,10 @@ Start hinting.
|
|||||||
- `inputs`: Only input fields.
|
- `inputs`: Only input fields.
|
||||||
|
|
||||||
|
|
||||||
|
Custom groups can be added via the `hints.selectors` setting
|
||||||
|
and also used here.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* +'target'+: What to do with the selected element.
|
* +'target'+: What to do with the selected element.
|
||||||
|
|
||||||
@ -576,7 +639,7 @@ Start hinting.
|
|||||||
- With `userscript`: The userscript to execute. Either store
|
- With `userscript`: The userscript to execute. Either store
|
||||||
the userscript in
|
the userscript in
|
||||||
`~/.local/share/qutebrowser/userscripts`
|
`~/.local/share/qutebrowser/userscripts`
|
||||||
(or `$XDG_DATA_DIR`), or use an absolute
|
(or `$XDG_DATA_HOME`), or use an absolute
|
||||||
path.
|
path.
|
||||||
- With `fill`: The command to fill the statusbar with.
|
- With `fill`: The command to fill the statusbar with.
|
||||||
`{hint-url}` will get replaced by the selected
|
`{hint-url}` will get replaced by the selected
|
||||||
@ -758,11 +821,11 @@ This tries to automatically click on typical _Previous Page_ or _Next Page_ link
|
|||||||
- `up`: Go up a level in the current URL.
|
- `up`: Go up a level in the current URL.
|
||||||
- `increment`: Increment the last number in the URL.
|
- `increment`: Increment the last number in the URL.
|
||||||
Uses the
|
Uses the
|
||||||
link:settings.html#url.incdec_segments[url.incdec_segments]
|
link:settings{outsuffix}#url.incdec_segments[url.incdec_segments]
|
||||||
config option.
|
config option.
|
||||||
- `decrement`: Decrement the last number in the URL.
|
- `decrement`: Decrement the last number in the URL.
|
||||||
Uses the
|
Uses the
|
||||||
link:settings.html#url.incdec_segments[url.incdec_segments]
|
link:settings{outsuffix}#url.incdec_segments[url.incdec_segments]
|
||||||
config option.
|
config option.
|
||||||
|
|
||||||
|
|
||||||
@ -918,6 +981,9 @@ Repeat a given command.
|
|||||||
* +'times'+: How many times to repeat.
|
* +'times'+: How many times to repeat.
|
||||||
* +'command'+: The command to run, with optional args.
|
* +'command'+: The command to run, with optional args.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
Multiplies with 'times' when given.
|
||||||
|
|
||||||
==== note
|
==== note
|
||||||
* This command does not split arguments after the last argument and handles quotes literally.
|
* This command does not split arguments after the last argument and handles quotes literally.
|
||||||
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
||||||
@ -1136,7 +1202,7 @@ Syntax: +:set [*--temp*] [*--print*] [*--pattern* 'pattern'] ['option'] ['value'
|
|||||||
|
|
||||||
Set an option.
|
Set an option.
|
||||||
|
|
||||||
If the option name ends with '?', the value of the option is shown instead. Using :set without any arguments opens a page where settings can be changed interactively.
|
If the option name ends with '?' or no value is provided, the value of the option is shown instead. Using :set without any arguments opens a page where settings can be changed interactively.
|
||||||
|
|
||||||
==== positional arguments
|
==== positional arguments
|
||||||
* +'option'+: The name of the option.
|
* +'option'+: The name of the option.
|
||||||
@ -1190,13 +1256,16 @@ Spawn a command in a shell.
|
|||||||
* +*-u*+, +*--userscript*+: Run the command as a userscript. You can use an absolute path, or store the userscript in one of those
|
* +*-u*+, +*--userscript*+: Run the command as a userscript. You can use an absolute path, or store the userscript in one of those
|
||||||
locations:
|
locations:
|
||||||
- `~/.local/share/qutebrowser/userscripts`
|
- `~/.local/share/qutebrowser/userscripts`
|
||||||
(or `$XDG_DATA_DIR`)
|
(or `$XDG_DATA_HOME`)
|
||||||
- `/usr/share/qutebrowser/userscripts`
|
- `/usr/share/qutebrowser/userscripts`
|
||||||
|
|
||||||
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
|
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
|
||||||
* +*-o*+, +*--output*+: Whether the output should be shown in a new tab.
|
* +*-o*+, +*--output*+: Whether the output should be shown in a new tab.
|
||||||
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
|
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
Given to userscripts as $QUTE_COUNT.
|
||||||
|
|
||||||
==== note
|
==== note
|
||||||
* This command does not split arguments after the last argument and handles quotes literally.
|
* This command does not split arguments after the last argument and handles quotes literally.
|
||||||
|
|
||||||
@ -1255,7 +1324,7 @@ The tab index to focus, starting with 1.
|
|||||||
|
|
||||||
[[tab-give]]
|
[[tab-give]]
|
||||||
=== tab-give
|
=== tab-give
|
||||||
Syntax: +:tab-give ['win-id']+
|
Syntax: +:tab-give [*--keep*] ['win-id']+
|
||||||
|
|
||||||
Give the current tab to a new or existing window if win_id given.
|
Give the current tab to a new or existing window if win_id given.
|
||||||
|
|
||||||
@ -1264,6 +1333,9 @@ If no win_id is given, the tab will get detached into a new window.
|
|||||||
==== positional arguments
|
==== positional arguments
|
||||||
* +'win-id'+: The window ID of the window to give the current tab to.
|
* +'win-id'+: The window ID of the window to give the current tab to.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-k*+, +*--keep*+: If given, keep the old tab around.
|
||||||
|
|
||||||
==== count
|
==== count
|
||||||
Overrides win_id (index starts at 1 for win_id=0).
|
Overrides win_id (index starts at 1 for win_id=0).
|
||||||
|
|
||||||
@ -1328,7 +1400,7 @@ How many tabs to switch back.
|
|||||||
|
|
||||||
[[tab-take]]
|
[[tab-take]]
|
||||||
=== tab-take
|
=== tab-take
|
||||||
Syntax: +:tab-take 'index'+
|
Syntax: +:tab-take [*--keep*] 'index'+
|
||||||
|
|
||||||
Take a tab from another window.
|
Take a tab from another window.
|
||||||
|
|
||||||
@ -1336,6 +1408,12 @@ Take a tab from another window.
|
|||||||
* +'index'+: The [win_id/]index of the tab to take. Or a substring in which case the closest match will be taken.
|
* +'index'+: The [win_id/]index of the tab to take. Or a substring in which case the closest match will be taken.
|
||||||
|
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-k*+, +*--keep*+: If given, keep the old tab around.
|
||||||
|
|
||||||
|
==== note
|
||||||
|
* This command does not split arguments after the last argument and handles quotes literally.
|
||||||
|
|
||||||
[[unbind]]
|
[[unbind]]
|
||||||
=== unbind
|
=== unbind
|
||||||
Syntax: +:unbind [*--mode* 'mode'] 'key'+
|
Syntax: +:unbind [*--mode* 'mode'] 'key'+
|
||||||
@ -1382,7 +1460,7 @@ Close all windows except for the current one.
|
|||||||
|
|
||||||
[[yank]]
|
[[yank]]
|
||||||
=== yank
|
=== yank
|
||||||
Syntax: +:yank [*--sel*] [*--keep*] ['what']+
|
Syntax: +:yank [*--sel*] [*--keep*] [*--quiet*] ['what']+
|
||||||
|
|
||||||
Yank something to the clipboard or primary selection.
|
Yank something to the clipboard or primary selection.
|
||||||
|
|
||||||
@ -1401,10 +1479,11 @@ Yank something to the clipboard or primary selection.
|
|||||||
==== optional arguments
|
==== optional arguments
|
||||||
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
||||||
* +*-k*+, +*--keep*+: Stay in visual mode after yanking the selection.
|
* +*-k*+, +*--keep*+: Stay in visual mode after yanking the selection.
|
||||||
|
* +*-q*+, +*--quiet*+: Don't show an information message.
|
||||||
|
|
||||||
[[zoom]]
|
[[zoom]]
|
||||||
=== zoom
|
=== zoom
|
||||||
Syntax: +:zoom ['zoom']+
|
Syntax: +:zoom [*--quiet*] ['zoom']+
|
||||||
|
|
||||||
Set the zoom level for the current tab.
|
Set the zoom level for the current tab.
|
||||||
|
|
||||||
@ -1413,20 +1492,33 @@ The zoom can be given as argument or as [count]. If neither is given, the zoom i
|
|||||||
==== positional arguments
|
==== positional arguments
|
||||||
* +'zoom'+: The zoom percentage to set.
|
* +'zoom'+: The zoom percentage to set.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-q*+, +*--quiet*+: Don't show a zoom level message.
|
||||||
|
|
||||||
==== count
|
==== count
|
||||||
The zoom percentage to set.
|
The zoom percentage to set.
|
||||||
|
|
||||||
[[zoom-in]]
|
[[zoom-in]]
|
||||||
=== zoom-in
|
=== zoom-in
|
||||||
|
Syntax: +:zoom-in [*--quiet*]+
|
||||||
|
|
||||||
Increase the zoom level for the current tab.
|
Increase the zoom level for the current tab.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-q*+, +*--quiet*+: Don't show a zoom level message.
|
||||||
|
|
||||||
==== count
|
==== count
|
||||||
How many steps to zoom in.
|
How many steps to zoom in.
|
||||||
|
|
||||||
[[zoom-out]]
|
[[zoom-out]]
|
||||||
=== zoom-out
|
=== zoom-out
|
||||||
|
Syntax: +:zoom-out [*--quiet*]+
|
||||||
|
|
||||||
Decrease the zoom level for the current tab.
|
Decrease the zoom level for the current tab.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-q*+, +*--quiet*+: Don't show a zoom level message.
|
||||||
|
|
||||||
==== count
|
==== count
|
||||||
How many steps to zoom out.
|
How many steps to zoom out.
|
||||||
|
|
||||||
@ -1657,7 +1749,7 @@ Shift the focus of the prompt file completion menu to another item.
|
|||||||
|
|
||||||
[[prompt-open-download]]
|
[[prompt-open-download]]
|
||||||
=== prompt-open-download
|
=== prompt-open-download
|
||||||
Syntax: +:prompt-open-download ['cmdline']+
|
Syntax: +:prompt-open-download [*--pdfjs*] ['cmdline']+
|
||||||
|
|
||||||
Immediately open a download.
|
Immediately open a download.
|
||||||
|
|
||||||
@ -1669,6 +1761,9 @@ If no specific command is given, this will use the system's default application
|
|||||||
cmdline.
|
cmdline.
|
||||||
|
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-p*+, +*--pdfjs*+: Open the download via PDF.js.
|
||||||
|
|
||||||
==== note
|
==== note
|
||||||
* This command does not split arguments after the last argument and handles quotes literally.
|
* This command does not split arguments after the last argument and handles quotes literally.
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ If you want to customize many settings, you can open the link:qute://settings[]
|
|||||||
page by running `:set` without any arguments, where all settings are listed and
|
page by running `:set` without any arguments, where all settings are listed and
|
||||||
customizable.
|
customizable.
|
||||||
|
|
||||||
Using the link:commands.html#set[`:set`] command and command completion, you
|
Using the link:commands{outfilesuffix}#set[`:set`] command and command completion, you
|
||||||
can quickly set settings interactively, for example `:set tabs.position left`.
|
can quickly set settings interactively, for example `:set tabs.position left`.
|
||||||
|
|
||||||
Some settings are also customizable for a given
|
Some settings are also customizable for a given
|
||||||
@ -53,8 +53,8 @@ https://developer.chrome.com/apps/match_patterns[URL pattern] by doing e.g.
|
|||||||
|
|
||||||
To get more help about a setting, use e.g. `:help tabs.position`.
|
To get more help about a setting, use e.g. `:help tabs.position`.
|
||||||
|
|
||||||
To bind and unbind keys, you can use the link:commands.html#bind[`:bind`] and
|
To bind and unbind keys, you can use the link:commands{outfilesuffix}#bind[`:bind`] and
|
||||||
link:commands.html#unbind[`:unbind`] commands:
|
link:commands{outfilesuffix}#unbind[`:unbind`] commands:
|
||||||
|
|
||||||
- Binding the key chain `,v` to the `:spawn mpv {url}` command:
|
- Binding the key chain `,v` to the `:spawn mpv {url}` command:
|
||||||
`:bind ,v spawn mpv {url}`
|
`:bind ,v spawn mpv {url}`
|
||||||
@ -67,9 +67,9 @@ See the help pages linked above (or `:help :bind`, `:help :unbind`) for more
|
|||||||
information.
|
information.
|
||||||
|
|
||||||
Other useful commands for config manipulation are
|
Other useful commands for config manipulation are
|
||||||
link:commands.html#config-unset[`:config-unset`] to reset a value to its default,
|
link:commands{outfilesuffix}#config-unset[`:config-unset`] to reset a value to its default,
|
||||||
link:commands.html#config-clear[`:config-clear`] to reset the entire configuration,
|
link:commands{outfilesuffix}#config-clear[`:config-clear`] to reset the entire configuration,
|
||||||
and link:commands.html#config-cycle[`:config-cycle`] to cycle a setting between
|
and link:commands{outfilesuffix}#config-cycle[`:config-cycle`] to cycle a setting between
|
||||||
different values.
|
different values.
|
||||||
|
|
||||||
[[configpy]]
|
[[configpy]]
|
||||||
@ -111,7 +111,7 @@ Note that qutebrowser does some Python magic so it's able to warn you about
|
|||||||
mistyped config settings. As an example, if you do `c.tabs.possition = "left"`,
|
mistyped config settings. As an example, if you do `c.tabs.possition = "left"`,
|
||||||
you'll get an error when starting.
|
you'll get an error when starting.
|
||||||
|
|
||||||
See the link:settings.html[settings help page] for all available settings. The
|
See the link:settings{outfilesuffix}[settings help page] for all available settings. The
|
||||||
accepted values depend on the type of the option. Commonly used are:
|
accepted values depend on the type of the option. Commonly used are:
|
||||||
|
|
||||||
- Strings: `c.tabs.position = "left"`
|
- Strings: `c.tabs.position = "left"`
|
||||||
@ -187,7 +187,7 @@ preferred to use the `config.bind` command. Doing so ensures the commands are
|
|||||||
valid and normalizes different expressions which map to the same key.
|
valid and normalizes different expressions which map to the same key.
|
||||||
|
|
||||||
For details on how to specify keys and the available modes, see the
|
For details on how to specify keys and the available modes, see the
|
||||||
link:settings.html#bindings.commands[documentation] for the `bindings.commands`
|
link:settings{outfilesuffix}#bindings.commands[documentation] for the `bindings.commands`
|
||||||
setting.
|
setting.
|
||||||
|
|
||||||
To bind a key:
|
To bind a key:
|
||||||
@ -395,6 +395,7 @@ Pre-built colorschemes
|
|||||||
|
|
||||||
- A collection of https://github.com/chriskempson/base16[base16] color-schemes can be found in https://github.com/theova/base16-qutebrowser[base16-qutebrowser] and used with https://github.com/AuditeMarlow/base16-manager[base16-manager].
|
- A collection of https://github.com/chriskempson/base16[base16] color-schemes can be found in https://github.com/theova/base16-qutebrowser[base16-qutebrowser] and used with https://github.com/AuditeMarlow/base16-manager[base16-manager].
|
||||||
- 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]
|
- 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/evannagle/qutebrowser-dracula-theme[Dracula]
|
||||||
|
|
||||||
Avoiding flake8 errors
|
Avoiding flake8 errors
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -452,7 +453,7 @@ or always navigate through command history with
|
|||||||
:bind -m command <Down> command-history-next
|
:bind -m command <Down> command-history-next
|
||||||
----
|
----
|
||||||
|
|
||||||
- The default for `completion.web_history_max_items` is now set to `-1`, showing
|
- The default for `completion.web_history.max_items` is now set to `-1`, showing
|
||||||
an unlimited number of items in the completion for `:open` as the new
|
an unlimited number of items in the completion for `:open` as the new
|
||||||
sqlite-based completion is much faster. If the `:open` completion is too slow
|
sqlite-based completion is much faster. If the `:open` completion is too slow
|
||||||
on your machine, set an appropriate limit again.
|
on your machine, set an appropriate limit again.
|
||||||
|
@ -6,14 +6,14 @@ Documentation
|
|||||||
|
|
||||||
The following help pages are currently available:
|
The following help pages are currently available:
|
||||||
|
|
||||||
* link:../quickstart.html[Quick start guide]
|
* link:../quickstart{outfilesuffix}[Quick start guide]
|
||||||
* link:../faq.html[Frequently asked questions]
|
* link:../faq{outfilesuffix}[Frequently asked questions]
|
||||||
* link:../changelog.html[Change Log]
|
* link:../changelog{outfilesuffix}[Change Log]
|
||||||
* link:commands.html[Documentation of commands]
|
* link:commands{outfilesuffix}[Documentation of commands]
|
||||||
* link:configuring.html[Configuring qutebrowser]
|
* link:configuring{outfilesuffix}[Configuring qutebrowser]
|
||||||
* link:settings.html[Documentation of settings]
|
* link:settings{outfilesuffix}[Documentation of settings]
|
||||||
* link:../userscripts.html[How to write userscripts]
|
* link:../userscripts{outfilesuffix}[How to write userscripts]
|
||||||
* link:../contributing.html[Contributing to qutebrowser]
|
* link:../contributing{outfilesuffix}[Contributing to qutebrowser]
|
||||||
|
|
||||||
Getting help
|
Getting help
|
||||||
------------
|
------------
|
||||||
|
@ -100,6 +100,7 @@
|
|||||||
|<<completion.delay,completion.delay>>|Delay (in milliseconds) before updating completions after typing a character.
|
|<<completion.delay,completion.delay>>|Delay (in milliseconds) before updating completions after typing a character.
|
||||||
|<<completion.height,completion.height>>|Height (in pixels or as percentage of the window) of the completion.
|
|<<completion.height,completion.height>>|Height (in pixels or as percentage of the window) of the completion.
|
||||||
|<<completion.min_chars,completion.min_chars>>|Minimum amount of characters needed to update completions.
|
|<<completion.min_chars,completion.min_chars>>|Minimum amount of characters needed to update completions.
|
||||||
|
|<<completion.open_categories,completion.open_categories>>|Which categories to show (in which order) in the :open completion.
|
||||||
|<<completion.quick,completion.quick>>|Move on to the next part when there's only one possible completion left.
|
|<<completion.quick,completion.quick>>|Move on to the next part when there's only one possible completion left.
|
||||||
|<<completion.scrollbar.padding,completion.scrollbar.padding>>|Padding (in pixels) of the scrollbar handle in the completion window.
|
|<<completion.scrollbar.padding,completion.scrollbar.padding>>|Padding (in pixels) of the scrollbar handle in the completion window.
|
||||||
|<<completion.scrollbar.width,completion.scrollbar.width>>|Width (in pixels) of the scrollbar in the completion window.
|
|<<completion.scrollbar.width,completion.scrollbar.width>>|Width (in pixels) of the scrollbar in the completion window.
|
||||||
@ -107,7 +108,8 @@
|
|||||||
|<<completion.shrink,completion.shrink>>|Shrink the completion to be smaller than the configured size if there are no scrollbars.
|
|<<completion.shrink,completion.shrink>>|Shrink the completion to be smaller than the configured size if there are no scrollbars.
|
||||||
|<<completion.timestamp_format,completion.timestamp_format>>|Format of timestamps (e.g. for the history completion).
|
|<<completion.timestamp_format,completion.timestamp_format>>|Format of timestamps (e.g. for the history completion).
|
||||||
|<<completion.use_best_match,completion.use_best_match>>|Execute the best-matching command on a partial match.
|
|<<completion.use_best_match,completion.use_best_match>>|Execute the best-matching command on a partial match.
|
||||||
|<<completion.web_history_max_items,completion.web_history_max_items>>|Number of URLs to show in the web history.
|
|<<completion.web_history.exclude,completion.web_history.exclude>>|A list of patterns which should not be shown in the history.
|
||||||
|
|<<completion.web_history.max_items,completion.web_history.max_items>>|Number of URLs to show in the web history.
|
||||||
|<<confirm_quit,confirm_quit>>|Require a confirmation before quitting the application.
|
|<<confirm_quit,confirm_quit>>|Require a confirmation before quitting the application.
|
||||||
|<<content.autoplay,content.autoplay>>|Automatically start playing `<video>` elements.
|
|<<content.autoplay,content.autoplay>>|Automatically start playing `<video>` elements.
|
||||||
|<<content.cache.appcache,content.cache.appcache>>|Enable support for the HTML 5 web application cache feature.
|
|<<content.cache.appcache,content.cache.appcache>>|Enable support for the HTML 5 web application cache feature.
|
||||||
@ -128,7 +130,7 @@
|
|||||||
|<<content.headers.user_agent,content.headers.user_agent>>|User agent to send. Unset to send the default.
|
|<<content.headers.user_agent,content.headers.user_agent>>|User agent to send. Unset to send the default.
|
||||||
|<<content.host_blocking.enabled,content.host_blocking.enabled>>|Enable host blocking.
|
|<<content.host_blocking.enabled,content.host_blocking.enabled>>|Enable host blocking.
|
||||||
|<<content.host_blocking.lists,content.host_blocking.lists>>|List of URLs of lists which contain hosts to block.
|
|<<content.host_blocking.lists,content.host_blocking.lists>>|List of URLs of lists which contain hosts to block.
|
||||||
|<<content.host_blocking.whitelist,content.host_blocking.whitelist>>|List of domains that should always be loaded, despite being ad-blocked.
|
|<<content.host_blocking.whitelist,content.host_blocking.whitelist>>|A list of patterns that should always be loaded, despite being ad-blocked.
|
||||||
|<<content.hyperlink_auditing,content.hyperlink_auditing>>|Enable hyperlink auditing (`<a ping>`).
|
|<<content.hyperlink_auditing,content.hyperlink_auditing>>|Enable hyperlink auditing (`<a ping>`).
|
||||||
|<<content.images,content.images>>|Load images automatically in web pages.
|
|<<content.images,content.images>>|Load images automatically in web pages.
|
||||||
|<<content.javascript.alert,content.javascript.alert>>|Show javascript alerts.
|
|<<content.javascript.alert,content.javascript.alert>>|Show javascript alerts.
|
||||||
@ -143,6 +145,8 @@
|
|||||||
|<<content.local_content_can_access_remote_urls,content.local_content_can_access_remote_urls>>|Allow locally loaded documents to access remote URLs.
|
|<<content.local_content_can_access_remote_urls,content.local_content_can_access_remote_urls>>|Allow locally loaded documents to access remote URLs.
|
||||||
|<<content.local_storage,content.local_storage>>|Enable support for HTML 5 local storage and Web SQL.
|
|<<content.local_storage,content.local_storage>>|Enable support for HTML 5 local storage and Web SQL.
|
||||||
|<<content.media_capture,content.media_capture>>|Allow websites to record audio/video.
|
|<<content.media_capture,content.media_capture>>|Allow websites to record audio/video.
|
||||||
|
|<<content.mouse_lock,content.mouse_lock>>|Allow websites to lock your mouse pointer.
|
||||||
|
|<<content.mute,content.mute>>|Automatically mute tabs.
|
||||||
|<<content.netrc_file,content.netrc_file>>|Netrc-file for HTTP authentication.
|
|<<content.netrc_file,content.netrc_file>>|Netrc-file for HTTP authentication.
|
||||||
|<<content.notifications,content.notifications>>|Allow websites to show notifications.
|
|<<content.notifications,content.notifications>>|Allow websites to show notifications.
|
||||||
|<<content.pdfjs,content.pdfjs>>|Allow pdf.js to view PDF files in the browser.
|
|<<content.pdfjs,content.pdfjs>>|Allow pdf.js to view PDF files in the browser.
|
||||||
@ -156,7 +160,7 @@
|
|||||||
|<<content.ssl_strict,content.ssl_strict>>|Validate SSL handshakes.
|
|<<content.ssl_strict,content.ssl_strict>>|Validate SSL handshakes.
|
||||||
|<<content.user_stylesheets,content.user_stylesheets>>|List of user stylesheet filenames to use.
|
|<<content.user_stylesheets,content.user_stylesheets>>|List of user stylesheet filenames to use.
|
||||||
|<<content.webgl,content.webgl>>|Enable WebGL.
|
|<<content.webgl,content.webgl>>|Enable WebGL.
|
||||||
|<<content.webrtc_public_interfaces_only,content.webrtc_public_interfaces_only>>|Only expose public interfaces via WebRTC.
|
|<<content.webrtc_ip_handling_policy,content.webrtc_ip_handling_policy>>|Which interfaces to expose via WebRTC.
|
||||||
|<<content.windowed_fullscreen,content.windowed_fullscreen>>|Limit fullscreen to the browser window (does not expand to fill the screen).
|
|<<content.windowed_fullscreen,content.windowed_fullscreen>>|Limit fullscreen to the browser window (does not expand to fill the screen).
|
||||||
|<<content.xss_auditing,content.xss_auditing>>|Monitor load requests for cross-site scripting attempts.
|
|<<content.xss_auditing,content.xss_auditing>>|Monitor load requests for cross-site scripting attempts.
|
||||||
|<<downloads.location.directory,downloads.location.directory>>|Directory to save downloads to.
|
|<<downloads.location.directory,downloads.location.directory>>|Directory to save downloads to.
|
||||||
@ -203,6 +207,7 @@
|
|||||||
|<<hints.next_regexes,hints.next_regexes>>|Comma-separated list of regular expressions to use for 'next' links.
|
|<<hints.next_regexes,hints.next_regexes>>|Comma-separated list of regular expressions to use for 'next' links.
|
||||||
|<<hints.prev_regexes,hints.prev_regexes>>|Comma-separated list of regular expressions to use for 'prev' links.
|
|<<hints.prev_regexes,hints.prev_regexes>>|Comma-separated list of regular expressions to use for 'prev' links.
|
||||||
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|
||||||
|
|<<hints.selectors,hints.selectors>>|CSS selectors used to determine which elements on a page should have hints.
|
||||||
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|
||||||
|<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session.
|
|<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session.
|
||||||
|<<input.escape_quits_reporter,input.escape_quits_reporter>>|Allow Escape to quit the crash reporter.
|
|<<input.escape_quits_reporter,input.escape_quits_reporter>>|Allow Escape to quit the crash reporter.
|
||||||
@ -227,7 +232,9 @@
|
|||||||
|<<qt.force_platform,qt.force_platform>>|Force a Qt platform to use.
|
|<<qt.force_platform,qt.force_platform>>|Force a Qt platform to use.
|
||||||
|<<qt.force_software_rendering,qt.force_software_rendering>>|Force software rendering for QtWebEngine.
|
|<<qt.force_software_rendering,qt.force_software_rendering>>|Force software rendering for QtWebEngine.
|
||||||
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
||||||
|<<scrolling.bar,scrolling.bar>>|Show a scrollbar.
|
|<<qt.low_end_device_mode,qt.low_end_device_mode>>|When to use Chromium's low-end device mode.
|
||||||
|
|<<qt.process_model,qt.process_model>>|Which Chromium process model to use.
|
||||||
|
|<<scrolling.bar,scrolling.bar>>|When to show the scrollbar.
|
||||||
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|
||||||
|<<search.ignore_case,search.ignore_case>>|When to find text on a page case-insensitively.
|
|<<search.ignore_case,search.ignore_case>>|When to find text on a page case-insensitively.
|
||||||
|<<search.incremental,search.incremental>>|Find text on a page incrementally, renewing the search for each typed character.
|
|<<search.incremental,search.incremental>>|Find text on a page incrementally, renewing the search for each typed character.
|
||||||
@ -246,11 +253,13 @@
|
|||||||
|<<tabs.indicator.padding,tabs.indicator.padding>>|Padding (in pixels) for tab indicators.
|
|<<tabs.indicator.padding,tabs.indicator.padding>>|Padding (in pixels) for tab indicators.
|
||||||
|<<tabs.indicator.width,tabs.indicator.width>>|Width (in pixels) of the progress indicator (0 to disable).
|
|<<tabs.indicator.width,tabs.indicator.width>>|Width (in pixels) of the progress indicator (0 to disable).
|
||||||
|<<tabs.last_close,tabs.last_close>>|How to behave when the last tab is closed.
|
|<<tabs.last_close,tabs.last_close>>|How to behave when the last tab is closed.
|
||||||
|
|<<tabs.max_width,tabs.max_width>>|Maximum width (in pixels) of tabs (-1 for no maximum).
|
||||||
|<<tabs.min_width,tabs.min_width>>|Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
|
|<<tabs.min_width,tabs.min_width>>|Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
|
||||||
|<<tabs.mode_on_change,tabs.mode_on_change>>|When switching tabs, what input mode is applied.
|
|<<tabs.mode_on_change,tabs.mode_on_change>>|When switching tabs, what input mode is applied.
|
||||||
|<<tabs.mousewheel_switching,tabs.mousewheel_switching>>|Switch between tabs using the mouse wheel.
|
|<<tabs.mousewheel_switching,tabs.mousewheel_switching>>|Switch between tabs using the mouse wheel.
|
||||||
|<<tabs.new_position.related,tabs.new_position.related>>|Position of new tabs opened from another tab.
|
|<<tabs.new_position.related,tabs.new_position.related>>|Position of new tabs opened from another tab.
|
||||||
|<<tabs.new_position.unrelated,tabs.new_position.unrelated>>|Position of new tabs which aren't opened from another tab.
|
|<<tabs.new_position.stacking,tabs.new_position.stacking>>|Stack related tabs on top of each other when opened consecutively.
|
||||||
|
|<<tabs.new_position.unrelated,tabs.new_position.unrelated>>|Position of new tabs which are not opened from another tab.
|
||||||
|<<tabs.padding,tabs.padding>>|Padding (in pixels) around text for tabs.
|
|<<tabs.padding,tabs.padding>>|Padding (in pixels) around text for tabs.
|
||||||
|<<tabs.pinned.shrink,tabs.pinned.shrink>>|Shrink pinned tabs down to their contents.
|
|<<tabs.pinned.shrink,tabs.pinned.shrink>>|Shrink pinned tabs down to their contents.
|
||||||
|<<tabs.position,tabs.position>>|Position of the tab bar.
|
|<<tabs.position,tabs.position>>|Position of the tab bar.
|
||||||
@ -287,9 +296,11 @@ Type: <<types,Dict>>
|
|||||||
|
|
||||||
Default:
|
Default:
|
||||||
|
|
||||||
- +pass:[q]+: +pass:[quit]+
|
- +pass:[q]+: +pass:[close]+
|
||||||
|
- +pass:[qa]+: +pass:[quit]+
|
||||||
- +pass:[w]+: +pass:[session-save]+
|
- +pass:[w]+: +pass:[session-save]+
|
||||||
- +pass:[wq]+: +pass:[quit --save]+
|
- +pass:[wq]+: +pass:[quit --save]+
|
||||||
|
- +pass:[wqa]+: +pass:[quit --save]+
|
||||||
|
|
||||||
[[auto_save.interval]]
|
[[auto_save.interval]]
|
||||||
=== auto_save.interval
|
=== auto_save.interval
|
||||||
@ -562,6 +573,7 @@ Default:
|
|||||||
* +pass:[g0]+: +pass:[tab-focus 1]+
|
* +pass:[g0]+: +pass:[tab-focus 1]+
|
||||||
* +pass:[gB]+: +pass:[set-cmd-text -s :bookmark-load -t]+
|
* +pass:[gB]+: +pass:[set-cmd-text -s :bookmark-load -t]+
|
||||||
* +pass:[gC]+: +pass:[tab-clone]+
|
* +pass:[gC]+: +pass:[tab-clone]+
|
||||||
|
* +pass:[gD]+: +pass:[tab-give]+
|
||||||
* +pass:[gO]+: +pass:[set-cmd-text :open -t -r {url:pretty}]+
|
* +pass:[gO]+: +pass:[set-cmd-text :open -t -r {url:pretty}]+
|
||||||
* +pass:[gU]+: +pass:[navigate up -t]+
|
* +pass:[gU]+: +pass:[navigate up -t]+
|
||||||
* +pass:[g^]+: +pass:[tab-focus 1]+
|
* +pass:[g^]+: +pass:[tab-focus 1]+
|
||||||
@ -593,6 +605,9 @@ Default:
|
|||||||
* +pass:[sk]+: +pass:[set-cmd-text -s :bind]+
|
* +pass:[sk]+: +pass:[set-cmd-text -s :bind]+
|
||||||
* +pass:[sl]+: +pass:[set-cmd-text -s :set -t]+
|
* +pass:[sl]+: +pass:[set-cmd-text -s :set -t]+
|
||||||
* +pass:[ss]+: +pass:[set-cmd-text -s :set]+
|
* +pass:[ss]+: +pass:[set-cmd-text -s :set]+
|
||||||
|
* +pass:[tIH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.images ;; reload]+
|
||||||
|
* +pass:[tIh]+: +pass:[config-cycle -p -u *://{url:host}/* content.images ;; reload]+
|
||||||
|
* +pass:[tIu]+: +pass:[config-cycle -p -u {url} content.images ;; reload]+
|
||||||
* +pass:[tPH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.plugins ;; reload]+
|
* +pass:[tPH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.plugins ;; reload]+
|
||||||
* +pass:[tPh]+: +pass:[config-cycle -p -u *://{url:host}/* content.plugins ;; reload]+
|
* +pass:[tPh]+: +pass:[config-cycle -p -u *://{url:host}/* content.plugins ;; reload]+
|
||||||
* +pass:[tPu]+: +pass:[config-cycle -p -u {url} content.plugins ;; reload]+
|
* +pass:[tPu]+: +pass:[config-cycle -p -u {url} content.plugins ;; reload]+
|
||||||
@ -600,6 +615,9 @@ Default:
|
|||||||
* +pass:[tSh]+: +pass:[config-cycle -p -u *://{url:host}/* content.javascript.enabled ;; reload]+
|
* +pass:[tSh]+: +pass:[config-cycle -p -u *://{url:host}/* content.javascript.enabled ;; reload]+
|
||||||
* +pass:[tSu]+: +pass:[config-cycle -p -u {url} content.javascript.enabled ;; reload]+
|
* +pass:[tSu]+: +pass:[config-cycle -p -u {url} content.javascript.enabled ;; reload]+
|
||||||
* +pass:[th]+: +pass:[back -t]+
|
* +pass:[th]+: +pass:[back -t]+
|
||||||
|
* +pass:[tiH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.images ;; reload]+
|
||||||
|
* +pass:[tih]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.images ;; reload]+
|
||||||
|
* +pass:[tiu]+: +pass:[config-cycle -p -t -u {url} content.images ;; reload]+
|
||||||
* +pass:[tl]+: +pass:[forward -t]+
|
* +pass:[tl]+: +pass:[forward -t]+
|
||||||
* +pass:[tpH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.plugins ;; reload]+
|
* +pass:[tpH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.plugins ;; reload]+
|
||||||
* +pass:[tph]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.plugins ;; reload]+
|
* +pass:[tph]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.plugins ;; reload]+
|
||||||
@ -633,7 +651,7 @@ Default:
|
|||||||
* +pass:[}}]+: +pass:[navigate next -t]+
|
* +pass:[}}]+: +pass:[navigate next -t]+
|
||||||
- +pass:[passthrough]+:
|
- +pass:[passthrough]+:
|
||||||
|
|
||||||
* +pass:[<Ctrl-V>]+: +pass:[leave-mode]+
|
* +pass:[<Shift-Escape>]+: +pass:[leave-mode]+
|
||||||
- +pass:[prompt]+:
|
- +pass:[prompt]+:
|
||||||
|
|
||||||
* +pass:[<Alt-B>]+: +pass:[rl-backward-word]+
|
* +pass:[<Alt-B>]+: +pass:[rl-backward-word]+
|
||||||
@ -649,6 +667,7 @@ Default:
|
|||||||
* +pass:[<Ctrl-F>]+: +pass:[rl-forward-char]+
|
* +pass:[<Ctrl-F>]+: +pass:[rl-forward-char]+
|
||||||
* +pass:[<Ctrl-H>]+: +pass:[rl-backward-delete-char]+
|
* +pass:[<Ctrl-H>]+: +pass:[rl-backward-delete-char]+
|
||||||
* +pass:[<Ctrl-K>]+: +pass:[rl-kill-line]+
|
* +pass:[<Ctrl-K>]+: +pass:[rl-kill-line]+
|
||||||
|
* +pass:[<Ctrl-P>]+: +pass:[prompt-open-download --pdfjs]+
|
||||||
* +pass:[<Ctrl-U>]+: +pass:[rl-unix-line-discard]+
|
* +pass:[<Ctrl-U>]+: +pass:[rl-unix-line-discard]+
|
||||||
* +pass:[<Ctrl-W>]+: +pass:[rl-unix-word-rubout]+
|
* +pass:[<Ctrl-W>]+: +pass:[rl-unix-word-rubout]+
|
||||||
* +pass:[<Ctrl-X>]+: +pass:[prompt-open-download]+
|
* +pass:[<Ctrl-X>]+: +pass:[prompt-open-download]+
|
||||||
@ -780,7 +799,7 @@ Default: +pass:[black]+
|
|||||||
=== colors.completion.match.fg
|
=== colors.completion.match.fg
|
||||||
Foreground color of the matched text in the completion.
|
Foreground color of the matched text in the completion.
|
||||||
|
|
||||||
Type: <<types,QssColor>>
|
Type: <<types,QtColor>>
|
||||||
|
|
||||||
Default: +pass:[#ff4444]+
|
Default: +pass:[#ff4444]+
|
||||||
|
|
||||||
@ -1383,6 +1402,26 @@ Type: <<types,Int>>
|
|||||||
|
|
||||||
Default: +pass:[1]+
|
Default: +pass:[1]+
|
||||||
|
|
||||||
|
[[completion.open_categories]]
|
||||||
|
=== completion.open_categories
|
||||||
|
Which categories to show (in which order) in the :open completion.
|
||||||
|
|
||||||
|
Type: <<types,FlagList>>
|
||||||
|
|
||||||
|
Valid values:
|
||||||
|
|
||||||
|
* +searchengines+
|
||||||
|
* +quickmarks+
|
||||||
|
* +bookmarks+
|
||||||
|
* +history+
|
||||||
|
|
||||||
|
Default:
|
||||||
|
|
||||||
|
- +pass:[searchengines]+
|
||||||
|
- +pass:[quickmarks]+
|
||||||
|
- +pass:[bookmarks]+
|
||||||
|
- +pass:[history]+
|
||||||
|
|
||||||
[[completion.quick]]
|
[[completion.quick]]
|
||||||
=== completion.quick
|
=== completion.quick
|
||||||
Move on to the next part when there's only one possible completion left.
|
Move on to the next part when there's only one possible completion left.
|
||||||
@ -1445,8 +1484,19 @@ Type: <<types,Bool>>
|
|||||||
|
|
||||||
Default: +pass:[false]+
|
Default: +pass:[false]+
|
||||||
|
|
||||||
[[completion.web_history_max_items]]
|
[[completion.web_history.exclude]]
|
||||||
=== completion.web_history_max_items
|
=== completion.web_history.exclude
|
||||||
|
A list of patterns which should not be shown in the history.
|
||||||
|
This only affects the completion. Matching URLs are still saved in the history (and visible on the qute://history page), but hidden in the completion.
|
||||||
|
Changing this setting will cause the completion history to be regenerated on the next start, which will take a short while.
|
||||||
|
This setting requires a restart.
|
||||||
|
|
||||||
|
Type: <<types,List of UrlPattern>>
|
||||||
|
|
||||||
|
Default: empty
|
||||||
|
|
||||||
|
[[completion.web_history.max_items]]
|
||||||
|
=== completion.web_history.max_items
|
||||||
Number of URLs to show in the web history.
|
Number of URLs to show in the web history.
|
||||||
0: no history / -1: unlimited
|
0: no history / -1: unlimited
|
||||||
|
|
||||||
@ -1474,7 +1524,9 @@ Default:
|
|||||||
[[content.autoplay]]
|
[[content.autoplay]]
|
||||||
=== content.autoplay
|
=== content.autoplay
|
||||||
Automatically start playing `<video>` elements.
|
Automatically start playing `<video>` elements.
|
||||||
Note this option needs a restart with QtWebEngine on Qt < 5.11.
|
Note: On Qt < 5.11, this option needs a restart and does not support URL patterns.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,Bool>>
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
@ -1570,6 +1622,8 @@ Default: +pass:[iso-8859-1]+
|
|||||||
Allow websites to share screen content.
|
Allow websites to share screen content.
|
||||||
On Qt < 5.10, a dialog box is always displayed, even if this is set to "true".
|
On Qt < 5.10, a dialog box is always displayed, even if this is set to "true".
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,BoolAsk>>
|
Type: <<types,BoolAsk>>
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
@ -1609,6 +1663,8 @@ This setting is only available with the QtWebKit backend.
|
|||||||
=== content.geolocation
|
=== content.geolocation
|
||||||
Allow websites to request geolocations.
|
Allow websites to request geolocations.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,BoolAsk>>
|
Type: <<types,BoolAsk>>
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
@ -1622,6 +1678,9 @@ Default: +pass:[ask]+
|
|||||||
[[content.headers.accept_language]]
|
[[content.headers.accept_language]]
|
||||||
=== content.headers.accept_language
|
=== content.headers.accept_language
|
||||||
Value to send in the `Accept-Language` header.
|
Value to send in the `Accept-Language` header.
|
||||||
|
Note that the value read from JavaScript is always the global value.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,String>>
|
Type: <<types,String>>
|
||||||
|
|
||||||
@ -1631,6 +1690,8 @@ Default: +pass:[en-US,en]+
|
|||||||
=== content.headers.custom
|
=== content.headers.custom
|
||||||
Custom headers for qutebrowser HTTP requests.
|
Custom headers for qutebrowser HTTP requests.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,Dict>>
|
Type: <<types,Dict>>
|
||||||
|
|
||||||
Default: empty
|
Default: empty
|
||||||
@ -1640,6 +1701,8 @@ Default: empty
|
|||||||
Value to send in the `DNT` header.
|
Value to send in the `DNT` header.
|
||||||
When this is set to true, qutebrowser asks websites to not track your identity. If set to null, the DNT header is not sent at all.
|
When this is set to true, qutebrowser asks websites to not track your identity. If set to null, the DNT header is not sent at all.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,Bool>>
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
Default: +pass:[true]+
|
Default: +pass:[true]+
|
||||||
@ -1648,6 +1711,8 @@ Default: +pass:[true]+
|
|||||||
=== content.headers.referer
|
=== content.headers.referer
|
||||||
When to send the Referer header.
|
When to send the Referer header.
|
||||||
The Referer header tells websites from which website you were coming from when visiting them.
|
The Referer header tells websites from which website you were coming from when visiting them.
|
||||||
|
No restart is needed with QtWebKit.
|
||||||
|
This setting requires a restart.
|
||||||
|
|
||||||
Type: <<types,String>>
|
Type: <<types,String>>
|
||||||
|
|
||||||
@ -1655,15 +1720,16 @@ Valid values:
|
|||||||
|
|
||||||
* +always+: Always send the Referer.
|
* +always+: Always send the Referer.
|
||||||
* +never+: Never send the Referer. This is not recommended, as some sites may break.
|
* +never+: Never send the Referer. This is not recommended, as some sites may break.
|
||||||
* +same-domain+: Only send the Referer for the same domain. This will still protect your privacy, but shouldn't break any sites.
|
* +same-domain+: Only send the Referer for the same domain. This will still protect your privacy, but shouldn't break any sites. With QtWebEngine, the referer will still be sent for other domains, but with stripped path information.
|
||||||
|
|
||||||
Default: +pass:[same-domain]+
|
Default: +pass:[same-domain]+
|
||||||
|
|
||||||
This setting is only available with the QtWebKit backend.
|
|
||||||
|
|
||||||
[[content.headers.user_agent]]
|
[[content.headers.user_agent]]
|
||||||
=== content.headers.user_agent
|
=== content.headers.user_agent
|
||||||
User agent to send. Unset to send the default.
|
User agent to send. Unset to send the default.
|
||||||
|
Note that the value read from JavaScript is always the global value.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,String>>
|
Type: <<types,String>>
|
||||||
|
|
||||||
@ -1673,6 +1739,8 @@ Default: empty
|
|||||||
=== content.host_blocking.enabled
|
=== content.host_blocking.enabled
|
||||||
Enable host blocking.
|
Enable host blocking.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,Bool>>
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
Default: +pass:[true]+
|
Default: +pass:[true]+
|
||||||
@ -1688,6 +1756,11 @@ The file can be in one of the following formats:
|
|||||||
- A zip-file of any of the above, with either only one file, or a file
|
- A zip-file of any of the above, with either only one file, or a file
|
||||||
named `hosts` (with any extension).
|
named `hosts` (with any extension).
|
||||||
|
|
||||||
|
It's also possible to add a local file or directory via a `file://` URL. In
|
||||||
|
case of a directory, all files in the directory are read as adblock lists.
|
||||||
|
|
||||||
|
The file `~/.config/qutebrowser/blocked-hosts` is always read if it exists.
|
||||||
|
|
||||||
|
|
||||||
Type: <<types,List of Url>>
|
Type: <<types,List of Url>>
|
||||||
|
|
||||||
@ -1697,11 +1770,11 @@ Default:
|
|||||||
|
|
||||||
[[content.host_blocking.whitelist]]
|
[[content.host_blocking.whitelist]]
|
||||||
=== content.host_blocking.whitelist
|
=== content.host_blocking.whitelist
|
||||||
List of domains that should always be loaded, despite being ad-blocked.
|
A list of patterns that should always be loaded, despite being ad-blocked.
|
||||||
Domains may contain * and ? wildcards and are otherwise required to exactly match the requested domain.
|
Note this whitelists blocked hosts, not first-party URLs. As an example, if `example.org` loads an ad from `ads.example.org`, the whitelisted host should be `ads.example.org`. If you want to disable the adblocker on a given page, use the `content.host_blocking.enabled` setting with a URL pattern instead.
|
||||||
Local domains are always exempt from hostblocking.
|
Local domains are always exempt from hostblocking.
|
||||||
|
|
||||||
Type: <<types,List of String>>
|
Type: <<types,List of UrlPattern>>
|
||||||
|
|
||||||
Default:
|
Default:
|
||||||
|
|
||||||
@ -1843,6 +1916,8 @@ Default: +pass:[true]+
|
|||||||
=== content.media_capture
|
=== content.media_capture
|
||||||
Allow websites to record audio/video.
|
Allow websites to record audio/video.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,BoolAsk>>
|
Type: <<types,BoolAsk>>
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
@ -1855,6 +1930,37 @@ Default: +pass:[ask]+
|
|||||||
|
|
||||||
This setting is only available with the QtWebEngine backend.
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
|
[[content.mouse_lock]]
|
||||||
|
=== content.mouse_lock
|
||||||
|
Allow websites to lock your mouse pointer.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
|
Type: <<types,BoolAsk>>
|
||||||
|
|
||||||
|
Valid values:
|
||||||
|
|
||||||
|
* +true+
|
||||||
|
* +false+
|
||||||
|
* +ask+
|
||||||
|
|
||||||
|
Default: +pass:[ask]+
|
||||||
|
|
||||||
|
On QtWebEngine, this setting requires Qt 5.8 or newer.
|
||||||
|
|
||||||
|
On QtWebKit, this setting is unavailable.
|
||||||
|
|
||||||
|
[[content.mute]]
|
||||||
|
=== content.mute
|
||||||
|
Automatically mute tabs.
|
||||||
|
Note that if the `:tab-mute` command is used, the mute status for the affected tab is now controlled manually, and this setting doesn't have any effect.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
|
Default: +pass:[false]+
|
||||||
|
|
||||||
[[content.netrc_file]]
|
[[content.netrc_file]]
|
||||||
=== content.netrc_file
|
=== content.netrc_file
|
||||||
Netrc-file for HTTP authentication.
|
Netrc-file for HTTP authentication.
|
||||||
@ -1868,6 +1974,8 @@ Default: empty
|
|||||||
=== content.notifications
|
=== content.notifications
|
||||||
Allow websites to show notifications.
|
Allow websites to show notifications.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,BoolAsk>>
|
Type: <<types,BoolAsk>>
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
@ -1889,12 +1997,12 @@ Type: <<types,Bool>>
|
|||||||
|
|
||||||
Default: +pass:[false]+
|
Default: +pass:[false]+
|
||||||
|
|
||||||
This setting is only available with the QtWebKit backend.
|
|
||||||
|
|
||||||
[[content.persistent_storage]]
|
[[content.persistent_storage]]
|
||||||
=== content.persistent_storage
|
=== content.persistent_storage
|
||||||
Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`.
|
Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,BoolAsk>>
|
Type: <<types,BoolAsk>>
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
@ -1967,6 +2075,8 @@ This setting is only available with the QtWebKit backend.
|
|||||||
=== content.register_protocol_handler
|
=== content.register_protocol_handler
|
||||||
Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|
Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,BoolAsk>>
|
Type: <<types,BoolAsk>>
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
@ -1985,6 +2095,8 @@ On QtWebKit, this setting is unavailable.
|
|||||||
=== content.ssl_strict
|
=== content.ssl_strict
|
||||||
Validate SSL handshakes.
|
Validate SSL handshakes.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,BoolAsk>>
|
Type: <<types,BoolAsk>>
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
@ -2013,14 +2125,22 @@ Type: <<types,Bool>>
|
|||||||
|
|
||||||
Default: +pass:[true]+
|
Default: +pass:[true]+
|
||||||
|
|
||||||
[[content.webrtc_public_interfaces_only]]
|
[[content.webrtc_ip_handling_policy]]
|
||||||
=== content.webrtc_public_interfaces_only
|
=== content.webrtc_ip_handling_policy
|
||||||
Only expose public interfaces via WebRTC.
|
Which interfaces to expose via WebRTC.
|
||||||
On Qt 5.9, this option requires a restart. On Qt 5.10, this option doesn't work at all because of a Qt bug. On Qt >= 5.11, no restart is required.
|
On Qt 5.10, this option doesn't work because of a Qt bug.
|
||||||
|
This setting requires a restart.
|
||||||
|
|
||||||
Type: <<types,Bool>>
|
Type: <<types,String>>
|
||||||
|
|
||||||
Default: +pass:[false]+
|
Valid values:
|
||||||
|
|
||||||
|
* +all-interfaces+: WebRTC has the right to enumerate all interfaces and bind them to discover public interfaces.
|
||||||
|
* +default-public-and-private-interfaces+: WebRTC should only use the default route used by http. This also exposes the associated default private address. Default route is the route chosen by the OS on a multi-homed endpoint.
|
||||||
|
* +default-public-interface-only+: WebRTC should only use the default route used by http. This doesn't expose any local addresses.
|
||||||
|
* +disable-non-proxied-udp+: WebRTC should only use TCP to contact peers or servers unless the proxy server supports UDP. This doesn't expose any local addresses either.
|
||||||
|
|
||||||
|
Default: +pass:[all-interfaces]+
|
||||||
|
|
||||||
On QtWebEngine, this setting requires Qt 5.9.2 or newer.
|
On QtWebEngine, this setting requires Qt 5.9.2 or newer.
|
||||||
|
|
||||||
@ -2037,13 +2157,13 @@ Default: +pass:[false]+
|
|||||||
[[content.xss_auditing]]
|
[[content.xss_auditing]]
|
||||||
=== content.xss_auditing
|
=== content.xss_auditing
|
||||||
Monitor load requests for cross-site scripting attempts.
|
Monitor load requests for cross-site scripting attempts.
|
||||||
Suspicious scripts will be blocked and reported in the inspector's JavaScript console. Enabling this feature might have an impact on performance.
|
Suspicious scripts will be blocked and reported in the inspector's JavaScript console.
|
||||||
|
|
||||||
This setting supports URL patterns.
|
This setting supports URL patterns.
|
||||||
|
|
||||||
Type: <<types,Bool>>
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
Default: +pass:[false]+
|
Default: +pass:[true]+
|
||||||
|
|
||||||
[[downloads.location.directory]]
|
[[downloads.location.directory]]
|
||||||
=== downloads.location.directory
|
=== downloads.location.directory
|
||||||
@ -2455,6 +2575,76 @@ Type: <<types,Bool>>
|
|||||||
|
|
||||||
Default: +pass:[true]+
|
Default: +pass:[true]+
|
||||||
|
|
||||||
|
[[hints.selectors]]
|
||||||
|
=== hints.selectors
|
||||||
|
CSS selectors used to determine which elements on a page should have hints.
|
||||||
|
|
||||||
|
This setting supports URL patterns.
|
||||||
|
|
||||||
|
This setting can only be set in config.py.
|
||||||
|
|
||||||
|
Type: <<types,Dict>>
|
||||||
|
|
||||||
|
Default:
|
||||||
|
|
||||||
|
- +pass:[all]+:
|
||||||
|
|
||||||
|
* +pass:[a]+
|
||||||
|
* +pass:[area]+
|
||||||
|
* +pass:[textarea]+
|
||||||
|
* +pass:[select]+
|
||||||
|
* +pass:[input:not([type="hidden"])]+
|
||||||
|
* +pass:[button]+
|
||||||
|
* +pass:[frame]+
|
||||||
|
* +pass:[iframe]+
|
||||||
|
* +pass:[img]+
|
||||||
|
* +pass:[link]+
|
||||||
|
* +pass:[summary]+
|
||||||
|
* +pass:[[onclick]]+
|
||||||
|
* +pass:[[onmousedown]]+
|
||||||
|
* +pass:[[role="link"]]+
|
||||||
|
* +pass:[[role="option"]]+
|
||||||
|
* +pass:[[role="button"]]+
|
||||||
|
* +pass:[[ng-click]]+
|
||||||
|
* +pass:[[ngClick]]+
|
||||||
|
* +pass:[[data-ng-click]]+
|
||||||
|
* +pass:[[x-ng-click]]+
|
||||||
|
* +pass:[[tabindex]]+
|
||||||
|
- +pass:[images]+:
|
||||||
|
|
||||||
|
* +pass:[img]+
|
||||||
|
- +pass:[inputs]+:
|
||||||
|
|
||||||
|
* +pass:[input[type="text"]]+
|
||||||
|
* +pass:[input[type="date"]]+
|
||||||
|
* +pass:[input[type="datetime-local"]]+
|
||||||
|
* +pass:[input[type="email"]]+
|
||||||
|
* +pass:[input[type="month"]]+
|
||||||
|
* +pass:[input[type="number"]]+
|
||||||
|
* +pass:[input[type="password"]]+
|
||||||
|
* +pass:[input[type="search"]]+
|
||||||
|
* +pass:[input[type="tel"]]+
|
||||||
|
* +pass:[input[type="time"]]+
|
||||||
|
* +pass:[input[type="url"]]+
|
||||||
|
* +pass:[input[type="week"]]+
|
||||||
|
* +pass:[input:not([type])]+
|
||||||
|
* +pass:[textarea]+
|
||||||
|
- +pass:[links]+:
|
||||||
|
|
||||||
|
* +pass:[a[href]]+
|
||||||
|
* +pass:[area[href]]+
|
||||||
|
* +pass:[link[href]]+
|
||||||
|
* +pass:[[role="link"][href]]+
|
||||||
|
- +pass:[media]+:
|
||||||
|
|
||||||
|
* +pass:[audio]+
|
||||||
|
* +pass:[img]+
|
||||||
|
* +pass:[video]+
|
||||||
|
- +pass:[url]+:
|
||||||
|
|
||||||
|
* +pass:[[src]]+
|
||||||
|
* +pass:[[href]]+
|
||||||
|
|
||||||
[[hints.uppercase]]
|
[[hints.uppercase]]
|
||||||
=== hints.uppercase
|
=== hints.uppercase
|
||||||
Make characters in hint strings uppercase.
|
Make characters in hint strings uppercase.
|
||||||
@ -2699,13 +2889,59 @@ Type: <<types,Bool>>
|
|||||||
|
|
||||||
Default: +pass:[false]+
|
Default: +pass:[false]+
|
||||||
|
|
||||||
|
[[qt.low_end_device_mode]]
|
||||||
|
=== qt.low_end_device_mode
|
||||||
|
When to use Chromium's low-end device mode.
|
||||||
|
This improves the RAM usage of renderer processes, at the expense of performance.
|
||||||
|
This setting requires a restart.
|
||||||
|
|
||||||
|
Type: <<types,String>>
|
||||||
|
|
||||||
|
Valid values:
|
||||||
|
|
||||||
|
* +always+: Always use low-end device mode.
|
||||||
|
* +auto+: Decide automatically (uses low-end mode with < 1 GB available RAM).
|
||||||
|
* +never+: Never use low-end device mode.
|
||||||
|
|
||||||
|
Default: +pass:[auto]+
|
||||||
|
|
||||||
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
|
[[qt.process_model]]
|
||||||
|
=== qt.process_model
|
||||||
|
Which Chromium process model to use.
|
||||||
|
Alternative process models use less resources, but decrease security and robustness.
|
||||||
|
See the following pages for more details:
|
||||||
|
|
||||||
|
- https://www.chromium.org/developers/design-documents/process-models
|
||||||
|
- https://doc.qt.io/qt-5/qtwebengine-features.html#process-models
|
||||||
|
This setting requires a restart.
|
||||||
|
|
||||||
|
Type: <<types,String>>
|
||||||
|
|
||||||
|
Valid values:
|
||||||
|
|
||||||
|
* +process-per-site-instance+: Pages from separate sites are put into separate processes and separate visits to the same site are also isolated.
|
||||||
|
* +process-per-site+: Pages from separate sites are put into separate processes. Unlike Process per Site Instance, all visits to the same site will share an OS process. The benefit of this model is reduced memory consumption, because more web pages will share processes. The drawbacks include reduced security, robustness, and responsiveness.
|
||||||
|
* +single-process+: Run all tabs in a single process. This should be used for debugging purposes only, and it disables `:open --private`.
|
||||||
|
|
||||||
|
Default: +pass:[process-per-site-instance]+
|
||||||
|
|
||||||
|
This setting is only available with the QtWebEngine backend.
|
||||||
|
|
||||||
[[scrolling.bar]]
|
[[scrolling.bar]]
|
||||||
=== scrolling.bar
|
=== scrolling.bar
|
||||||
Show a scrollbar.
|
When to show the scrollbar.
|
||||||
|
|
||||||
Type: <<types,Bool>>
|
Type: <<types,String>>
|
||||||
|
|
||||||
Default: +pass:[false]+
|
Valid values:
|
||||||
|
|
||||||
|
* +always+: Always show the scrollbar.
|
||||||
|
* +never+: Never show the scrollbar.
|
||||||
|
* +when-searching+: Show the scrollbar when searching for text in the webpage. With the QtWebKit backend, this is equal to `never`.
|
||||||
|
|
||||||
|
Default: +pass:[when-searching]+
|
||||||
|
|
||||||
[[scrolling.smooth]]
|
[[scrolling.smooth]]
|
||||||
=== scrolling.smooth
|
=== scrolling.smooth
|
||||||
@ -2972,6 +3208,17 @@ Valid values:
|
|||||||
|
|
||||||
Default: +pass:[ignore]+
|
Default: +pass:[ignore]+
|
||||||
|
|
||||||
|
[[tabs.max_width]]
|
||||||
|
=== tabs.max_width
|
||||||
|
Maximum width (in pixels) of tabs (-1 for no maximum).
|
||||||
|
This setting only applies when tabs are horizontal.
|
||||||
|
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False.
|
||||||
|
This setting may not apply properly if max_width is smaller than the minimum size of tab contents, or smaller than tabs.min_width.
|
||||||
|
|
||||||
|
Type: <<types,Int>>
|
||||||
|
|
||||||
|
Default: +pass:[-1]+
|
||||||
|
|
||||||
[[tabs.min_width]]
|
[[tabs.min_width]]
|
||||||
=== tabs.min_width
|
=== tabs.min_width
|
||||||
Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
|
Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
|
||||||
@ -3007,6 +3254,7 @@ Default: +pass:[true]+
|
|||||||
[[tabs.new_position.related]]
|
[[tabs.new_position.related]]
|
||||||
=== tabs.new_position.related
|
=== tabs.new_position.related
|
||||||
Position of new tabs opened from another tab.
|
Position of new tabs opened from another tab.
|
||||||
|
See `tabs.new_position.stacking` for controlling stacking behavior.
|
||||||
|
|
||||||
Type: <<types,NewTabPosition>>
|
Type: <<types,NewTabPosition>>
|
||||||
|
|
||||||
@ -3019,9 +3267,19 @@ Valid values:
|
|||||||
|
|
||||||
Default: +pass:[next]+
|
Default: +pass:[next]+
|
||||||
|
|
||||||
|
[[tabs.new_position.stacking]]
|
||||||
|
=== tabs.new_position.stacking
|
||||||
|
Stack related tabs on top of each other when opened consecutively.
|
||||||
|
Only applies for `next` and `prev` values of `tabs.new_position.related` and `tabs.new_position.unrelated`.
|
||||||
|
|
||||||
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
|
Default: +pass:[true]+
|
||||||
|
|
||||||
[[tabs.new_position.unrelated]]
|
[[tabs.new_position.unrelated]]
|
||||||
=== tabs.new_position.unrelated
|
=== tabs.new_position.unrelated
|
||||||
Position of new tabs which aren't opened from another tab.
|
Position of new tabs which are not opened from another tab.
|
||||||
|
See `tabs.new_position.stacking` for controlling stacking behavior.
|
||||||
|
|
||||||
Type: <<types,NewTabPosition>>
|
Type: <<types,NewTabPosition>>
|
||||||
|
|
||||||
@ -3381,7 +3639,7 @@ When setting from a string, pass a json-like list, e.g. `["one", "two"]`.
|
|||||||
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in http://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color) * `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages) * `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359) * A gradient as explained in http://doc.qt.io/qt-5/stylesheet-reference.html#list-of-property-types[the Qt documentation] under ``Gradient''
|
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in http://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color) * `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages) * `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359) * A gradient as explained in http://doc.qt.io/qt-5/stylesheet-reference.html#list-of-property-types[the Qt documentation] under ``Gradient''
|
||||||
|QtColor|A color value.
|
|QtColor|A color value.
|
||||||
|
|
||||||
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in http://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color)
|
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in http://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color) * `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages) * `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359)
|
||||||
|QtFont|A font family, with optional style/weight/size.
|
|QtFont|A font family, with optional style/weight/size.
|
||||||
|
|
||||||
* Style: `normal`/`italic`/`oblique` * Weight: `normal`, `bold`, `100`..`900` * Size: _number_ `px`/`pt`
|
* Style: `normal`/`italic`/`oblique` * Weight: `normal`, `bold`, `100`..`900` * Size: _number_ `px`/`pt`
|
||||||
@ -3403,5 +3661,8 @@ See the setting's valid values for more information on allowed values.
|
|||||||
See https://sqlite.org/lang_datefunc.html for reference.
|
See https://sqlite.org/lang_datefunc.html for reference.
|
||||||
|UniqueCharString|A string which may not contain duplicate chars.
|
|UniqueCharString|A string which may not contain duplicate chars.
|
||||||
|Url|A URL as a string.
|
|Url|A URL as a string.
|
||||||
|
|UrlPattern|A match pattern for a URL.
|
||||||
|
|
||||||
|
See https://developer.chrome.com/apps/match_patterns for the allowed syntax.
|
||||||
|VerticalPosition|The position of the download bar.
|
|VerticalPosition|The position of the download bar.
|
||||||
|==============
|
|==============
|
||||||
|
@ -222,6 +222,38 @@ There are prebuilt RPMs available at https://software.opensuse.org/download.html
|
|||||||
|
|
||||||
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
|
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
|
||||||
|
|
||||||
|
On Slackware
|
||||||
|
------------
|
||||||
|
|
||||||
|
qutebrowser is available in the 3rd party repository at http://slackbuilds.org[slackbuilds.org]
|
||||||
|
|
||||||
|
An easy way to install it is with sbopkg (frontend for slackbuilds.org) available at http://sbopkg.org[sbopkg.org]
|
||||||
|
|
||||||
|
sbopkg can be run with a dialog screen interface, or via command line options.
|
||||||
|
|
||||||
|
After installing the latest sbopkg package, choose your release version, and sync the repo.
|
||||||
|
|
||||||
|
----
|
||||||
|
sbopkg -V 14.2
|
||||||
|
sbopkg -r
|
||||||
|
----
|
||||||
|
|
||||||
|
The pyPEG2 and MarkupSafe dependencies both need building for python3. You can either set PYTHON3=yes in the shell or set those as options in the dialog menu for each.
|
||||||
|
|
||||||
|
Generate a queue file for qutebrowser and dependencies:
|
||||||
|
|
||||||
|
----
|
||||||
|
sqg -p qutebrowser
|
||||||
|
----
|
||||||
|
|
||||||
|
Then load the queue in the dialog queue menu or via:
|
||||||
|
|
||||||
|
----
|
||||||
|
PYTHON3=yes sbopkg -i qutebrowser
|
||||||
|
----
|
||||||
|
|
||||||
|
If you use the dialog screen you can deselect any already-installed packages that you don't need/want to rebuild before starting the build process.
|
||||||
|
|
||||||
On OpenBSD
|
On OpenBSD
|
||||||
----------
|
----------
|
||||||
|
|
||||||
@ -407,6 +439,14 @@ caveats:
|
|||||||
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
|
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
|
||||||
qutebrowser if you want SSL to work in certain downloads (e.g. for
|
qutebrowser if you want SSL to work in certain downloads (e.g. for
|
||||||
`:adblock-update` or `:download`).
|
`:adblock-update` or `:download`).
|
||||||
|
* On Ubuntu (tested on 18.04), you will need to install the `libssl1.0.0`
|
||||||
|
package (`apt install libssl1.0.0`). Then, in the qutebrowser git
|
||||||
|
repository, create a directory named `libssl` (`mkdir libssl`), and link
|
||||||
|
`libcrypto.so.1.0.0` and `libssl.so.1.0.0` into it without the versioning
|
||||||
|
part in their names (`ln -s /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.0
|
||||||
|
libssl/libcrypto.so` and `ln -s /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0
|
||||||
|
libssl/libssl.so`). Now you can start qutebrowser issuing `export
|
||||||
|
LD_LIBRARY_PATH=$(pwd)/libssl` beforehand.
|
||||||
- It comes with a QtWebEngine compiled without proprietary codec support (such
|
- It comes with a QtWebEngine compiled without proprietary codec support (such
|
||||||
as h.264).
|
as h.264).
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ Also note userscripts need to have the executable bit set (`chmod +x`) for
|
|||||||
qutebrowser to run them.
|
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 data directory under
|
||||||
`userscripts` (for example: `~/.local/share/qutebrowser/userscripts/myscript`),
|
`userscripts` (for example: `~/.local/share/qutebrowser/userscripts/myscript`),
|
||||||
or just use an absolute path.
|
or just use an absolute path.
|
||||||
|
|
||||||
NOTE: On Windows, only userscripts with `com`, `bat`, or `exe` extensions will be launched.
|
NOTE: On Windows, only userscripts with `com`, `bat`, or `exe` extensions will be launched.
|
||||||
@ -45,12 +45,13 @@ In `command` mode:
|
|||||||
- `QUTE_URL`: The current URL.
|
- `QUTE_URL`: The current URL.
|
||||||
- `QUTE_TITLE`: The title of the current page.
|
- `QUTE_TITLE`: The title of the current page.
|
||||||
- `QUTE_SELECTED_TEXT`: The text currently selected on the page.
|
- `QUTE_SELECTED_TEXT`: The text currently selected on the page.
|
||||||
|
- `QUTE_COUNT`: The `count` from the spawn command running the userscript.
|
||||||
|
|
||||||
In `hints` mode:
|
In `hints` mode:
|
||||||
|
|
||||||
- `QUTE_URL`: The URL selected via hints.
|
- `QUTE_URL`: The URL selected via hints.
|
||||||
- `QUTE_SELECTED_TEXT`: The plain text of the element selected via hints.
|
- `QUTE_SELECTED_TEXT`: The plain text of the element selected via hints.
|
||||||
- `QUTE_SELECTED_HTML` The HTML of the element selected via hints.
|
- `QUTE_SELECTED_HTML`: The HTML of the element selected via hints.
|
||||||
|
|
||||||
Sending commands
|
Sending commands
|
||||||
----------------
|
----------------
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
PYTHON = python3
|
PYTHON = python3
|
||||||
PREFIX = /usr/local
|
PREFIX ?= /usr/local
|
||||||
DESTDIR =
|
|
||||||
ICONSIZES = 16 24 32 48 64 128 256 512
|
ICONSIZES = 16 24 32 48 64 128 256 512
|
||||||
|
DATAROOTDIR = $(PREFIX)/share
|
||||||
|
DATADIR ?= $(DATAROOTDIR)
|
||||||
|
MANDIR ?= $(DATAROOTDIR)/man
|
||||||
|
|
||||||
SETUPTOOLSOPTIONS =
|
SETUPTOOLSOPTIONS =
|
||||||
ifdef DESTDIR
|
ifdef DESTDIR
|
||||||
@ -16,18 +18,18 @@ doc/qutebrowser.1.html:
|
|||||||
install: doc/qutebrowser.1.html
|
install: doc/qutebrowser.1.html
|
||||||
$(PYTHON) setup.py install --prefix="$(PREFIX)" --optimize=1 $(SETUPTOOLSOPTS)
|
$(PYTHON) setup.py install --prefix="$(PREFIX)" --optimize=1 $(SETUPTOOLSOPTS)
|
||||||
install -Dm644 misc/qutebrowser.appdata.xml \
|
install -Dm644 misc/qutebrowser.appdata.xml \
|
||||||
"$(DESTDIR)$(PREFIX)/share/metainfo/qutebrowser.appdata.xml"
|
"$(DESTDIR)$(DATADIR)/metainfo/qutebrowser.appdata.xml"
|
||||||
install -Dm644 doc/qutebrowser.1 \
|
install -Dm644 doc/qutebrowser.1 \
|
||||||
"$(DESTDIR)$(PREFIX)/share/man/man1/qutebrowser.1"
|
"$(DESTDIR)$(MANDIR)/man1/qutebrowser.1"
|
||||||
install -Dm644 misc/qutebrowser.desktop \
|
install -Dm644 misc/qutebrowser.desktop \
|
||||||
"$(DESTDIR)$(PREFIX)/share/applications/qutebrowser.desktop"
|
"$(DESTDIR)$(DATADIR)/applications/qutebrowser.desktop"
|
||||||
$(foreach i,$(ICONSIZES),install -Dm644 "icons/qutebrowser-$(i)x$(i).png" \
|
$(foreach i,$(ICONSIZES),install -Dm644 "icons/qutebrowser-$(i)x$(i).png" \
|
||||||
"$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";)
|
"$(DESTDIR)$(DATADIR)/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";)
|
||||||
install -Dm644 icons/qutebrowser.svg \
|
install -Dm644 icons/qutebrowser.svg \
|
||||||
"$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/qutebrowser.svg"
|
"$(DESTDIR)$(DATADIR)/icons/hicolor/scalable/apps/qutebrowser.svg"
|
||||||
install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/userscripts/" \
|
install -Dm755 -t "$(DESTDIR)$(DATADIR)/qutebrowser/userscripts/" \
|
||||||
$(wildcard misc/userscripts/*)
|
$(wildcard misc/userscripts/*)
|
||||||
install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/scripts/" \
|
install -Dm755 -t "$(DESTDIR)$(DATADIR)/qutebrowser/scripts/" \
|
||||||
$(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \
|
$(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \
|
||||||
scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \
|
scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \
|
||||||
scripts/link_pyqt.py,$(wildcard scripts/*))
|
scripts/link_pyqt.py,$(wildcard scripts/*))
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
height="682.66669"
|
height="682.66669"
|
||||||
id="svg2"
|
id="svg2"
|
||||||
sodipodi:version="0.32"
|
sodipodi:version="0.32"
|
||||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
version="1.0"
|
version="1.0"
|
||||||
sodipodi:docname="cheatsheet.svg"
|
sodipodi:docname="cheatsheet.svg"
|
||||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||||
@ -33,7 +33,7 @@
|
|||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="1.7536248"
|
inkscape:zoom="1.7536248"
|
||||||
inkscape:cx="430.72917"
|
inkscape:cx="613.20834"
|
||||||
inkscape:cy="268.64059"
|
inkscape:cy="268.64059"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
@ -3085,7 +3085,9 @@
|
|||||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
id="flowPara4056"> (to index/left/right)</flowPara><flowPara
|
id="flowPara4056"> (to index/left/right)</flowPara><flowPara
|
||||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
id="flowPara3858">gC - clone tab </flowPara><flowPara
|
id="flowPara3858">gC - clone tab</flowPara><flowPara
|
||||||
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
|
id="flowPara6098">gD - detach tab </flowPara><flowPara
|
||||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
id="flowPara3860">gf - view page source</flowPara><flowPara
|
id="flowPara3860">gf - view page source</flowPara><flowPara
|
||||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
@ -3097,9 +3099,9 @@
|
|||||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
id="flowPara3921">sf - save config</flowPara><flowPara
|
id="flowPara3921">sf - save config</flowPara><flowPara
|
||||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
id="flowPara3925">ss - set setting</flowPara><flowPara
|
id="flowPara3925">ss - set setting (sl: temp)</flowPara><flowPara
|
||||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
id="flowPara3927">sl - set temp. setting</flowPara><flowPara
|
id="flowPara3927" /><flowPara
|
||||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
id="flowPara3929">sk - bind key</flowPara><flowPara
|
id="flowPara3929">sk - bind key</flowPara><flowPara
|
||||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||||
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 181 KiB |
@ -1,6 +1,7 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=qutebrowser
|
Name=qutebrowser
|
||||||
GenericName=Web Browser
|
GenericName=Web Browser
|
||||||
|
Comment=A keyboard-driven, vim-like browser based on PyQt5
|
||||||
Icon=qutebrowser
|
Icon=qutebrowser
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Network;WebBrowser;
|
Categories=Network;WebBrowser;
|
||||||
|
@ -40,6 +40,9 @@ Section "Install"
|
|||||||
; Uninstall old versions
|
; Uninstall old versions
|
||||||
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{633F41F9-FE9B-42D1-9CC4-718CBD01EE11}'
|
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{633F41F9-FE9B-42D1-9CC4-718CBD01EE11}'
|
||||||
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{9331D947-AC86-4542-A755-A833429C6E69}'
|
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{9331D947-AC86-4542-A755-A833429C6E69}'
|
||||||
|
IfFileExists "$INSTDIR\uninst.exe" 0 +2
|
||||||
|
ExecWait "$INSTDIR\uninst.exe /S _?=$INSTDIR"
|
||||||
|
CreateDirectory "$INSTDIR"
|
||||||
|
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ def get_data_files():
|
|||||||
('../qutebrowser/config/configdata.yml', 'config'),
|
('../qutebrowser/config/configdata.yml', 'config'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
|
if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
|
||||||
# data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
|
data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
|
||||||
# else:
|
else:
|
||||||
# print("Warning: excluding pdfjs as it's not present!")
|
print("Warning: excluding pdfjs as it's not present!")
|
||||||
|
|
||||||
return data_files
|
return data_files
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
certifi==2018.4.16
|
certifi==2018.8.24
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
codecov==2.0.15
|
codecov==2.0.15
|
||||||
coverage==4.5.1
|
coverage==4.5.1
|
||||||
idna==2.7
|
idna==2.7
|
||||||
requests==2.18.4
|
requests==2.19.1
|
||||||
urllib3==1.22
|
urllib3==1.23
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
attrs==18.1.0
|
attrs==18.2.0
|
||||||
flake8==3.5.0
|
flake8==3.5.0
|
||||||
flake8-bugbear==18.2.0
|
flake8-bugbear==18.8.0
|
||||||
flake8-builtins==1.4.1 # rq.filter: != 1.4.0
|
flake8-builtins==1.4.1 # rq.filter: != 1.4.0
|
||||||
flake8-comprehensions==1.4.1
|
flake8-comprehensions==1.4.1
|
||||||
flake8-copyright==0.2.0
|
flake8-copyright==0.2.0
|
||||||
flake8-debugger==3.1.0
|
flake8-debugger==3.1.0
|
||||||
flake8-deprecated==1.3
|
flake8-deprecated==1.3
|
||||||
flake8-docstrings==1.3.0
|
flake8-docstrings==1.3.0
|
||||||
flake8-future-import==0.4.4
|
flake8-future-import==0.4.5
|
||||||
flake8-mock==0.3
|
flake8-mock==0.3
|
||||||
flake8-per-file-ignores==0.6
|
flake8-per-file-ignores==0.6
|
||||||
flake8-polyfill==1.0.2
|
flake8-polyfill==1.0.2
|
||||||
@ -24,4 +24,4 @@ pydocstyle==2.1.1
|
|||||||
pyflakes==2.0.0
|
pyflakes==2.0.0
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
snowballstemmer==1.2.1
|
snowballstemmer==1.2.1
|
||||||
typing==3.6.4
|
typing==3.6.6
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
appdirs==1.4.3
|
appdirs==1.4.3
|
||||||
packaging==17.1
|
packaging==18.0
|
||||||
pyparsing==2.2.0
|
pyparsing==2.2.2
|
||||||
setuptools==39.2.0
|
setuptools==40.4.3
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
wheel==0.31.1
|
wheel==0.32.1
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
altgraph==0.15
|
altgraph==0.16.1
|
||||||
future==0.16.0
|
future==0.16.0
|
||||||
macholib==1.9
|
macholib==1.11
|
||||||
pefile==2017.11.5
|
pefile==2018.8.8
|
||||||
PyInstaller==3.3.1
|
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||||
|
@ -1 +1,4 @@
|
|||||||
PyInstaller
|
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||||
|
|
||||||
|
# remove @commit-id for scm installs
|
||||||
|
#@ replace: @.*# @develop#
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
|
||||||
|
|
||||||
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
|
|
||||||
certifi==2018.4.16
|
|
||||||
chardet==3.0.4
|
|
||||||
github3.py==1.1.0
|
|
||||||
idna==2.7
|
|
||||||
isort==4.3.4
|
|
||||||
lazy-object-proxy==1.3.1
|
|
||||||
mccabe==0.6.1
|
|
||||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
|
||||||
python-dateutil==2.7.3
|
|
||||||
./scripts/dev/pylint_checkers
|
|
||||||
requests==2.18.4
|
|
||||||
six==1.11.0
|
|
||||||
uritemplate==3.0.0
|
|
||||||
urllib3==1.22
|
|
||||||
wrapt==1.10.11
|
|
@ -1,11 +0,0 @@
|
|||||||
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
|
|
||||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
|
||||||
./scripts/dev/pylint_checkers
|
|
||||||
requests
|
|
||||||
github3.py
|
|
||||||
|
|
||||||
# remove @commit-id for scm installs
|
|
||||||
#@ replace: @.*# #
|
|
||||||
|
|
||||||
# fix qute-pylint location
|
|
||||||
#@ replace: qute-pylint==.* ./scripts/dev/pylint_checkers
|
|
@ -1,18 +1,23 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
astroid==1.6.5
|
asn1crypto==0.24.0
|
||||||
certifi==2018.4.16
|
astroid==2.0.4
|
||||||
|
certifi==2018.8.24
|
||||||
|
cffi==1.11.5
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
github3.py==1.1.0
|
cryptography==2.3.1
|
||||||
|
github3.py==1.2.0
|
||||||
idna==2.7
|
idna==2.7
|
||||||
isort==4.3.4
|
isort==4.3.4
|
||||||
|
jwcrypto==0.5.0
|
||||||
lazy-object-proxy==1.3.1
|
lazy-object-proxy==1.3.1
|
||||||
mccabe==0.6.1
|
mccabe==0.6.1
|
||||||
pylint==1.9.2
|
pycparser==2.19
|
||||||
|
pylint==2.1.1
|
||||||
python-dateutil==2.7.3
|
python-dateutil==2.7.3
|
||||||
./scripts/dev/pylint_checkers
|
./scripts/dev/pylint_checkers
|
||||||
requests==2.18.4
|
requests==2.19.1
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
uritemplate==3.0.0
|
uritemplate==3.0.0
|
||||||
urllib3==1.22
|
urllib3==1.23
|
||||||
wrapt==1.10.11
|
wrapt==1.10.11
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
|
||||||
|
|
||||||
PyQt5==5.10 # rq.filter: != 5.10.1
|
|
||||||
sip==4.19.8
|
|
@ -1,2 +0,0 @@
|
|||||||
PyQt5==5.10.0
|
|
||||||
#@ filter: PyQt5 != 5.10.1
|
|
@ -1,4 +1,4 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
PyQt5==5.10.1
|
PyQt5==5.11.3
|
||||||
sip==4.19.8
|
PyQt5-sip==4.19.13
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
docutils==0.14
|
docutils==0.14
|
||||||
pyroma==2.3.1
|
pyroma==2.4
|
||||||
|
@ -35,8 +35,4 @@ git+https://github.com/pallets/markupsafe.git
|
|||||||
hg+http://bitbucket.org/birkenfeld/pygments-main
|
hg+http://bitbucket.org/birkenfeld/pygments-main
|
||||||
hg+https://bitbucket.org/fdik/pypeg
|
hg+https://bitbucket.org/fdik/pypeg
|
||||||
git+https://github.com/python-attrs/attrs.git
|
git+https://github.com/python-attrs/attrs.git
|
||||||
|
git+https://github.com/yaml/pyyaml.git
|
||||||
# Fails to build:
|
|
||||||
# gcc: error: ext/_yaml.c: No such file or directory
|
|
||||||
# hg+https://bitbucket.org/xi/pyyaml
|
|
||||||
PyYAML==3.12
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
attrs==18.1.0
|
atomicwrites==1.2.1
|
||||||
beautifulsoup4==4.6.0
|
attrs==18.2.0
|
||||||
cheroot==6.3.1
|
backports.functools-lru-cache==1.5
|
||||||
click==6.7
|
beautifulsoup4==4.6.3
|
||||||
|
cheroot==6.5.2
|
||||||
|
click==7.0
|
||||||
# colorama==0.3.9
|
# colorama==0.3.9
|
||||||
coverage==4.5.1
|
coverage==4.5.1
|
||||||
EasyProcess==0.2.3
|
EasyProcess==0.2.3
|
||||||
@ -11,30 +13,30 @@ fields==5.0.0
|
|||||||
Flask==1.0.2
|
Flask==1.0.2
|
||||||
glob2==0.6
|
glob2==0.6
|
||||||
hunter==2.0.2
|
hunter==2.0.2
|
||||||
hypothesis==3.57.0
|
hypothesis==3.74.3
|
||||||
itsdangerous==0.24
|
itsdangerous==0.24
|
||||||
# Jinja2==2.10
|
# Jinja2==2.10
|
||||||
Mako==1.0.7
|
Mako==1.0.7
|
||||||
# MarkupSafe==1.0
|
# MarkupSafe==1.0
|
||||||
more-itertools==4.2.0
|
more-itertools==4.3.0
|
||||||
parse==1.8.4
|
parse==1.9.0
|
||||||
parse-type==0.4.2
|
parse-type==0.4.2
|
||||||
pluggy==0.6.0
|
pluggy==0.7.1
|
||||||
py==1.5.3
|
py==1.6.0
|
||||||
py-cpuinfo==4.0.0
|
py-cpuinfo==4.0.0
|
||||||
pytest==3.6.1
|
pytest==3.6.4 # rq.filter: <3.7
|
||||||
pytest-bdd==2.21.0
|
pytest-bdd==2.21.0
|
||||||
pytest-benchmark==3.1.1
|
pytest-benchmark==3.1.1
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.6.0
|
||||||
pytest-faulthandler==1.5.0
|
pytest-faulthandler==1.5.0
|
||||||
pytest-instafail==0.4.0
|
pytest-instafail==0.4.0
|
||||||
pytest-mock==1.10.0
|
pytest-mock==1.10.0
|
||||||
pytest-qt==2.4.0
|
pytest-qt==3.2.1
|
||||||
pytest-repeat==0.4.1
|
pytest-repeat==0.7.0
|
||||||
pytest-rerunfailures==4.1
|
pytest-rerunfailures==4.2
|
||||||
pytest-travis-fold==1.3.0
|
pytest-travis-fold==1.3.0
|
||||||
pytest-xvfb==1.1.0
|
pytest-xvfb==1.1.0
|
||||||
PyVirtualDisplay==0.2.1
|
PyVirtualDisplay==0.2.1
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
vulture==0.27
|
vulture==0.29
|
||||||
Werkzeug==0.14.1
|
Werkzeug==0.14.1
|
||||||
|
@ -4,7 +4,7 @@ coverage
|
|||||||
Flask
|
Flask
|
||||||
hunter
|
hunter
|
||||||
hypothesis
|
hypothesis
|
||||||
pytest
|
pytest<3.7
|
||||||
pytest-bdd
|
pytest-bdd
|
||||||
pytest-benchmark
|
pytest-benchmark
|
||||||
pytest-cov
|
pytest-cov
|
||||||
@ -19,3 +19,4 @@ pytest-xvfb
|
|||||||
vulture
|
vulture
|
||||||
|
|
||||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||||
|
#@ filter: pytest <3.7
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
pluggy==0.6.0
|
pluggy==0.7.1
|
||||||
py==1.5.3
|
py==1.6.0
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
tox==3.0.0
|
toml==0.10.0
|
||||||
|
tox==3.5.1
|
||||||
virtualenv==16.0.0
|
virtualenv==16.0.0
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
vulture==0.27
|
vulture==0.29
|
||||||
|
61
misc/userscripts/README.md
Normal file
61
misc/userscripts/README.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Userscripts
|
||||||
|
|
||||||
|
The following userscripts are included in the current directory.
|
||||||
|
|
||||||
|
- [cast](./cast): Cast content on your Chromecast using [castnow][]. Only
|
||||||
|
[youtube-dl][] downloadable content.
|
||||||
|
- [dmenu_qutebrowser](./dmenu_qutebrowser): Pipes history, quickmarks, and URL into dmenu.
|
||||||
|
- [format_json](./format_json): Pretty prints current page's JSON code in other
|
||||||
|
tab.
|
||||||
|
- [getbib](./getbib): Scraping the current web page for DOIs and downloading
|
||||||
|
corresponding bibtex information.
|
||||||
|
- [open_download](./open_download): Opens a rofi menu with
|
||||||
|
all files from the download directory and opens the selected file.
|
||||||
|
- [openfeeds](./openfeeds): Opens all links to feeds defined in the head of a site.
|
||||||
|
- [password_fill](./password_fill): Find a username/password entry and fill it
|
||||||
|
with credentials given by the configured backend (currently only pass) for the
|
||||||
|
current website.
|
||||||
|
- [qute-keepass](./qute-keepass): Insertion of usernames and passwords from keepass
|
||||||
|
databases using pykeepass.
|
||||||
|
- [qute-pass](./qute-pass): Insert login information using pass and a
|
||||||
|
dmenu-compatible application (e.g. dmenu, rofi -dmenu, ...).
|
||||||
|
- [qutedmenu](./qutedmenu): Handle open -s && open -t with bemenu.
|
||||||
|
- [readability](./readability): Executes python-readability on current page 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.
|
||||||
|
- [taskadd](./taskadd): Adds a task to taskwarrior.
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
[castnow]: https://github.com/xat/castnow
|
||||||
|
[youtube-dl]: https://rg3.github.io/youtube-dl/
|
||||||
|
|
||||||
|
## Others
|
||||||
|
|
||||||
|
The following userscripts can be found on their own repositories.
|
||||||
|
|
||||||
|
- [qurlshare](https://github.com/sim590/qurlshare): *secure* sharing of an URL between qutebrowser
|
||||||
|
instances using a distributed hash table.
|
||||||
|
- [qutebrowser-userscripts](https://github.com/cryzed/qutebrowser-userscripts):
|
||||||
|
a small pack of userscripts.
|
||||||
|
- [qutebrowser-zotero](https://github.com/parchd-1/qutebrowser-zotero): connects
|
||||||
|
qutebrowser to [Zotero][] standalone.
|
||||||
|
- [qute.match](https://github.com/bziur/qute.match): execute script based on
|
||||||
|
visisted url.
|
||||||
|
- [qutepocket](https://github.com/kepi/qutepocket): Add URL to your [Pocket][]
|
||||||
|
bookmark manager.
|
||||||
|
- [qb-scripts](https://github.com/peterjschroeder/qb-scripts): a small pack of
|
||||||
|
userscripts.
|
||||||
|
- [instapaper.zsh](https://github.com/dmcgrady/instapaper.zsh): Add URL to
|
||||||
|
your [Instapaper][] bookmark manager.
|
||||||
|
- [qtb.us](https://github.com/Chinggis6/qtb.us): small pack of userscripts.
|
||||||
|
- [pinboard.zsh](https://github.com/dmix/pinboard.zsh): Add URL to your
|
||||||
|
[Pinboard][] bookmark manager.
|
||||||
|
|
||||||
|
[Zotero]: https://www.zotero.org/
|
||||||
|
[Pocket]: https://getpocket.com/
|
||||||
|
[Instapaper]: https://www.instapaper.com/
|
||||||
|
[Pinboard]: https://pinboard.in/
|
||||||
|
|
@ -25,9 +25,17 @@ demonstration can be seen here: https://i.imgur.com/KN3XuZP.gif.
|
|||||||
USAGE = """The domain of the site has to appear as a segment in the pass path, for example: "github.com/cryzed" or
|
USAGE = """The domain of the site has to appear as a segment in the pass path, for example: "github.com/cryzed" or
|
||||||
"websites/github.com". How the username and password are determined is freely configurable using the CLI arguments. The
|
"websites/github.com". How the username and password are determined is freely configurable using the CLI arguments. The
|
||||||
login information is inserted by emulating key events using qutebrowser's fake-key command in this manner:
|
login information is inserted by emulating key events using qutebrowser's fake-key command in this manner:
|
||||||
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms."""
|
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms.
|
||||||
|
|
||||||
EPILOG = """Dependencies: tldextract (Python 3 module), pass.
|
Suggested bindings similar to Uzbl's `formfiller` script:
|
||||||
|
|
||||||
|
config.bind('<z><l>', 'spawn --userscript qute-pass')
|
||||||
|
config.bind('<z><u><l>', 'spawn --userscript qute-pass --username-only')
|
||||||
|
config.bind('<z><p><l>', 'spawn --userscript qute-pass --password-only')
|
||||||
|
config.bind('<z><o><l>', 'spawn --userscript qute-pass --otp-only')
|
||||||
|
"""
|
||||||
|
|
||||||
|
EPILOG = """Dependencies: tldextract (Python 3 module), pass, pass-otp (optional).
|
||||||
For issues and feedback please use: https://github.com/cryzed/qutebrowser-userscripts.
|
For issues and feedback please use: https://github.com/cryzed/qutebrowser-userscripts.
|
||||||
|
|
||||||
WARNING: The login details are viewable as plaintext in qutebrowser's debug log (qute://log) and might be shared if
|
WARNING: The login details are viewable as plaintext in qutebrowser's debug log (qute://log) and might be shared if
|
||||||
@ -66,6 +74,7 @@ argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
|
|||||||
group = argument_parser.add_mutually_exclusive_group()
|
group = argument_parser.add_mutually_exclusive_group()
|
||||||
group.add_argument('--username-only', '-e', action='store_true', help='Only insert username')
|
group.add_argument('--username-only', '-e', action='store_true', help='Only insert username')
|
||||||
group.add_argument('--password-only', '-w', action='store_true', help='Only insert password')
|
group.add_argument('--password-only', '-w', action='store_true', help='Only insert password')
|
||||||
|
group.add_argument('--otp-only', '-o', action='store_true', help='Only insert OTP code')
|
||||||
|
|
||||||
stderr = functools.partial(print, file=sys.stderr)
|
stderr = functools.partial(print, file=sys.stderr)
|
||||||
|
|
||||||
@ -87,7 +96,7 @@ def qute_command(command):
|
|||||||
|
|
||||||
def find_pass_candidates(domain, password_store_path):
|
def find_pass_candidates(domain, password_store_path):
|
||||||
candidates = []
|
candidates = []
|
||||||
for path, directories, file_names in os.walk(password_store_path):
|
for path, directories, file_names in os.walk(password_store_path, followlinks=True):
|
||||||
if directories or domain not in path.split(os.path.sep):
|
if directories or domain not in path.split(os.path.sep):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -98,11 +107,19 @@ def find_pass_candidates(domain, password_store_path):
|
|||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
|
|
||||||
def pass_(path, encoding):
|
def _run_pass(command, encoding):
|
||||||
process = subprocess.run(['pass', path], stdout=subprocess.PIPE)
|
process = subprocess.run(command, stdout=subprocess.PIPE)
|
||||||
return process.stdout.decode(encoding).strip()
|
return process.stdout.decode(encoding).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def pass_(path, encoding):
|
||||||
|
return _run_pass(['pass', path], encoding)
|
||||||
|
|
||||||
|
|
||||||
|
def pass_otp(path, encoding):
|
||||||
|
return _run_pass(['pass', 'otp', path], encoding)
|
||||||
|
|
||||||
|
|
||||||
def dmenu(items, invocation, encoding):
|
def dmenu(items, invocation, encoding):
|
||||||
command = shlex.split(invocation)
|
command = shlex.split(invocation)
|
||||||
process = subprocess.run(command, input='\n'.join(items).encode(encoding), stdout=subprocess.PIPE)
|
process = subprocess.run(command, input='\n'.join(items).encode(encoding), stdout=subprocess.PIPE)
|
||||||
@ -152,7 +169,7 @@ def main(arguments):
|
|||||||
|
|
||||||
# Match username
|
# Match username
|
||||||
target = selection if arguments.username_target == 'path' else secret
|
target = selection if arguments.username_target == 'path' else secret
|
||||||
match = re.match(arguments.username_pattern, target)
|
match = re.search(arguments.username_pattern, target, re.MULTILINE)
|
||||||
if not match:
|
if not match:
|
||||||
stderr('Failed to match username pattern on {}!'.format(arguments.username_target))
|
stderr('Failed to match username pattern on {}!'.format(arguments.username_target))
|
||||||
return ExitCodes.COULD_NOT_MATCH_USERNAME
|
return ExitCodes.COULD_NOT_MATCH_USERNAME
|
||||||
@ -169,6 +186,9 @@ def main(arguments):
|
|||||||
fake_key_raw(username)
|
fake_key_raw(username)
|
||||||
elif arguments.password_only:
|
elif arguments.password_only:
|
||||||
fake_key_raw(password)
|
fake_key_raw(password)
|
||||||
|
elif arguments.otp_only:
|
||||||
|
otp = pass_otp(selection, arguments.io_encoding)
|
||||||
|
fake_key_raw(otp)
|
||||||
else:
|
else:
|
||||||
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
|
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
|
||||||
# back into insert-mode, so the form can be directly submitted by hitting enter afterwards
|
# back into insert-mode, so the form can be directly submitted by hitting enter afterwards
|
||||||
|
@ -63,4 +63,8 @@ qt_log_ignore =
|
|||||||
^inotify_add_watch\(".*"\) failed: "No space left on device"
|
^inotify_add_watch\(".*"\) failed: "No space left on device"
|
||||||
^QSettings::value: Empty key passed
|
^QSettings::value: Empty key passed
|
||||||
^Icon theme ".*" not found
|
^Icon theme ".*" not found
|
||||||
|
^Error receiving trust for a CA certificate
|
||||||
xfail_strict = true
|
xfail_strict = true
|
||||||
|
filterwarnings =
|
||||||
|
# This happens in many qutebrowser dependencies...
|
||||||
|
ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning
|
||||||
|
@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
|
|||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
__maintainer__ = __author__
|
__maintainer__ = __author__
|
||||||
__email__ = "mail@qutebrowser.org"
|
__email__ = "mail@qutebrowser.org"
|
||||||
__version_info__ = (1, 3, 2)
|
__version_info__ = (1, 5, 1)
|
||||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||||
|
|
||||||
|
@ -59,7 +59,6 @@ except ImportError:
|
|||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
import qutebrowser.resources
|
import qutebrowser.resources
|
||||||
from qutebrowser.completion import completiondelegate
|
|
||||||
from qutebrowser.completion.models import miscmodels
|
from qutebrowser.completion.models import miscmodels
|
||||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||||
from qutebrowser.config import config, websettings, configfiles, configinit
|
from qutebrowser.config import config, websettings, configfiles, configinit
|
||||||
@ -72,9 +71,9 @@ from qutebrowser.keyinput import macros
|
|||||||
from qutebrowser.mainwindow import mainwindow, prompt
|
from qutebrowser.mainwindow import mainwindow, prompt
|
||||||
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
|
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
|
||||||
crashsignal, earlyinit, sql, cmdhistory,
|
crashsignal, earlyinit, sql, cmdhistory,
|
||||||
backendproblem)
|
backendproblem, objects)
|
||||||
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
|
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
|
||||||
usertypes, standarddir, error)
|
usertypes, standarddir, error, qtutils)
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
# We import those to run the cmdutils.register decorators.
|
# We import those to run the cmdutils.register decorators.
|
||||||
from qutebrowser.mainwindow.statusbar import command
|
from qutebrowser.mainwindow.statusbar import command
|
||||||
@ -104,6 +103,7 @@ def run(args):
|
|||||||
qApp = Application(args)
|
qApp = Application(args)
|
||||||
qApp.setOrganizationName("qutebrowser")
|
qApp.setOrganizationName("qutebrowser")
|
||||||
qApp.setApplicationName("qutebrowser")
|
qApp.setApplicationName("qutebrowser")
|
||||||
|
qApp.setDesktopFileName("qutebrowser")
|
||||||
qApp.setApplicationVersion(qutebrowser.__version__)
|
qApp.setApplicationVersion(qutebrowser.__version__)
|
||||||
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
|
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
|
||||||
|
|
||||||
@ -129,6 +129,9 @@ def run(args):
|
|||||||
sys.exit(usertypes.Exit.err_ipc)
|
sys.exit(usertypes.Exit.err_ipc)
|
||||||
|
|
||||||
if server is None:
|
if server is None:
|
||||||
|
if args.backend is not None:
|
||||||
|
log.init.warning(
|
||||||
|
"Backend from the running instance will be used")
|
||||||
sys.exit(usertypes.Exit.ok)
|
sys.exit(usertypes.Exit.ok)
|
||||||
else:
|
else:
|
||||||
server.got_args.connect(lambda args, target_arg, cwd:
|
server.got_args.connect(lambda args, target_arg, cwd:
|
||||||
@ -181,8 +184,6 @@ def init(args, crash_handler):
|
|||||||
QDesktopServices.setUrlHandler('https', open_desktopservices_url)
|
QDesktopServices.setUrlHandler('https', open_desktopservices_url)
|
||||||
QDesktopServices.setUrlHandler('qute', open_desktopservices_url)
|
QDesktopServices.setUrlHandler('qute', open_desktopservices_url)
|
||||||
|
|
||||||
objreg.get('web-history').import_txt()
|
|
||||||
|
|
||||||
log.init.debug("Init done!")
|
log.init.debug("Init done!")
|
||||||
crash_handler.raise_crashdlg()
|
crash_handler.raise_crashdlg()
|
||||||
|
|
||||||
@ -349,10 +350,6 @@ def _open_startpage(win_id=None):
|
|||||||
def _open_special_pages(args):
|
def _open_special_pages(args):
|
||||||
"""Open special notification pages which are only shown once.
|
"""Open special notification pages which are only shown once.
|
||||||
|
|
||||||
Currently this is:
|
|
||||||
- Quickstart page if it's the first start.
|
|
||||||
- Legacy QtWebKit warning if needed.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
args: The argparse namespace.
|
args: The argparse namespace.
|
||||||
"""
|
"""
|
||||||
@ -364,25 +361,30 @@ def _open_special_pages(args):
|
|||||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||||
window='last-focused')
|
window='last-focused')
|
||||||
|
|
||||||
# Quickstart page
|
pages = [
|
||||||
|
# state, condition, URL
|
||||||
|
('quickstart-done',
|
||||||
|
True,
|
||||||
|
'https://www.qutebrowser.org/quickstart.html'),
|
||||||
|
|
||||||
quickstart_done = general_sect.get('quickstart-done') == '1'
|
('config-migration-shown',
|
||||||
|
os.path.exists(os.path.join(standarddir.config(),
|
||||||
|
'qutebrowser.conf')),
|
||||||
|
'qute://help/configuring.html'),
|
||||||
|
|
||||||
if not quickstart_done:
|
('webkit-warning-shown',
|
||||||
tabbed_browser.tabopen(
|
objects.backend == usertypes.Backend.QtWebKit,
|
||||||
QUrl('https://www.qutebrowser.org/quickstart.html'))
|
'qute://warning/webkit'),
|
||||||
general_sect['quickstart-done'] = '1'
|
|
||||||
|
|
||||||
# Setting migration page
|
('old-qt-warning-shown',
|
||||||
|
not qtutils.version_check('5.9'),
|
||||||
|
'qute://warning/old-qt'),
|
||||||
|
]
|
||||||
|
|
||||||
needs_migration = os.path.exists(
|
for state, condition, url in pages:
|
||||||
os.path.join(standarddir.config(), 'qutebrowser.conf'))
|
if general_sect.get(state) != '1' and condition:
|
||||||
migration_shown = general_sect.get('config-migration-shown') == '1'
|
tabbed_browser.tabopen(QUrl(url), background=False)
|
||||||
|
general_sect[state] = '1'
|
||||||
if needs_migration and not migration_shown:
|
|
||||||
tabbed_browser.tabopen(QUrl('qute://help/configuring.html'),
|
|
||||||
background=False)
|
|
||||||
general_sect['config-migration-shown'] = '1'
|
|
||||||
|
|
||||||
|
|
||||||
def on_focus_changed(_old, new):
|
def on_focus_changed(_old, new):
|
||||||
@ -445,16 +447,10 @@ def _init_modules(args, crash_handler):
|
|||||||
|
|
||||||
log.init.debug("Initializing web history...")
|
log.init.debug("Initializing web history...")
|
||||||
history.init(qApp)
|
history.init(qApp)
|
||||||
except sql.SqlError as e:
|
except sql.SqlEnvironmentError as e:
|
||||||
if e.environmental:
|
error.handle_fatal_exc(e, args, 'Error initializing SQL',
|
||||||
error.handle_fatal_exc(e, args, 'Error initializing SQL',
|
pre_text='Error initializing SQL')
|
||||||
pre_text='Error initializing SQL')
|
sys.exit(usertypes.Exit.err_init)
|
||||||
sys.exit(usertypes.Exit.err_init)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
log.init.debug("Initializing completion...")
|
|
||||||
completiondelegate.init()
|
|
||||||
|
|
||||||
log.init.debug("Initializing command history...")
|
log.init.debug("Initializing command history...")
|
||||||
cmdhistory.init()
|
cmdhistory.init()
|
||||||
|
@ -24,7 +24,6 @@ import os.path
|
|||||||
import functools
|
import functools
|
||||||
import posixpath
|
import posixpath
|
||||||
import zipfile
|
import zipfile
|
||||||
import fnmatch
|
|
||||||
|
|
||||||
from qutebrowser.browser import downloads
|
from qutebrowser.browser import downloads
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
@ -32,7 +31,7 @@ from qutebrowser.utils import objreg, standarddir, log, message
|
|||||||
from qutebrowser.commands import cmdutils
|
from qutebrowser.commands import cmdutils
|
||||||
|
|
||||||
|
|
||||||
def guess_zip_filename(zf):
|
def _guess_zip_filename(zf):
|
||||||
"""Guess which file to use inside a zip file.
|
"""Guess which file to use inside a zip file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -54,26 +53,26 @@ def get_fileobj(byte_io):
|
|||||||
if zipfile.is_zipfile(byte_io):
|
if zipfile.is_zipfile(byte_io):
|
||||||
byte_io.seek(0) # rewind what zipfile.is_zipfile did
|
byte_io.seek(0) # rewind what zipfile.is_zipfile did
|
||||||
zf = zipfile.ZipFile(byte_io)
|
zf = zipfile.ZipFile(byte_io)
|
||||||
filename = guess_zip_filename(zf)
|
filename = _guess_zip_filename(zf)
|
||||||
byte_io = zf.open(filename, mode='r')
|
byte_io = zf.open(filename, mode='r')
|
||||||
else:
|
else:
|
||||||
byte_io.seek(0) # rewind what zipfile.is_zipfile did
|
byte_io.seek(0) # rewind what zipfile.is_zipfile did
|
||||||
return byte_io
|
return byte_io
|
||||||
|
|
||||||
|
|
||||||
def is_whitelisted_host(host):
|
def _is_whitelisted_url(url):
|
||||||
"""Check if the given host is on the adblock whitelist.
|
"""Check if the given URL is on the adblock whitelist.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
host: The host of the request as string.
|
url: The URL to check as QUrl.
|
||||||
"""
|
"""
|
||||||
for pattern in config.val.content.host_blocking.whitelist:
|
for pattern in config.val.content.host_blocking.whitelist:
|
||||||
if fnmatch.fnmatch(host, pattern.lower()):
|
if pattern.matches(url):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class FakeDownload:
|
class _FakeDownload:
|
||||||
|
|
||||||
"""A download stub to use on_download_finished with local files."""
|
"""A download stub to use on_download_finished with local files."""
|
||||||
|
|
||||||
@ -111,14 +110,18 @@ class HostBlocker:
|
|||||||
|
|
||||||
config.instance.changed.connect(self._update_files)
|
config.instance.changed.connect(self._update_files)
|
||||||
|
|
||||||
def is_blocked(self, url):
|
def is_blocked(self, url, first_party_url=None):
|
||||||
"""Check if the given URL (as QUrl) is blocked."""
|
"""Check if the given URL (as QUrl) is blocked."""
|
||||||
if not config.val.content.host_blocking.enabled:
|
if first_party_url is not None and not first_party_url.isValid():
|
||||||
|
first_party_url = None
|
||||||
|
if not config.instance.get('content.host_blocking.enabled',
|
||||||
|
url=first_party_url):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
host = url.host()
|
host = url.host()
|
||||||
return ((host in self._blocked_hosts or
|
return ((host in self._blocked_hosts or
|
||||||
host in self._config_blocked_hosts) and
|
host in self._config_blocked_hosts) and
|
||||||
not is_whitelisted_host(host))
|
not _is_whitelisted_url(url))
|
||||||
|
|
||||||
def _read_hosts_file(self, filename, target):
|
def _read_hosts_file(self, filename, target):
|
||||||
"""Read hosts from the given filename.
|
"""Read hosts from the given filename.
|
||||||
@ -174,15 +177,12 @@ class HostBlocker:
|
|||||||
for url in config.val.content.host_blocking.lists:
|
for url in config.val.content.host_blocking.lists:
|
||||||
if url.scheme() == 'file':
|
if url.scheme() == 'file':
|
||||||
filename = url.toLocalFile()
|
filename = url.toLocalFile()
|
||||||
try:
|
if os.path.isdir(filename):
|
||||||
fileobj = open(filename, 'rb')
|
for entry in os.scandir(filename):
|
||||||
except OSError as e:
|
if entry.is_file():
|
||||||
message.error("adblock: Error while reading {}: {}".format(
|
self._import_local(entry.path)
|
||||||
filename, e.strerror))
|
else:
|
||||||
continue
|
self._import_local(filename)
|
||||||
download = FakeDownload(fileobj)
|
|
||||||
self._in_progress.append(download)
|
|
||||||
self.on_download_finished(download)
|
|
||||||
else:
|
else:
|
||||||
fobj = io.BytesIO()
|
fobj = io.BytesIO()
|
||||||
fobj.name = 'adblock: ' + url.host()
|
fobj.name = 'adblock: ' + url.host()
|
||||||
@ -191,7 +191,23 @@ class HostBlocker:
|
|||||||
auto_remove=True)
|
auto_remove=True)
|
||||||
self._in_progress.append(download)
|
self._in_progress.append(download)
|
||||||
download.finished.connect(
|
download.finished.connect(
|
||||||
functools.partial(self.on_download_finished, download))
|
functools.partial(self._on_download_finished, download))
|
||||||
|
|
||||||
|
def _import_local(self, filename):
|
||||||
|
"""Adds the contents of a file to the blocklist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: path to a local file to import.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
fileobj = open(filename, 'rb')
|
||||||
|
except OSError as e:
|
||||||
|
message.error("adblock: Error while reading {}: {}".format(
|
||||||
|
filename, e.strerror))
|
||||||
|
return
|
||||||
|
download = _FakeDownload(fileobj)
|
||||||
|
self._in_progress.append(download)
|
||||||
|
self._on_download_finished(download)
|
||||||
|
|
||||||
def _parse_line(self, line):
|
def _parse_line(self, line):
|
||||||
"""Parse a line from a host file.
|
"""Parse a line from a host file.
|
||||||
@ -234,7 +250,9 @@ class HostBlocker:
|
|||||||
hosts = parts[1:]
|
hosts = parts[1:]
|
||||||
|
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
if '.' in host and not host.endswith('.localdomain'):
|
if ('.' in host and
|
||||||
|
not host.endswith('.localdomain') and
|
||||||
|
host != '0.0.0.0'):
|
||||||
self._blocked_hosts.add(host)
|
self._blocked_hosts.add(host)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -269,7 +287,7 @@ class HostBlocker:
|
|||||||
message.error("adblock: {} read errors for {}".format(
|
message.error("adblock: {} read errors for {}".format(
|
||||||
error_count, byte_io.name))
|
error_count, byte_io.name))
|
||||||
|
|
||||||
def on_lists_downloaded(self):
|
def _on_lists_downloaded(self):
|
||||||
"""Install block lists after files have been downloaded."""
|
"""Install block lists after files have been downloaded."""
|
||||||
with open(self._local_hosts_file, 'w', encoding='utf-8') as f:
|
with open(self._local_hosts_file, 'w', encoding='utf-8') as f:
|
||||||
for host in sorted(self._blocked_hosts):
|
for host in sorted(self._blocked_hosts):
|
||||||
@ -288,7 +306,7 @@ class HostBlocker:
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.misc.exception("Failed to delete hosts file: {}".format(e))
|
log.misc.exception("Failed to delete hosts file: {}".format(e))
|
||||||
|
|
||||||
def on_download_finished(self, download):
|
def _on_download_finished(self, download):
|
||||||
"""Check if all downloads are finished and if so, trigger reading.
|
"""Check if all downloads are finished and if so, trigger reading.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@ -303,6 +321,6 @@ class HostBlocker:
|
|||||||
download.fileobj.close()
|
download.fileobj.close()
|
||||||
if not self._in_progress:
|
if not self._in_progress:
|
||||||
try:
|
try:
|
||||||
self.on_lists_downloaded()
|
self._on_lists_downloaded()
|
||||||
except OSError:
|
except OSError:
|
||||||
log.misc.exception("Failed to write host block list!")
|
log.misc.exception("Failed to write host block list!")
|
||||||
|
@ -22,11 +22,11 @@
|
|||||||
import enum
|
import enum
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
import sip
|
|
||||||
import attr
|
import attr
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
|
||||||
from PyQt5.QtGui import QIcon
|
from PyQt5.QtGui import QIcon
|
||||||
from PyQt5.QtWidgets import QWidget, QApplication
|
from PyQt5.QtWidgets import QWidget, QApplication, QDialog
|
||||||
|
from PyQt5.QtPrintSupport import QPrintDialog
|
||||||
|
|
||||||
import pygments
|
import pygments
|
||||||
import pygments.lexers
|
import pygments.lexers
|
||||||
@ -38,6 +38,7 @@ from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
|
|||||||
urlutils, message)
|
urlutils, message)
|
||||||
from qutebrowser.misc import miscwidgets, objects
|
from qutebrowser.misc import miscwidgets, objects
|
||||||
from qutebrowser.browser import mouse, hints
|
from qutebrowser.browser import mouse, hints
|
||||||
|
from qutebrowser.qt import sip
|
||||||
|
|
||||||
|
|
||||||
tab_id_gen = itertools.count(0)
|
tab_id_gen = itertools.count(0)
|
||||||
@ -187,8 +188,9 @@ class AbstractPrinting:
|
|||||||
|
|
||||||
"""Attribute of AbstractTab for printing the page."""
|
"""Attribute of AbstractTab for printing the page."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, tab):
|
||||||
self._widget = None
|
self._widget = None
|
||||||
|
self._tab = tab
|
||||||
|
|
||||||
def check_pdf_support(self):
|
def check_pdf_support(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -212,6 +214,29 @@ class AbstractPrinting:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def show_dialog(self):
|
||||||
|
"""Print with a QPrintDialog."""
|
||||||
|
self.check_printer_support()
|
||||||
|
|
||||||
|
def print_callback(ok):
|
||||||
|
"""Called when printing finished."""
|
||||||
|
if not ok:
|
||||||
|
message.error("Printing failed!")
|
||||||
|
diag.deleteLater()
|
||||||
|
|
||||||
|
def do_print():
|
||||||
|
"""Called when the dialog was closed."""
|
||||||
|
self.to_printer(diag.printer(), print_callback)
|
||||||
|
|
||||||
|
diag = QPrintDialog(self._tab)
|
||||||
|
if utils.is_mac:
|
||||||
|
# For some reason we get a segfault when using open() on macOS
|
||||||
|
ret = diag.exec_()
|
||||||
|
if ret == QDialog.Accepted:
|
||||||
|
do_print()
|
||||||
|
else:
|
||||||
|
diag.open(do_print)
|
||||||
|
|
||||||
|
|
||||||
class AbstractSearch(QObject):
|
class AbstractSearch(QObject):
|
||||||
|
|
||||||
@ -223,10 +248,19 @@ class AbstractSearch(QObject):
|
|||||||
this view.
|
this view.
|
||||||
_flags: The flags of the last search (needs to be set by subclasses).
|
_flags: The flags of the last search (needs to be set by subclasses).
|
||||||
_widget: The underlying WebView widget.
|
_widget: The underlying WebView widget.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
finished: Emitted when a search was finished.
|
||||||
|
arg: True if the text was found, False otherwise.
|
||||||
|
cleared: Emitted when an existing search was cleared.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
finished = pyqtSignal(bool)
|
||||||
|
cleared = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, tab, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
self._tab = tab
|
||||||
self._widget = None
|
self._widget = None
|
||||||
self.text = None
|
self.text = None
|
||||||
self.search_displayed = False
|
self.search_displayed = False
|
||||||
@ -370,9 +404,11 @@ class AbstractCaret(QObject):
|
|||||||
Signals:
|
Signals:
|
||||||
selection_toggled: Emitted when the selection was toggled.
|
selection_toggled: Emitted when the selection was toggled.
|
||||||
arg: Whether the selection is now active.
|
arg: Whether the selection is now active.
|
||||||
|
follow_selected_done: Emitted when a follow_selection action is done.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
selection_toggled = pyqtSignal(bool)
|
selection_toggled = pyqtSignal(bool)
|
||||||
|
follow_selected_done = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, tab, mode_manager, parent=None):
|
def __init__(self, tab, mode_manager, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -596,6 +632,9 @@ class AbstractElements:
|
|||||||
def find_css(self, selector, callback, *, only_visible=False):
|
def find_css(self, selector, callback, *, only_visible=False):
|
||||||
"""Find all HTML elements matching a given selector async.
|
"""Find all HTML elements matching a given selector async.
|
||||||
|
|
||||||
|
If there's an error, the callback is called with a webelem.Error
|
||||||
|
instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
callback: The callback to be called when the search finished.
|
callback: The callback to be called when the search finished.
|
||||||
selector: The CSS selector to search for.
|
selector: The CSS selector to search for.
|
||||||
@ -641,20 +680,27 @@ class AbstractAudio(QObject):
|
|||||||
muted_changed = pyqtSignal(bool)
|
muted_changed = pyqtSignal(bool)
|
||||||
recently_audible_changed = pyqtSignal(bool)
|
recently_audible_changed = pyqtSignal(bool)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, tab, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._widget = None
|
self._widget = None
|
||||||
|
self._tab = tab
|
||||||
|
|
||||||
def set_muted(self, muted: bool):
|
def set_muted(self, muted: bool, override: bool = False):
|
||||||
"""Set this tab as muted or not."""
|
"""Set this tab as muted or not.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
override: If set to True, muting/unmuting was done manually and
|
||||||
|
overrides future automatic mute/unmute changes based on
|
||||||
|
the URL.
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def is_muted(self):
|
def is_muted(self):
|
||||||
"""Whether this tab is muted."""
|
"""Whether this tab is muted."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def toggle_muted(self):
|
def toggle_muted(self, *, override: bool = False):
|
||||||
self.set_muted(not self.is_muted())
|
self.set_muted(not self.is_muted(), override=override)
|
||||||
|
|
||||||
def is_recently_audible(self):
|
def is_recently_audible(self):
|
||||||
"""Whether this tab has had audio playing recently."""
|
"""Whether this tab has had audio playing recently."""
|
||||||
@ -829,12 +875,20 @@ class AbstractTab(QWidget):
|
|||||||
navigation.navigation_type,
|
navigation.navigation_type,
|
||||||
navigation.is_main_frame))
|
navigation.is_main_frame))
|
||||||
|
|
||||||
if (navigation.navigation_type == navigation.Type.link_clicked and
|
if not navigation.url.isValid():
|
||||||
not navigation.url.isValid()):
|
# Also a WORKAROUND for missing IDNA 2008 support in QUrl, see
|
||||||
msg = urlutils.get_errstring(navigation.url,
|
# https://bugreports.qt.io/browse/QTBUG-60364
|
||||||
"Invalid link clicked")
|
|
||||||
message.error(msg)
|
if navigation.navigation_type == navigation.Type.link_clicked:
|
||||||
self.data.open_target = usertypes.ClickTarget.normal
|
msg = urlutils.get_errstring(navigation.url,
|
||||||
|
"Invalid link clicked")
|
||||||
|
message.error(msg)
|
||||||
|
self.data.open_target = usertypes.ClickTarget.normal
|
||||||
|
|
||||||
|
log.webview.debug("Ignoring invalid URL {} in "
|
||||||
|
"acceptNavigationRequest: {}".format(
|
||||||
|
navigation.url.toDisplayString(),
|
||||||
|
navigation.url.errorString()))
|
||||||
navigation.accepted = False
|
navigation.accepted = False
|
||||||
|
|
||||||
def handle_auto_insert_mode(self, ok):
|
def handle_auto_insert_mode(self, ok):
|
||||||
@ -893,10 +947,6 @@ class AbstractTab(QWidget):
|
|||||||
self._progress = perc
|
self._progress = perc
|
||||||
self.load_progress.emit(perc)
|
self.load_progress.emit(perc)
|
||||||
|
|
||||||
@pyqtSlot()
|
|
||||||
def _on_ssl_errors(self):
|
|
||||||
self._has_ssl_errors = True
|
|
||||||
|
|
||||||
def url(self, requested=False):
|
def url(self, requested=False):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -25,9 +25,9 @@ import shlex
|
|||||||
import functools
|
import functools
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
|
from PyQt5.QtWidgets import QApplication, QTabBar
|
||||||
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QEvent, QUrlQuery
|
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QEvent, QUrlQuery
|
||||||
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
|
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||||
|
|
||||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||||
from qutebrowser.config import config, configdata
|
from qutebrowser.config import config, configdata
|
||||||
@ -66,6 +66,11 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
def _new_tabbed_browser(self, private):
|
def _new_tabbed_browser(self, private):
|
||||||
"""Get a tabbed-browser from a new window."""
|
"""Get a tabbed-browser from a new window."""
|
||||||
|
args = QApplication.instance().arguments()
|
||||||
|
if private and '--single-process' in args:
|
||||||
|
raise cmdexc.CommandError("Private windows are unavailable with "
|
||||||
|
"the single-process process model.")
|
||||||
|
|
||||||
new_window = mainwindow.MainWindow(private=private)
|
new_window = mainwindow.MainWindow(private=private)
|
||||||
new_window.show()
|
new_window.show()
|
||||||
return new_window.tabbed_browser
|
return new_window.tabbed_browser
|
||||||
@ -415,27 +420,6 @@ class CommandDispatcher:
|
|||||||
tab.printing.to_pdf(filename)
|
tab.printing.to_pdf(filename)
|
||||||
log.misc.debug("Print to file: {}".format(filename))
|
log.misc.debug("Print to file: {}".format(filename))
|
||||||
|
|
||||||
def _print(self, tab):
|
|
||||||
"""Print with a QPrintDialog."""
|
|
||||||
def print_callback(ok):
|
|
||||||
"""Called when printing finished."""
|
|
||||||
if not ok:
|
|
||||||
message.error("Printing failed!")
|
|
||||||
diag.deleteLater()
|
|
||||||
|
|
||||||
def do_print():
|
|
||||||
"""Called when the dialog was closed."""
|
|
||||||
tab.printing.to_printer(diag.printer(), print_callback)
|
|
||||||
|
|
||||||
diag = QPrintDialog(tab)
|
|
||||||
if utils.is_mac:
|
|
||||||
# For some reason we get a segfault when using open() on macOS
|
|
||||||
ret = diag.exec_()
|
|
||||||
if ret == QDialog.Accepted:
|
|
||||||
do_print()
|
|
||||||
else:
|
|
||||||
diag.open(do_print)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', name='print',
|
@cmdutils.register(instance='command-dispatcher', name='print',
|
||||||
scope='window')
|
scope='window')
|
||||||
@cmdutils.argument('count', count=True)
|
@cmdutils.argument('count', count=True)
|
||||||
@ -453,22 +437,15 @@ class CommandDispatcher:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if pdf:
|
|
||||||
tab.printing.check_pdf_support()
|
|
||||||
else:
|
|
||||||
tab.printing.check_printer_support()
|
|
||||||
if preview:
|
if preview:
|
||||||
tab.printing.check_preview_support()
|
self._print_preview(tab)
|
||||||
|
elif pdf:
|
||||||
|
self._print_pdf(tab, pdf)
|
||||||
|
else:
|
||||||
|
tab.printing.show_dialog()
|
||||||
except browsertab.WebTabError as e:
|
except browsertab.WebTabError as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
|
|
||||||
if preview:
|
|
||||||
self._print_preview(tab)
|
|
||||||
elif pdf:
|
|
||||||
self._print_pdf(tab, pdf)
|
|
||||||
else:
|
|
||||||
self._print(tab)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
def tab_clone(self, bg=False, window=False):
|
def tab_clone(self, bg=False, window=False):
|
||||||
"""Duplicate the current tab.
|
"""Duplicate the current tab.
|
||||||
@ -513,14 +490,16 @@ class CommandDispatcher:
|
|||||||
new_tabbed_browser.widget.set_tab_pinned(newtab, curtab.data.pinned)
|
new_tabbed_browser.widget.set_tab_pinned(newtab, curtab.data.pinned)
|
||||||
return newtab
|
return newtab
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||||
|
maxsplit=0)
|
||||||
@cmdutils.argument('index', completion=miscmodels.other_buffer)
|
@cmdutils.argument('index', completion=miscmodels.other_buffer)
|
||||||
def tab_take(self, index):
|
def tab_take(self, index, keep=False):
|
||||||
"""Take a tab from another window.
|
"""Take a tab from another window.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
index: The [win_id/]index of the tab to take. Or a substring
|
index: The [win_id/]index of the tab to take. Or a substring
|
||||||
in which case the closest match will be taken.
|
in which case the closest match will be taken.
|
||||||
|
keep: If given, keep the old tab around.
|
||||||
"""
|
"""
|
||||||
tabbed_browser, tab = self._resolve_buffer_index(index)
|
tabbed_browser, tab = self._resolve_buffer_index(index)
|
||||||
|
|
||||||
@ -528,18 +507,20 @@ class CommandDispatcher:
|
|||||||
raise cmdexc.CommandError("Can't take a tab from the same window")
|
raise cmdexc.CommandError("Can't take a tab from the same window")
|
||||||
|
|
||||||
self._open(tab.url(), tab=True)
|
self._open(tab.url(), tab=True)
|
||||||
tabbed_browser.close_tab(tab, add_undo=False)
|
if not keep:
|
||||||
|
tabbed_browser.close_tab(tab, add_undo=False)
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
@cmdutils.argument('win_id', completion=miscmodels.window)
|
@cmdutils.argument('win_id', completion=miscmodels.window)
|
||||||
@cmdutils.argument('count', count=True)
|
@cmdutils.argument('count', count=True)
|
||||||
def tab_give(self, win_id: int = None, count=None):
|
def tab_give(self, win_id: int = None, keep=False, count=None):
|
||||||
"""Give the current tab to a new or existing window if win_id given.
|
"""Give the current tab to a new or existing window if win_id given.
|
||||||
|
|
||||||
If no win_id is given, the tab will get detached into a new window.
|
If no win_id is given, the tab will get detached into a new window.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
win_id: The window ID of the window to give the current tab to.
|
win_id: The window ID of the window to give the current tab to.
|
||||||
|
keep: If given, keep the old tab around.
|
||||||
count: Overrides win_id (index starts at 1 for win_id=0).
|
count: Overrides win_id (index starts at 1 for win_id=0).
|
||||||
"""
|
"""
|
||||||
if count is not None:
|
if count is not None:
|
||||||
@ -549,7 +530,7 @@ class CommandDispatcher:
|
|||||||
raise cmdexc.CommandError("Can't give a tab to the same window")
|
raise cmdexc.CommandError("Can't give a tab to the same window")
|
||||||
|
|
||||||
if win_id is None:
|
if win_id is None:
|
||||||
if self._count() < 2:
|
if self._count() < 2 and not keep:
|
||||||
raise cmdexc.CommandError("Cannot detach from a window with "
|
raise cmdexc.CommandError("Cannot detach from a window with "
|
||||||
"only one tab")
|
"only one tab")
|
||||||
|
|
||||||
@ -564,7 +545,9 @@ class CommandDispatcher:
|
|||||||
window=win_id)
|
window=win_id)
|
||||||
|
|
||||||
tabbed_browser.tabopen(self._current_url())
|
tabbed_browser.tabopen(self._current_url())
|
||||||
self._tabbed_browser.close_tab(self._current_widget(), add_undo=False)
|
if not keep:
|
||||||
|
self._tabbed_browser.close_tab(self._current_widget(),
|
||||||
|
add_undo=False)
|
||||||
|
|
||||||
def _back_forward(self, tab, bg, window, count, forward):
|
def _back_forward(self, tab, bg, window, count, forward):
|
||||||
"""Helper function for :back/:forward."""
|
"""Helper function for :back/:forward."""
|
||||||
@ -634,11 +617,11 @@ class CommandDispatcher:
|
|||||||
- `up`: Go up a level in the current URL.
|
- `up`: Go up a level in the current URL.
|
||||||
- `increment`: Increment the last number in the URL.
|
- `increment`: Increment the last number in the URL.
|
||||||
Uses the
|
Uses the
|
||||||
link:settings.html#url.incdec_segments[url.incdec_segments]
|
link:settings{outsuffix}#url.incdec_segments[url.incdec_segments]
|
||||||
config option.
|
config option.
|
||||||
- `decrement`: Decrement the last number in the URL.
|
- `decrement`: Decrement the last number in the URL.
|
||||||
Uses the
|
Uses the
|
||||||
link:settings.html#url.incdec_segments[url.incdec_segments]
|
link:settings{outsuffix}#url.incdec_segments[url.incdec_segments]
|
||||||
config option.
|
config option.
|
||||||
|
|
||||||
tab: Open in a new tab.
|
tab: Open in a new tab.
|
||||||
@ -652,7 +635,8 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
cmdutils.check_exclusive((tab, bg, window), 'tbw')
|
cmdutils.check_exclusive((tab, bg, window), 'tbw')
|
||||||
widget = self._current_widget()
|
widget = self._current_widget()
|
||||||
url = self._current_url().adjusted(QUrl.RemoveFragment)
|
url = self._current_url()
|
||||||
|
url = url.adjusted(QUrl.RemoveFragment | QUrl.RemoveQuery)
|
||||||
|
|
||||||
handlers = {
|
handlers = {
|
||||||
'prev': functools.partial(navigate.prevnext, prev=True),
|
'prev': functools.partial(navigate.prevnext, prev=True),
|
||||||
@ -833,7 +817,7 @@ class CommandDispatcher:
|
|||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
@cmdutils.argument('what', choices=['selection', 'url', 'pretty-url',
|
@cmdutils.argument('what', choices=['selection', 'url', 'pretty-url',
|
||||||
'title', 'domain'])
|
'title', 'domain'])
|
||||||
def yank(self, what='url', sel=False, keep=False):
|
def yank(self, what='url', sel=False, keep=False, quiet=False):
|
||||||
"""Yank something to the clipboard or primary selection.
|
"""Yank something to the clipboard or primary selection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -847,6 +831,7 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
sel: Use the primary selection instead of the clipboard.
|
sel: Use the primary selection instead of the clipboard.
|
||||||
keep: Stay in visual mode after yanking the selection.
|
keep: Stay in visual mode after yanking the selection.
|
||||||
|
quiet: Don't show an information message.
|
||||||
"""
|
"""
|
||||||
if what == 'title':
|
if what == 'title':
|
||||||
s = self._tabbed_browser.widget.page_title(self._current_index())
|
s = self._tabbed_browser.widget.page_title(self._current_index())
|
||||||
@ -860,10 +845,10 @@ class CommandDispatcher:
|
|||||||
what = 'URL' # For printing
|
what = 'URL' # For printing
|
||||||
elif what == 'selection':
|
elif what == 'selection':
|
||||||
def _selection_callback(s):
|
def _selection_callback(s):
|
||||||
if not s:
|
if not s and not quiet:
|
||||||
message.info("Nothing to yank")
|
message.info("Nothing to yank")
|
||||||
return
|
return
|
||||||
self._yank_to_target(s, sel, what, keep)
|
self._yank_to_target(s, sel, what, keep, quiet)
|
||||||
|
|
||||||
caret = self._current_widget().caret
|
caret = self._current_widget().caret
|
||||||
caret.selection(callback=_selection_callback)
|
caret.selection(callback=_selection_callback)
|
||||||
@ -871,9 +856,9 @@ class CommandDispatcher:
|
|||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
raise ValueError("Invalid value {!r} for `what'.".format(what))
|
raise ValueError("Invalid value {!r} for `what'.".format(what))
|
||||||
|
|
||||||
self._yank_to_target(s, sel, what, keep)
|
self._yank_to_target(s, sel, what, keep, quiet)
|
||||||
|
|
||||||
def _yank_to_target(self, s, sel, what, keep):
|
def _yank_to_target(self, s, sel, what, keep, quiet):
|
||||||
if sel and utils.supports_selection():
|
if sel and utils.supports_selection():
|
||||||
target = "primary selection"
|
target = "primary selection"
|
||||||
else:
|
else:
|
||||||
@ -882,47 +867,53 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
utils.set_clipboard(s, selection=sel)
|
utils.set_clipboard(s, selection=sel)
|
||||||
if what != 'selection':
|
if what != 'selection':
|
||||||
message.info("Yanked {} to {}: {}".format(what, target, s))
|
if not quiet:
|
||||||
|
message.info("Yanked {} to {}: {}".format(what, target, s))
|
||||||
else:
|
else:
|
||||||
message.info("{} {} yanked to {}".format(
|
if not quiet:
|
||||||
len(s), "char" if len(s) == 1 else "chars", target))
|
message.info("{} {} yanked to {}".format(
|
||||||
|
len(s), "char" if len(s) == 1 else "chars", target))
|
||||||
if not keep:
|
if not keep:
|
||||||
modeman.leave(self._win_id, KeyMode.caret, "yank selected",
|
modeman.leave(self._win_id, KeyMode.caret, "yank selected",
|
||||||
maybe=True)
|
maybe=True)
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
@cmdutils.argument('count', count=True)
|
@cmdutils.argument('count', count=True)
|
||||||
def zoom_in(self, count=1):
|
def zoom_in(self, count=1, quiet=False):
|
||||||
"""Increase the zoom level for the current tab.
|
"""Increase the zoom level for the current tab.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
count: How many steps to zoom in.
|
count: How many steps to zoom in.
|
||||||
|
quiet: Don't show a zoom level message.
|
||||||
"""
|
"""
|
||||||
tab = self._current_widget()
|
tab = self._current_widget()
|
||||||
try:
|
try:
|
||||||
perc = tab.zoom.offset(count)
|
perc = tab.zoom.offset(count)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
if not quiet:
|
||||||
|
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
@cmdutils.argument('count', count=True)
|
@cmdutils.argument('count', count=True)
|
||||||
def zoom_out(self, count=1):
|
def zoom_out(self, count=1, quiet=False):
|
||||||
"""Decrease the zoom level for the current tab.
|
"""Decrease the zoom level for the current tab.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
count: How many steps to zoom out.
|
count: How many steps to zoom out.
|
||||||
|
quiet: Don't show a zoom level message.
|
||||||
"""
|
"""
|
||||||
tab = self._current_widget()
|
tab = self._current_widget()
|
||||||
try:
|
try:
|
||||||
perc = tab.zoom.offset(-count)
|
perc = tab.zoom.offset(-count)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
if not quiet:
|
||||||
|
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
@cmdutils.argument('count', count=True)
|
@cmdutils.argument('count', count=True)
|
||||||
def zoom(self, zoom=None, count=None):
|
def zoom(self, zoom=None, count=None, quiet=False):
|
||||||
"""Set the zoom level for the current tab.
|
"""Set the zoom level for the current tab.
|
||||||
|
|
||||||
The zoom can be given as argument or as [count]. If neither is
|
The zoom can be given as argument or as [count]. If neither is
|
||||||
@ -932,6 +923,7 @@ class CommandDispatcher:
|
|||||||
Args:
|
Args:
|
||||||
zoom: The zoom percentage to set.
|
zoom: The zoom percentage to set.
|
||||||
count: The zoom percentage to set.
|
count: The zoom percentage to set.
|
||||||
|
quiet: Don't show a zoom level message.
|
||||||
"""
|
"""
|
||||||
if zoom is not None:
|
if zoom is not None:
|
||||||
try:
|
try:
|
||||||
@ -949,7 +941,8 @@ class CommandDispatcher:
|
|||||||
tab.zoom.set_factor(float(level) / 100)
|
tab.zoom.set_factor(float(level) / 100)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise cmdexc.CommandError("Can't zoom {}%!".format(level))
|
raise cmdexc.CommandError("Can't zoom {}%!".format(level))
|
||||||
message.info("Zoom level: {}%".format(int(level)), replace=True)
|
if not quiet:
|
||||||
|
message.info("Zoom level: {}%".format(int(level)), replace=True)
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
def tab_only(self, prev=False, next_=False, force=False):
|
def tab_only(self, prev=False, next_=False, force=False):
|
||||||
@ -1138,9 +1131,6 @@ class CommandDispatcher:
|
|||||||
if index == 'last':
|
if index == 'last':
|
||||||
self._tab_focus_last()
|
self._tab_focus_last()
|
||||||
return
|
return
|
||||||
elif not no_last and index == self._current_index() + 1:
|
|
||||||
self._tab_focus_last(show_error=False)
|
|
||||||
return
|
|
||||||
elif index is None:
|
elif index is None:
|
||||||
self.tab_next()
|
self.tab_next()
|
||||||
return
|
return
|
||||||
@ -1148,6 +1138,10 @@ class CommandDispatcher:
|
|||||||
if index < 0:
|
if index < 0:
|
||||||
index = self._count() + index + 1
|
index = self._count() + index + 1
|
||||||
|
|
||||||
|
if not no_last and index == self._current_index() + 1:
|
||||||
|
self._tab_focus_last(show_error=False)
|
||||||
|
return
|
||||||
|
|
||||||
if 1 <= index <= self._count():
|
if 1 <= index <= self._count():
|
||||||
self._set_current_index(index - 1)
|
self._set_current_index(index - 1)
|
||||||
else:
|
else:
|
||||||
@ -1201,8 +1195,9 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||||
maxsplit=0, no_replace_variables=True)
|
maxsplit=0, no_replace_variables=True)
|
||||||
|
@cmdutils.argument('count', count=True)
|
||||||
def spawn(self, cmdline, userscript=False, verbose=False,
|
def spawn(self, cmdline, userscript=False, verbose=False,
|
||||||
output=False, detach=False):
|
output=False, detach=False, count=None):
|
||||||
"""Spawn a command in a shell.
|
"""Spawn a command in a shell.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1210,12 +1205,13 @@ class CommandDispatcher:
|
|||||||
absolute path, or store the userscript in one of those
|
absolute path, or store the userscript in one of those
|
||||||
locations:
|
locations:
|
||||||
- `~/.local/share/qutebrowser/userscripts`
|
- `~/.local/share/qutebrowser/userscripts`
|
||||||
(or `$XDG_DATA_DIR`)
|
(or `$XDG_DATA_HOME`)
|
||||||
- `/usr/share/qutebrowser/userscripts`
|
- `/usr/share/qutebrowser/userscripts`
|
||||||
verbose: Show notifications when the command started/exited.
|
verbose: Show notifications when the command started/exited.
|
||||||
output: Whether the output should be shown in a new tab.
|
output: Whether the output should be shown in a new tab.
|
||||||
detach: Whether the command should be detached from qutebrowser.
|
detach: Whether the command should be detached from qutebrowser.
|
||||||
cmdline: The commandline to execute.
|
cmdline: The commandline to execute.
|
||||||
|
count: Given to userscripts as $QUTE_COUNT.
|
||||||
"""
|
"""
|
||||||
cmdutils.check_exclusive((userscript, detach), 'ud')
|
cmdutils.check_exclusive((userscript, detach), 'ud')
|
||||||
try:
|
try:
|
||||||
@ -1239,7 +1235,7 @@ class CommandDispatcher:
|
|||||||
if userscript:
|
if userscript:
|
||||||
def _selection_callback(s):
|
def _selection_callback(s):
|
||||||
try:
|
try:
|
||||||
runner = self._run_userscript(s, cmd, args, verbose)
|
runner = self._run_userscript(s, cmd, args, verbose, count)
|
||||||
runner.finished.connect(_on_proc_finished)
|
runner.finished.connect(_on_proc_finished)
|
||||||
except cmdexc.CommandError as e:
|
except cmdexc.CommandError as e:
|
||||||
message.error(str(e))
|
message.error(str(e))
|
||||||
@ -1266,19 +1262,23 @@ class CommandDispatcher:
|
|||||||
"""Open main startpage in current tab."""
|
"""Open main startpage in current tab."""
|
||||||
self.openurl(config.val.url.start_pages[0])
|
self.openurl(config.val.url.start_pages[0])
|
||||||
|
|
||||||
def _run_userscript(self, selection, cmd, args, verbose):
|
def _run_userscript(self, selection, cmd, args, verbose, count):
|
||||||
"""Run a userscript given as argument.
|
"""Run a userscript given as argument.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cmd: The userscript to run.
|
cmd: The userscript to run.
|
||||||
args: Arguments to pass to the userscript.
|
args: Arguments to pass to the userscript.
|
||||||
verbose: Show notifications when the command started/exited.
|
verbose: Show notifications when the command started/exited.
|
||||||
|
count: Exposed to the userscript.
|
||||||
"""
|
"""
|
||||||
env = {
|
env = {
|
||||||
'QUTE_MODE': 'command',
|
'QUTE_MODE': 'command',
|
||||||
'QUTE_SELECTED_TEXT': selection,
|
'QUTE_SELECTED_TEXT': selection,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if count is not None:
|
||||||
|
env['QUTE_COUNT'] = str(count)
|
||||||
|
|
||||||
idx = self._current_index()
|
idx = self._current_index()
|
||||||
if idx != -1:
|
if idx != -1:
|
||||||
env['QUTE_TITLE'] = self._tabbed_browser.widget.page_title(idx)
|
env['QUTE_TITLE'] = self._tabbed_browser.widget.page_title(idx)
|
||||||
@ -1455,6 +1455,7 @@ class CommandDispatcher:
|
|||||||
if tab.data.inspector is None:
|
if tab.data.inspector is None:
|
||||||
tab.data.inspector = inspector.create()
|
tab.data.inspector = inspector.create()
|
||||||
tab.data.inspector.inspect(page)
|
tab.data.inspector.inspect(page)
|
||||||
|
tab.data.inspector.show()
|
||||||
else:
|
else:
|
||||||
tab.data.inspector.toggle(page)
|
tab.data.inspector.toggle(page)
|
||||||
except inspector.WebInspectorError as e:
|
except inspector.WebInspectorError as e:
|
||||||
@ -1673,6 +1674,8 @@ class CommandDispatcher:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
elem.set_value(text)
|
elem.set_value(text)
|
||||||
|
# Kick off js handlers to trick them into thinking there was input.
|
||||||
|
elem.dispatch_event("input", bubbles=True)
|
||||||
except webelem.OrphanedError:
|
except webelem.OrphanedError:
|
||||||
message.error('Edited element vanished')
|
message.error('Edited element vanished')
|
||||||
ed.backup()
|
ed.backup()
|
||||||
@ -2106,7 +2109,10 @@ class CommandDispatcher:
|
|||||||
raise cmdexc.CommandError(str(e))
|
raise cmdexc.CommandError(str(e))
|
||||||
|
|
||||||
widget = self._current_widget()
|
widget = self._current_widget()
|
||||||
widget.run_js_async(js_code, callback=jseval_cb, world=world)
|
try:
|
||||||
|
widget.run_js_async(js_code, callback=jseval_cb, world=world)
|
||||||
|
except browsertab.WebTabError as e:
|
||||||
|
raise cmdexc.CommandError(str(e))
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
def fake_key(self, keystring, global_=False):
|
def fake_key(self, keystring, global_=False):
|
||||||
@ -2243,6 +2249,6 @@ class CommandDispatcher:
|
|||||||
if tab is None:
|
if tab is None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
tab.audio.toggle_muted()
|
tab.audio.toggle_muted(override=True)
|
||||||
except browsertab.WebTabError as e:
|
except browsertab.WebTabError as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
|
@ -29,14 +29,15 @@ import pathlib
|
|||||||
import tempfile
|
import tempfile
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
import sip
|
|
||||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||||
QTimer, QAbstractListModel, QUrl)
|
QTimer, QAbstractListModel, QUrl)
|
||||||
|
|
||||||
|
from qutebrowser.browser import pdfjs
|
||||||
from qutebrowser.commands import cmdexc, cmdutils
|
from qutebrowser.commands import cmdexc, cmdutils
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
|
from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
|
||||||
qtutils)
|
qtutils, objreg)
|
||||||
|
from qutebrowser.qt import sip
|
||||||
|
|
||||||
|
|
||||||
ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole)
|
ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole)
|
||||||
@ -80,9 +81,9 @@ def download_dir():
|
|||||||
ddir = directory
|
ddir = directory
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(ddir)
|
os.makedirs(ddir, exist_ok=True)
|
||||||
except FileExistsError:
|
except OSError as e:
|
||||||
pass
|
message.error("Failed to create download directory: {}".format(e))
|
||||||
|
|
||||||
return ddir
|
return ddir
|
||||||
|
|
||||||
@ -134,11 +135,12 @@ def create_full_filename(basename, filename):
|
|||||||
Return:
|
Return:
|
||||||
The full absolute path, or None if filename creation was not possible.
|
The full absolute path, or None if filename creation was not possible.
|
||||||
"""
|
"""
|
||||||
|
basename = utils.sanitize_filename(basename)
|
||||||
|
# Filename can be a full path so don't use sanitize_filename on it.
|
||||||
# Remove chars which can't be encoded in the filename encoding.
|
# Remove chars which can't be encoded in the filename encoding.
|
||||||
# See https://github.com/qutebrowser/qutebrowser/issues/427
|
# See https://github.com/qutebrowser/qutebrowser/issues/427
|
||||||
encoding = sys.getfilesystemencoding()
|
encoding = sys.getfilesystemencoding()
|
||||||
filename = utils.force_encoding(filename, encoding)
|
filename = utils.force_encoding(filename, encoding)
|
||||||
basename = utils.force_encoding(basename, encoding)
|
|
||||||
if os.path.isabs(filename) and (os.path.isdir(filename) or
|
if os.path.isabs(filename) and (os.path.isdir(filename) or
|
||||||
filename.endswith(os.sep)):
|
filename.endswith(os.sep)):
|
||||||
# We got an absolute directory from the user, so we save it under
|
# We got an absolute directory from the user, so we save it under
|
||||||
@ -159,8 +161,7 @@ def get_filename_question(*, suggested_filename, url, parent=None):
|
|||||||
url: The URL the download originated from.
|
url: The URL the download originated from.
|
||||||
parent: The parent of the question (a QObject).
|
parent: The parent of the question (a QObject).
|
||||||
"""
|
"""
|
||||||
encoding = sys.getfilesystemencoding()
|
suggested_filename = utils.sanitize_filename(suggested_filename)
|
||||||
suggested_filename = utils.force_encoding(suggested_filename, encoding)
|
|
||||||
|
|
||||||
q = usertypes.Question(parent)
|
q = usertypes.Question(parent)
|
||||||
q.title = "Save file to:"
|
q.title = "Save file to:"
|
||||||
@ -224,9 +225,6 @@ class _DownloadTarget:
|
|||||||
|
|
||||||
"""Abstract base class for different download targets."""
|
"""Abstract base class for different download targets."""
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def suggested_filename(self):
|
def suggested_filename(self):
|
||||||
"""Get the suggested filename for this download target."""
|
"""Get the suggested filename for this download target."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -243,7 +241,6 @@ class FileDownloadTarget(_DownloadTarget):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filename, force_overwrite=False):
|
def __init__(self, filename, force_overwrite=False):
|
||||||
# pylint: disable=super-init-not-called
|
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.force_overwrite = force_overwrite
|
self.force_overwrite = force_overwrite
|
||||||
|
|
||||||
@ -263,7 +260,6 @@ class FileObjDownloadTarget(_DownloadTarget):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fileobj):
|
def __init__(self, fileobj):
|
||||||
# pylint: disable=super-init-not-called
|
|
||||||
self.fileobj = fileobj
|
self.fileobj = fileobj
|
||||||
|
|
||||||
def suggested_filename(self):
|
def suggested_filename(self):
|
||||||
@ -290,7 +286,6 @@ class OpenFileDownloadTarget(_DownloadTarget):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cmdline=None):
|
def __init__(self, cmdline=None):
|
||||||
# pylint: disable=super-init-not-called
|
|
||||||
self.cmdline = cmdline
|
self.cmdline = cmdline
|
||||||
|
|
||||||
def suggested_filename(self):
|
def suggested_filename(self):
|
||||||
@ -300,6 +295,17 @@ class OpenFileDownloadTarget(_DownloadTarget):
|
|||||||
return 'temporary file'
|
return 'temporary file'
|
||||||
|
|
||||||
|
|
||||||
|
class PDFJSDownloadTarget(_DownloadTarget):
|
||||||
|
|
||||||
|
"""Open the download via PDF.js."""
|
||||||
|
|
||||||
|
def suggested_filename(self):
|
||||||
|
raise NoFilenameError
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'temporary PDF.js file'
|
||||||
|
|
||||||
|
|
||||||
class DownloadItemStats(QObject):
|
class DownloadItemStats(QObject):
|
||||||
|
|
||||||
"""Statistics (bytes done, total bytes, time, etc.) about a download.
|
"""Statistics (bytes done, total bytes, time, etc.) about a download.
|
||||||
@ -405,6 +411,8 @@ class AbstractDownloadItem(QObject):
|
|||||||
arg: The error message as string.
|
arg: The error message as string.
|
||||||
remove_requested: Emitted when the removal of this download was
|
remove_requested: Emitted when the removal of this download was
|
||||||
requested.
|
requested.
|
||||||
|
pdfjs_requested: Emitted when PDF.js should be opened with the given
|
||||||
|
filename.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data_changed = pyqtSignal()
|
data_changed = pyqtSignal()
|
||||||
@ -412,6 +420,7 @@ class AbstractDownloadItem(QObject):
|
|||||||
error = pyqtSignal(str)
|
error = pyqtSignal(str)
|
||||||
cancelled = pyqtSignal()
|
cancelled = pyqtSignal()
|
||||||
remove_requested = pyqtSignal()
|
remove_requested = pyqtSignal()
|
||||||
|
pdfjs_requested = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -692,9 +701,7 @@ class AbstractDownloadItem(QObject):
|
|||||||
global last_used_directory
|
global last_used_directory
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(self._filename))
|
os.makedirs(os.path.dirname(self._filename), exist_ok=True)
|
||||||
except FileExistsError:
|
|
||||||
pass
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self._die(e.strerror)
|
self._die(e.strerror)
|
||||||
|
|
||||||
@ -732,6 +739,19 @@ class AbstractDownloadItem(QObject):
|
|||||||
return
|
return
|
||||||
self.open_file(cmdline)
|
self.open_file(cmdline)
|
||||||
|
|
||||||
|
def _pdfjs_if_successful(self):
|
||||||
|
"""Open the file via PDF.js if downloading was successful."""
|
||||||
|
if not self.successful:
|
||||||
|
log.downloads.debug("{} finished but not successful, not opening!"
|
||||||
|
.format(self))
|
||||||
|
return
|
||||||
|
|
||||||
|
filename = self._get_open_filename()
|
||||||
|
if filename is None: # pragma: no cover
|
||||||
|
log.downloads.error("No filename to open the download!")
|
||||||
|
return
|
||||||
|
self.pdfjs_requested.emit(os.path.basename(filename))
|
||||||
|
|
||||||
def set_target(self, target):
|
def set_target(self, target):
|
||||||
"""Set the target for a given download.
|
"""Set the target for a given download.
|
||||||
|
|
||||||
@ -743,7 +763,7 @@ class AbstractDownloadItem(QObject):
|
|||||||
elif isinstance(target, FileDownloadTarget):
|
elif isinstance(target, FileDownloadTarget):
|
||||||
self._set_filename(
|
self._set_filename(
|
||||||
target.filename, force_overwrite=target.force_overwrite)
|
target.filename, force_overwrite=target.force_overwrite)
|
||||||
elif isinstance(target, OpenFileDownloadTarget):
|
elif isinstance(target, (OpenFileDownloadTarget, PDFJSDownloadTarget)):
|
||||||
try:
|
try:
|
||||||
fobj = temp_download_manager.get_tmpfile(self.basename)
|
fobj = temp_download_manager.get_tmpfile(self.basename)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
@ -751,8 +771,15 @@ class AbstractDownloadItem(QObject):
|
|||||||
message.error(msg)
|
message.error(msg)
|
||||||
self.cancel()
|
self.cancel()
|
||||||
return
|
return
|
||||||
self.finished.connect(
|
|
||||||
functools.partial(self._open_if_successful, target.cmdline))
|
if isinstance(target, OpenFileDownloadTarget):
|
||||||
|
self.finished.connect(functools.partial(
|
||||||
|
self._open_if_successful, target.cmdline))
|
||||||
|
elif isinstance(target, PDFJSDownloadTarget):
|
||||||
|
self.finished.connect(self._pdfjs_if_successful)
|
||||||
|
else:
|
||||||
|
raise utils.Unreachable
|
||||||
|
|
||||||
self._set_tempfile(fobj)
|
self._set_tempfile(fobj)
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
raise ValueError("Unsupported download target: {}".format(target))
|
raise ValueError("Unsupported download target: {}".format(target))
|
||||||
@ -799,6 +826,13 @@ class AbstractDownloadManager(QObject):
|
|||||||
dl.stats.update_speed()
|
dl.stats.update_speed()
|
||||||
self.data_changed.emit(-1)
|
self.data_changed.emit(-1)
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def _on_pdfjs_requested(self, filename):
|
||||||
|
"""Open PDF.js when a download requests it."""
|
||||||
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||||
|
window='last-focused')
|
||||||
|
tabbed_browser.tabopen(pdfjs.get_main_url(filename), background=False)
|
||||||
|
|
||||||
def _init_item(self, download, auto_remove, suggested_filename):
|
def _init_item(self, download, auto_remove, suggested_filename):
|
||||||
"""Initialize a newly created DownloadItem."""
|
"""Initialize a newly created DownloadItem."""
|
||||||
download.cancelled.connect(download.remove)
|
download.cancelled.connect(download.remove)
|
||||||
@ -815,6 +849,8 @@ class AbstractDownloadManager(QObject):
|
|||||||
download.data_changed.connect(
|
download.data_changed.connect(
|
||||||
functools.partial(self._on_data_changed, download))
|
functools.partial(self._on_data_changed, download))
|
||||||
download.error.connect(self._on_error)
|
download.error.connect(self._on_error)
|
||||||
|
download.pdfjs_requested.connect(self._on_pdfjs_requested)
|
||||||
|
|
||||||
download.basename = suggested_filename
|
download.basename = suggested_filename
|
||||||
idx = len(self.downloads)
|
idx = len(self.downloads)
|
||||||
download.index = idx + 1 # "Human readable" index
|
download.index = idx + 1 # "Human readable" index
|
||||||
@ -1197,7 +1233,7 @@ class TempDownloadManager:
|
|||||||
"directory")
|
"directory")
|
||||||
self._tmpdir = None
|
self._tmpdir = None
|
||||||
|
|
||||||
def _get_tmpdir(self):
|
def get_tmpdir(self):
|
||||||
"""Return the temporary directory that is used for downloads.
|
"""Return the temporary directory that is used for downloads.
|
||||||
|
|
||||||
The directory is created lazily on first access.
|
The directory is created lazily on first access.
|
||||||
@ -1223,13 +1259,12 @@ class TempDownloadManager:
|
|||||||
Return:
|
Return:
|
||||||
A tempfile.NamedTemporaryFile that should be used to save the file.
|
A tempfile.NamedTemporaryFile that should be used to save the file.
|
||||||
"""
|
"""
|
||||||
tmpdir = self._get_tmpdir()
|
tmpdir = self.get_tmpdir()
|
||||||
encoding = sys.getfilesystemencoding()
|
suggested_name = utils.sanitize_filename(suggested_name)
|
||||||
suggested_name = utils.force_encoding(suggested_name, encoding)
|
|
||||||
# Make sure that the filename is not too long
|
# Make sure that the filename is not too long
|
||||||
suggested_name = utils.elide_filename(suggested_name, 50)
|
suggested_name = utils.elide_filename(suggested_name, 50)
|
||||||
fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
|
fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
|
||||||
suffix=suggested_name)
|
suffix='_' + suggested_name)
|
||||||
self.files.append(fobj)
|
self.files.append(fobj)
|
||||||
return fobj
|
return fobj
|
||||||
|
|
||||||
|
@ -21,13 +21,13 @@
|
|||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
import sip
|
|
||||||
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
|
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
|
||||||
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
||||||
|
|
||||||
from qutebrowser.browser import downloads
|
from qutebrowser.browser import downloads
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import qtutils, utils, objreg
|
from qutebrowser.utils import qtutils, utils, objreg
|
||||||
|
from qutebrowser.qt import sip
|
||||||
|
|
||||||
|
|
||||||
def update_geometry(obj):
|
def update_geometry(obj):
|
||||||
|
@ -46,7 +46,8 @@ class GreasemonkeyScript:
|
|||||||
|
|
||||||
"""Container class for userscripts, parses metadata blocks."""
|
"""Container class for userscripts, parses metadata blocks."""
|
||||||
|
|
||||||
def __init__(self, properties, code):
|
def __init__(self, properties, code, # noqa: C901 pragma: no mccabe
|
||||||
|
filename=None):
|
||||||
self._code = code
|
self._code = code
|
||||||
self.includes = []
|
self.includes = []
|
||||||
self.matches = []
|
self.matches = []
|
||||||
@ -58,6 +59,7 @@ class GreasemonkeyScript:
|
|||||||
self.run_at = None
|
self.run_at = None
|
||||||
self.script_meta = None
|
self.script_meta = None
|
||||||
self.runs_on_sub_frames = True
|
self.runs_on_sub_frames = True
|
||||||
|
self.jsworld = "main"
|
||||||
for name, value in properties:
|
for name, value in properties:
|
||||||
if name == 'name':
|
if name == 'name':
|
||||||
self.name = value
|
self.name = value
|
||||||
@ -77,12 +79,22 @@ class GreasemonkeyScript:
|
|||||||
self.runs_on_sub_frames = False
|
self.runs_on_sub_frames = False
|
||||||
elif name == 'require':
|
elif name == 'require':
|
||||||
self.requires.append(value)
|
self.requires.append(value)
|
||||||
|
elif name == 'qute-js-world':
|
||||||
|
self.jsworld = value
|
||||||
|
|
||||||
|
if not self.name:
|
||||||
|
if filename:
|
||||||
|
self.name = filename
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"@name key required or pass filename to init."
|
||||||
|
)
|
||||||
|
|
||||||
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
||||||
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, source):
|
def parse(cls, source, filename=None):
|
||||||
"""GreasemonkeyScript factory.
|
"""GreasemonkeyScript factory.
|
||||||
|
|
||||||
Takes a userscript source and returns a GreasemonkeyScript.
|
Takes a userscript source and returns a GreasemonkeyScript.
|
||||||
@ -94,7 +106,11 @@ class GreasemonkeyScript:
|
|||||||
_head, props, _code = matches
|
_head, props, _code = matches
|
||||||
except ValueError:
|
except ValueError:
|
||||||
props = ""
|
props = ""
|
||||||
script = cls(re.findall(cls.PROPS_REGEX, props), source)
|
script = cls(
|
||||||
|
re.findall(cls.PROPS_REGEX, props),
|
||||||
|
source,
|
||||||
|
filename=filename
|
||||||
|
)
|
||||||
script.script_meta = props
|
script.script_meta = props
|
||||||
if not script.includes and not script.matches:
|
if not script.includes and not script.matches:
|
||||||
script.includes = ['*']
|
script.includes = ['*']
|
||||||
@ -118,7 +134,7 @@ class GreasemonkeyScript:
|
|||||||
scriptName=javascript.string_escape(
|
scriptName=javascript.string_escape(
|
||||||
"/".join([self.namespace or '', self.name])),
|
"/".join([self.namespace or '', self.name])),
|
||||||
scriptInfo=self._meta_json(),
|
scriptInfo=self._meta_json(),
|
||||||
scriptMeta=javascript.string_escape(self.script_meta),
|
scriptMeta=javascript.string_escape(self.script_meta or ''),
|
||||||
scriptSource=self._code,
|
scriptSource=self._code,
|
||||||
use_proxy=use_proxy)
|
use_proxy=use_proxy)
|
||||||
|
|
||||||
@ -142,7 +158,7 @@ class GreasemonkeyScript:
|
|||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class MatchingScripts(object):
|
class MatchingScripts:
|
||||||
|
|
||||||
"""All userscripts registered to run on a particular url."""
|
"""All userscripts registered to run on a particular url."""
|
||||||
|
|
||||||
@ -231,8 +247,9 @@ class GreasemonkeyManager(QObject):
|
|||||||
if not os.path.isfile(script_filename):
|
if not os.path.isfile(script_filename):
|
||||||
continue
|
continue
|
||||||
script_path = os.path.join(scripts_dir, script_filename)
|
script_path = os.path.join(scripts_dir, script_filename)
|
||||||
with open(script_path, encoding='utf-8') as script_file:
|
with open(script_path, encoding='utf-8-sig') as script_file:
|
||||||
script = GreasemonkeyScript.parse(script_file.read())
|
script = GreasemonkeyScript.parse(script_file.read(),
|
||||||
|
script_filename)
|
||||||
if not script.name:
|
if not script.name:
|
||||||
script.name = script_filename
|
script.name = script_filename
|
||||||
self.add_script(script, force)
|
self.add_script(script, force)
|
||||||
|
@ -32,7 +32,7 @@ import attr
|
|||||||
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QUrl
|
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QUrl
|
||||||
from PyQt5.QtWidgets import QLabel
|
from PyQt5.QtWidgets import QLabel
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config, configexc
|
||||||
from qutebrowser.keyinput import modeman, modeparsers
|
from qutebrowser.keyinput import modeman, modeparsers
|
||||||
from qutebrowser.browser import webelem
|
from qutebrowser.browser import webelem
|
||||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||||
@ -601,8 +601,12 @@ class HintManager(QObject):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if elems is None:
|
if elems is None:
|
||||||
message.error("There was an error while getting hint elements")
|
message.error("Unknown error while getting hint elements.")
|
||||||
return
|
return
|
||||||
|
elif isinstance(elems, webelem.Error):
|
||||||
|
message.error(str(elems))
|
||||||
|
return
|
||||||
|
|
||||||
if not elems:
|
if not elems:
|
||||||
message.error("No elements found.")
|
message.error("No elements found.")
|
||||||
return
|
return
|
||||||
@ -637,9 +641,8 @@ class HintManager(QObject):
|
|||||||
star_args_optional=True, maxsplit=2)
|
star_args_optional=True, maxsplit=2)
|
||||||
@cmdutils.argument('win_id', win_id=True)
|
@cmdutils.argument('win_id', win_id=True)
|
||||||
def start(self, # pylint: disable=keyword-arg-before-vararg
|
def start(self, # pylint: disable=keyword-arg-before-vararg
|
||||||
group=webelem.Group.all, target=Target.normal,
|
group='all', target=Target.normal, *args, win_id, mode=None,
|
||||||
*args, win_id, mode=None, add_history=False, rapid=False,
|
add_history=False, rapid=False, first=False):
|
||||||
first=False):
|
|
||||||
"""Start hinting.
|
"""Start hinting.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -658,6 +661,9 @@ class HintManager(QObject):
|
|||||||
- `images`: Only images.
|
- `images`: Only images.
|
||||||
- `inputs`: Only input fields.
|
- `inputs`: Only input fields.
|
||||||
|
|
||||||
|
Custom groups can be added via the `hints.selectors` setting
|
||||||
|
and also used here.
|
||||||
|
|
||||||
target: What to do with the selected element.
|
target: What to do with the selected element.
|
||||||
|
|
||||||
- `normal`: Open the link.
|
- `normal`: Open the link.
|
||||||
@ -693,7 +699,7 @@ class HintManager(QObject):
|
|||||||
- With `userscript`: The userscript to execute. Either store
|
- With `userscript`: The userscript to execute. Either store
|
||||||
the userscript in
|
the userscript in
|
||||||
`~/.local/share/qutebrowser/userscripts`
|
`~/.local/share/qutebrowser/userscripts`
|
||||||
(or `$XDG_DATA_DIR`), or use an absolute
|
(or `$XDG_DATA_HOME`), or use an absolute
|
||||||
path.
|
path.
|
||||||
- With `fill`: The command to fill the statusbar with.
|
- With `fill`: The command to fill the statusbar with.
|
||||||
`{hint-url}` will get replaced by the selected
|
`{hint-url}` will get replaced by the selected
|
||||||
@ -724,15 +730,12 @@ class HintManager(QObject):
|
|||||||
raise cmdexc.CommandError("Rapid hinting makes no sense with "
|
raise cmdexc.CommandError("Rapid hinting makes no sense with "
|
||||||
"target {}!".format(name))
|
"target {}!".format(name))
|
||||||
|
|
||||||
if mode is None:
|
|
||||||
mode = config.val.hints.mode
|
|
||||||
|
|
||||||
self._check_args(target, *args)
|
self._check_args(target, *args)
|
||||||
self._context = HintContext()
|
self._context = HintContext()
|
||||||
self._context.tab = tab
|
self._context.tab = tab
|
||||||
self._context.target = target
|
self._context.target = target
|
||||||
self._context.rapid = rapid
|
self._context.rapid = rapid
|
||||||
self._context.hint_mode = mode
|
self._context.hint_mode = self._get_hint_mode(mode)
|
||||||
self._context.add_history = add_history
|
self._context.add_history = add_history
|
||||||
self._context.first = first
|
self._context.first = first
|
||||||
try:
|
try:
|
||||||
@ -741,10 +744,28 @@ class HintManager(QObject):
|
|||||||
raise cmdexc.CommandError("No URL set for this page yet!")
|
raise cmdexc.CommandError("No URL set for this page yet!")
|
||||||
self._context.args = args
|
self._context.args = args
|
||||||
self._context.group = group
|
self._context.group = group
|
||||||
selector = webelem.SELECTORS[self._context.group]
|
|
||||||
|
try:
|
||||||
|
selector = webelem.css_selector(self._context.group,
|
||||||
|
self._context.baseurl)
|
||||||
|
except webelem.Error as e:
|
||||||
|
raise cmdexc.CommandError(str(e))
|
||||||
|
|
||||||
self._context.tab.elements.find_css(selector, self._start_cb,
|
self._context.tab.elements.find_css(selector, self._start_cb,
|
||||||
only_visible=True)
|
only_visible=True)
|
||||||
|
|
||||||
|
def _get_hint_mode(self, mode):
|
||||||
|
"""Get the hinting mode to use based on a mode argument."""
|
||||||
|
if mode is None:
|
||||||
|
return config.val.hints.mode
|
||||||
|
|
||||||
|
opt = config.instance.get_opt('hints.mode')
|
||||||
|
try:
|
||||||
|
opt.typ.to_py(mode)
|
||||||
|
except configexc.ValidationError as e:
|
||||||
|
raise cmdexc.CommandError("Invalid mode: {}".format(e))
|
||||||
|
return mode
|
||||||
|
|
||||||
def current_mode(self):
|
def current_mode(self):
|
||||||
"""Return the currently active hinting mode (or None otherwise)."""
|
"""Return the currently active hinting mode (or None otherwise)."""
|
||||||
if self._context is None:
|
if self._context is None:
|
||||||
|
@ -23,11 +23,12 @@ import os
|
|||||||
import time
|
import time
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer, pyqtSignal
|
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal
|
||||||
|
from PyQt5.QtWidgets import QProgressDialog, QApplication
|
||||||
|
|
||||||
|
from qutebrowser.config import config
|
||||||
from qutebrowser.commands import cmdutils, cmdexc
|
from qutebrowser.commands import cmdutils, cmdexc
|
||||||
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
|
from qutebrowser.utils import utils, objreg, log, usertypes, message, qtutils
|
||||||
debug, standarddir, qtutils)
|
|
||||||
from qutebrowser.misc import objects, sql
|
from qutebrowser.misc import objects, sql
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +36,77 @@ from qutebrowser.misc import objects, sql
|
|||||||
_USER_VERSION = 2
|
_USER_VERSION = 2
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryProgress:
|
||||||
|
|
||||||
|
"""Progress dialog for history imports/conversions.
|
||||||
|
|
||||||
|
This makes WebHistory simpler as it can call methods of this class even
|
||||||
|
when we don't want to show a progress dialog (for very small imports). This
|
||||||
|
means tick() and finish() can be called even when start() wasn't.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._progress = None
|
||||||
|
self._value = 0
|
||||||
|
|
||||||
|
def start(self, text, maximum):
|
||||||
|
"""Start showing a progress dialog."""
|
||||||
|
self._progress = QProgressDialog()
|
||||||
|
self._progress.setMinimumDuration(500)
|
||||||
|
self._progress.setLabelText(text)
|
||||||
|
self._progress.setMaximum(maximum)
|
||||||
|
self._progress.setCancelButton(None)
|
||||||
|
self._progress.show()
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
|
def tick(self):
|
||||||
|
"""Increase the displayed progress value."""
|
||||||
|
self._value += 1
|
||||||
|
if self._progress is not None:
|
||||||
|
self._progress.setValue(self._value)
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
"""Finish showing the progress dialog."""
|
||||||
|
if self._progress is not None:
|
||||||
|
self._progress.hide()
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionMetaInfo(sql.SqlTable):
|
||||||
|
|
||||||
|
"""Table containing meta-information for the completion."""
|
||||||
|
|
||||||
|
KEYS = {
|
||||||
|
'force_rebuild': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__("CompletionMetaInfo", ['key', 'value'],
|
||||||
|
constraints={'key': 'PRIMARY KEY'})
|
||||||
|
for key, default in self.KEYS.items():
|
||||||
|
if key not in self:
|
||||||
|
self[key] = default
|
||||||
|
|
||||||
|
def _check_key(self, key):
|
||||||
|
if key not in self.KEYS:
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
self._check_key(key)
|
||||||
|
query = self.contains_query('key')
|
||||||
|
return query.run(val=key).value()
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
self._check_key(key)
|
||||||
|
query = sql.Query('SELECT value FROM CompletionMetaInfo '
|
||||||
|
'WHERE key = :key')
|
||||||
|
return query.run(key=key).value()
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._check_key(key)
|
||||||
|
self.insert({'key': key, 'value': value}, replace=True)
|
||||||
|
|
||||||
|
|
||||||
class CompletionHistory(sql.SqlTable):
|
class CompletionHistory(sql.SqlTable):
|
||||||
|
|
||||||
"""History which only has the newest entry for each URL."""
|
"""History which only has the newest entry for each URL."""
|
||||||
@ -50,26 +122,46 @@ class CompletionHistory(sql.SqlTable):
|
|||||||
|
|
||||||
class WebHistory(sql.SqlTable):
|
class WebHistory(sql.SqlTable):
|
||||||
|
|
||||||
"""The global history of visited pages."""
|
"""The global history of visited pages.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
completion: A CompletionHistory instance.
|
||||||
|
metainfo: A CompletionMetaInfo instance.
|
||||||
|
_progress: A HistoryProgress instance.
|
||||||
|
|
||||||
|
Class attributes:
|
||||||
|
_PROGRESS_THRESHOLD: When to start showing progress dialogs.
|
||||||
|
"""
|
||||||
|
|
||||||
# All web history cleared
|
# All web history cleared
|
||||||
history_cleared = pyqtSignal()
|
history_cleared = pyqtSignal()
|
||||||
# one url cleared
|
# one url cleared
|
||||||
url_cleared = pyqtSignal(QUrl)
|
url_cleared = pyqtSignal(QUrl)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
_PROGRESS_THRESHOLD = 1000
|
||||||
|
|
||||||
|
def __init__(self, progress, parent=None):
|
||||||
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
|
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
|
||||||
constraints={'url': 'NOT NULL',
|
constraints={'url': 'NOT NULL',
|
||||||
'title': 'NOT NULL',
|
'title': 'NOT NULL',
|
||||||
'atime': 'NOT NULL',
|
'atime': 'NOT NULL',
|
||||||
'redirect': 'NOT NULL'},
|
'redirect': 'NOT NULL'},
|
||||||
parent=parent)
|
parent=parent)
|
||||||
|
self._progress = progress
|
||||||
|
|
||||||
self.completion = CompletionHistory(parent=self)
|
self.completion = CompletionHistory(parent=self)
|
||||||
|
self.metainfo = CompletionMetaInfo(parent=self)
|
||||||
|
|
||||||
if sql.Query('pragma user_version').run().value() < _USER_VERSION:
|
if sql.Query('pragma user_version').run().value() < _USER_VERSION:
|
||||||
self.completion.delete_all()
|
self.completion.delete_all()
|
||||||
|
if self.metainfo['force_rebuild']:
|
||||||
|
self.completion.delete_all()
|
||||||
|
self.metainfo['force_rebuild'] = False
|
||||||
|
|
||||||
if not self.completion:
|
if not self.completion:
|
||||||
# either the table is out-of-date or the user wiped it manually
|
# either the table is out-of-date or the user wiped it manually
|
||||||
self._rebuild_completion()
|
self._rebuild_completion()
|
||||||
|
|
||||||
self.create_index('HistoryIndex', 'url')
|
self.create_index('HistoryIndex', 'url')
|
||||||
self.create_index('HistoryAtimeIndex', 'atime')
|
self.create_index('HistoryAtimeIndex', 'atime')
|
||||||
self._contains_query = self.contains_query('url')
|
self._contains_query = self.contains_query('url')
|
||||||
@ -87,21 +179,29 @@ class WebHistory(sql.SqlTable):
|
|||||||
'ORDER BY atime desc '
|
'ORDER BY atime desc '
|
||||||
'limit :limit offset :offset')
|
'limit :limit offset :offset')
|
||||||
|
|
||||||
|
config.instance.changed.connect(self._on_config_changed)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return utils.get_repr(self, length=len(self))
|
return utils.get_repr(self, length=len(self))
|
||||||
|
|
||||||
def __contains__(self, url):
|
def __contains__(self, url):
|
||||||
return self._contains_query.run(val=url).value()
|
return self._contains_query.run(val=url).value()
|
||||||
|
|
||||||
|
@config.change_filter('completion.web_history.exclude')
|
||||||
|
def _on_config_changed(self):
|
||||||
|
self.metainfo['force_rebuild'] = True
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _handle_sql_errors(self):
|
def _handle_sql_errors(self):
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except sql.SqlError as e:
|
except sql.SqlEnvironmentError as e:
|
||||||
if e.environmental:
|
message.error("Failed to write history: {}".format(e.text()))
|
||||||
message.error("Failed to write history: {}".format(e.text()))
|
|
||||||
else:
|
def _is_excluded(self, url):
|
||||||
raise
|
"""Check if the given URL is excluded from the completion."""
|
||||||
|
patterns = config.cache['completion.web_history.exclude']
|
||||||
|
return any(pattern.matches(url) for pattern in patterns)
|
||||||
|
|
||||||
def _rebuild_completion(self):
|
def _rebuild_completion(self):
|
||||||
data = {'url': [], 'title': [], 'last_atime': []}
|
data = {'url': [], 'title': [], 'last_atime': []}
|
||||||
@ -109,10 +209,23 @@ class WebHistory(sql.SqlTable):
|
|||||||
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
|
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
|
||||||
'WHERE NOT redirect and url NOT LIKE "qute://back%" '
|
'WHERE NOT redirect and url NOT LIKE "qute://back%" '
|
||||||
'GROUP BY url ORDER BY atime asc')
|
'GROUP BY url ORDER BY atime asc')
|
||||||
for entry in q.run():
|
entries = list(q.run())
|
||||||
data['url'].append(self._format_completion_url(QUrl(entry.url)))
|
|
||||||
|
if len(entries) > self._PROGRESS_THRESHOLD:
|
||||||
|
self._progress.start("Rebuilding completion...", len(entries))
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
self._progress.tick()
|
||||||
|
|
||||||
|
url = QUrl(entry.url)
|
||||||
|
if self._is_excluded(url):
|
||||||
|
continue
|
||||||
|
data['url'].append(self._format_completion_url(url))
|
||||||
data['title'].append(entry.title)
|
data['title'].append(entry.title)
|
||||||
data['last_atime'].append(entry.atime)
|
data['last_atime'].append(entry.atime)
|
||||||
|
|
||||||
|
self._progress.finish()
|
||||||
|
|
||||||
self.completion.insert_batch(data, replace=True)
|
self.completion.insert_batch(data, replace=True)
|
||||||
sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run()
|
sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run()
|
||||||
|
|
||||||
@ -218,114 +331,15 @@ class WebHistory(sql.SqlTable):
|
|||||||
'title': title,
|
'title': title,
|
||||||
'atime': atime,
|
'atime': atime,
|
||||||
'redirect': redirect})
|
'redirect': redirect})
|
||||||
if not redirect:
|
|
||||||
self.completion.insert({
|
|
||||||
'url': self._format_completion_url(url),
|
|
||||||
'title': title,
|
|
||||||
'last_atime': atime
|
|
||||||
}, replace=True)
|
|
||||||
|
|
||||||
def _parse_entry(self, line):
|
if redirect or self._is_excluded(url):
|
||||||
"""Parse a history line like '12345 http://example.com title'."""
|
return
|
||||||
if not line or line.startswith('#'):
|
|
||||||
return None
|
|
||||||
data = line.split(maxsplit=2)
|
|
||||||
if len(data) == 2:
|
|
||||||
atime, url = data
|
|
||||||
title = ""
|
|
||||||
elif len(data) == 3:
|
|
||||||
atime, url, title = data
|
|
||||||
else:
|
|
||||||
raise ValueError("2 or 3 fields expected")
|
|
||||||
|
|
||||||
# http://xn--pple-43d.com/ with
|
self.completion.insert({
|
||||||
# https://bugreports.qt.io/browse/QTBUG-60364
|
'url': self._format_completion_url(url),
|
||||||
if url in ['http://.com/', 'https://.com/',
|
'title': title,
|
||||||
'http://www..com/', 'https://www..com/']:
|
'last_atime': atime
|
||||||
return None
|
}, replace=True)
|
||||||
|
|
||||||
url = QUrl(url)
|
|
||||||
if not url.isValid():
|
|
||||||
raise ValueError("Invalid URL: {}".format(url.errorString()))
|
|
||||||
|
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/2646
|
|
||||||
if url.scheme() == 'data':
|
|
||||||
return None
|
|
||||||
|
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/670
|
|
||||||
atime = atime.lstrip('\0')
|
|
||||||
|
|
||||||
if '-' in atime:
|
|
||||||
atime, flags = atime.split('-')
|
|
||||||
else:
|
|
||||||
flags = ''
|
|
||||||
|
|
||||||
if not set(flags).issubset('r'):
|
|
||||||
raise ValueError("Invalid flags {!r}".format(flags))
|
|
||||||
|
|
||||||
redirect = 'r' in flags
|
|
||||||
return (url, title, int(atime), redirect)
|
|
||||||
|
|
||||||
def import_txt(self):
|
|
||||||
"""Import a history text file into sqlite if it exists.
|
|
||||||
|
|
||||||
In older versions of qutebrowser, history was stored in a text format.
|
|
||||||
This converts that file into the new sqlite format and moves it to a
|
|
||||||
backup location.
|
|
||||||
"""
|
|
||||||
path = os.path.join(standarddir.data(), 'history')
|
|
||||||
if not os.path.isfile(path):
|
|
||||||
return
|
|
||||||
|
|
||||||
def action():
|
|
||||||
"""Actually run the import."""
|
|
||||||
with debug.log_time(log.init, 'Import old history file to sqlite'):
|
|
||||||
try:
|
|
||||||
self._read(path)
|
|
||||||
except ValueError as ex:
|
|
||||||
message.error('Failed to import history: {}'.format(ex))
|
|
||||||
else:
|
|
||||||
self._write_backup(path)
|
|
||||||
|
|
||||||
# delay to give message time to appear before locking down for import
|
|
||||||
message.info('Converting {} to sqlite...'.format(path))
|
|
||||||
QTimer.singleShot(100, action)
|
|
||||||
|
|
||||||
def _read(self, path):
|
|
||||||
"""Import a text file into the sql database."""
|
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
|
||||||
data = {'url': [], 'title': [], 'atime': [], 'redirect': []}
|
|
||||||
completion_data = {'url': [], 'title': [], 'last_atime': []}
|
|
||||||
for (i, line) in enumerate(f):
|
|
||||||
try:
|
|
||||||
parsed = self._parse_entry(line.strip())
|
|
||||||
if parsed is None:
|
|
||||||
continue
|
|
||||||
url, title, atime, redirect = parsed
|
|
||||||
data['url'].append(self._format_url(url))
|
|
||||||
data['title'].append(title)
|
|
||||||
data['atime'].append(atime)
|
|
||||||
data['redirect'].append(redirect)
|
|
||||||
if not redirect:
|
|
||||||
completion_data['url'].append(
|
|
||||||
self._format_completion_url(url))
|
|
||||||
completion_data['title'].append(title)
|
|
||||||
completion_data['last_atime'].append(atime)
|
|
||||||
except ValueError as ex:
|
|
||||||
raise ValueError('Failed to parse line #{} of {}: "{}"'
|
|
||||||
.format(i, path, ex))
|
|
||||||
self.insert_batch(data)
|
|
||||||
self.completion.insert_batch(completion_data, replace=True)
|
|
||||||
|
|
||||||
def _write_backup(self, path):
|
|
||||||
bak = path + '.bak'
|
|
||||||
message.info('History import complete. Appending {} to {}'
|
|
||||||
.format(path, bak))
|
|
||||||
with open(path, 'r', encoding='utf-8') as infile:
|
|
||||||
with open(bak, 'a', encoding='utf-8') as outfile:
|
|
||||||
for line in infile:
|
|
||||||
outfile.write('\n' + line)
|
|
||||||
os.remove(path)
|
|
||||||
|
|
||||||
def _format_url(self, url):
|
def _format_url(self, url):
|
||||||
return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||||
@ -360,7 +374,8 @@ def init(parent=None):
|
|||||||
Args:
|
Args:
|
||||||
parent: The parent to use for WebHistory.
|
parent: The parent to use for WebHistory.
|
||||||
"""
|
"""
|
||||||
history = WebHistory(parent=parent)
|
progress = HistoryProgress()
|
||||||
|
history = WebHistory(progress=progress, parent=parent)
|
||||||
objreg.register('web-history', history)
|
objreg.register('web-history', history)
|
||||||
|
|
||||||
if objects.backend == usertypes.Backend.QtWebKit: # pragma: no cover
|
if objects.backend == usertypes.Backend.QtWebKit: # pragma: no cover
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import message, log, usertypes, qtutils
|
from qutebrowser.utils import message, log, usertypes, qtutils, objreg
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
|
|
||||||
|
|
||||||
@ -40,11 +40,12 @@ class ChildEventFilter(QObject):
|
|||||||
_widget: The widget expected to send out childEvents.
|
_widget: The widget expected to send out childEvents.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, eventfilter, widget, parent=None):
|
def __init__(self, eventfilter, widget, win_id, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._filter = eventfilter
|
self._filter = eventfilter
|
||||||
assert widget is not None
|
assert widget is not None
|
||||||
self._widget = widget
|
self._widget = widget
|
||||||
|
self._win_id = win_id
|
||||||
|
|
||||||
def eventFilter(self, obj, event):
|
def eventFilter(self, obj, event):
|
||||||
"""Act on ChildAdded events."""
|
"""Act on ChildAdded events."""
|
||||||
@ -57,7 +58,22 @@ class ChildEventFilter(QObject):
|
|||||||
|
|
||||||
if qtutils.version_check('5.11', compiled=False, exact=True):
|
if qtutils.version_check('5.11', compiled=False, exact=True):
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
|
||||||
QTimer.singleShot(0, self._widget.setFocus)
|
pass_modes = [usertypes.KeyMode.command,
|
||||||
|
usertypes.KeyMode.prompt,
|
||||||
|
usertypes.KeyMode.yesno]
|
||||||
|
if modeman.instance(self._win_id).mode not in pass_modes:
|
||||||
|
tabbed_browser = objreg.get('tabbed-browser',
|
||||||
|
scope='window',
|
||||||
|
window=self._win_id)
|
||||||
|
current_index = tabbed_browser.widget.currentIndex()
|
||||||
|
try:
|
||||||
|
widget_index = tabbed_browser.widget.indexOf(
|
||||||
|
self._widget.parent())
|
||||||
|
except RuntimeError:
|
||||||
|
widget_index = -1
|
||||||
|
if current_index == widget_index:
|
||||||
|
QTimer.singleShot(0, self._widget.setFocus)
|
||||||
|
|
||||||
elif event.type() == QEvent.ChildRemoved:
|
elif event.type() == QEvent.ChildRemoved:
|
||||||
child = event.child()
|
child = event.child()
|
||||||
log.mouse.debug("{}: removed child {}".format(obj, child))
|
log.mouse.debug("{}: removed child {}".format(obj, child))
|
||||||
@ -100,9 +116,13 @@ class MouseEventFilter(QObject):
|
|||||||
|
|
||||||
self._ignore_wheel_event = True
|
self._ignore_wheel_event = True
|
||||||
|
|
||||||
|
pos = e.pos()
|
||||||
|
if pos.x() < 0 or pos.y() < 0:
|
||||||
|
log.mouse.warning("Ignoring invalid click at {}".format(pos))
|
||||||
|
return False
|
||||||
|
|
||||||
if e.button() != Qt.NoButton:
|
if e.button() != Qt.NoButton:
|
||||||
self._tab.elements.find_at_pos(e.pos(),
|
self._tab.elements.find_at_pos(pos, self._mousepress_insertmode_cb)
|
||||||
self._mousepress_insertmode_cb)
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -125,6 +145,10 @@ class MouseEventFilter(QObject):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if e.modifiers() & Qt.ControlModifier:
|
if e.modifiers() & Qt.ControlModifier:
|
||||||
|
mode = modeman.instance(self._tab.win_id).mode
|
||||||
|
if mode == usertypes.KeyMode.passthrough:
|
||||||
|
return False
|
||||||
|
|
||||||
divider = config.val.zoom.mouse_divider
|
divider = config.val.zoom.mouse_divider
|
||||||
if divider == 0:
|
if divider == 0:
|
||||||
return False
|
return False
|
||||||
|
@ -117,7 +117,10 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
|
|||||||
"""
|
"""
|
||||||
def _prevnext_cb(elems):
|
def _prevnext_cb(elems):
|
||||||
if elems is None:
|
if elems is None:
|
||||||
message.error("There was an error while getting hint elements")
|
message.error("Unknown error while getting hint elements")
|
||||||
|
return
|
||||||
|
elif isinstance(elems, webelem.Error):
|
||||||
|
message.error(str(elems))
|
||||||
return
|
return
|
||||||
|
|
||||||
elem = _find_prevnext(prev, elems)
|
elem = _find_prevnext(prev, elems)
|
||||||
@ -147,5 +150,9 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
|
|||||||
else:
|
else:
|
||||||
browsertab.openurl(url)
|
browsertab.openurl(url)
|
||||||
|
|
||||||
browsertab.elements.find_css(webelem.SELECTORS[webelem.Group.links],
|
try:
|
||||||
_prevnext_cb)
|
link_selector = webelem.css_selector('links', baseurl)
|
||||||
|
except webelem.Error as e:
|
||||||
|
raise Error(str(e))
|
||||||
|
|
||||||
|
browsertab.elements.find_css(link_selector, _prevnext_cb)
|
||||||
|
@ -22,9 +22,12 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl
|
from PyQt5.QtCore import QUrl, QUrlQuery
|
||||||
|
|
||||||
from qutebrowser.utils import utils, javascript
|
from qutebrowser.utils import (utils, javascript, jinja, qtutils, usertypes,
|
||||||
|
standarddir, log)
|
||||||
|
from qutebrowser.misc import objects
|
||||||
|
from qutebrowser.config import config
|
||||||
|
|
||||||
|
|
||||||
class PDFJSNotFound(Exception):
|
class PDFJSNotFound(Exception):
|
||||||
@ -41,73 +44,73 @@ class PDFJSNotFound(Exception):
|
|||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
def generate_pdfjs_page(url):
|
def generate_pdfjs_page(filename, url):
|
||||||
"""Return the html content of a page that displays url with pdfjs.
|
"""Return the html content of a page that displays a file with pdfjs.
|
||||||
|
|
||||||
Returns a string.
|
Returns a string.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: The url of the pdf as QUrl.
|
filename: The filename of the PDF to open.
|
||||||
|
url: The URL being opened.
|
||||||
"""
|
"""
|
||||||
viewer = get_pdfjs_res('web/viewer.html').decode('utf-8')
|
if not is_available():
|
||||||
script = _generate_pdfjs_script(url)
|
pdfjs_dir = os.path.join(standarddir.data(), 'pdfjs')
|
||||||
html_page = viewer.replace('</body>',
|
return jinja.render('no_pdfjs.html',
|
||||||
'</body><script>{}</script>'.format(script))
|
url=url.toDisplayString(),
|
||||||
return html_page
|
title="PDF.js not found",
|
||||||
|
pdfjs_dir=pdfjs_dir)
|
||||||
|
html = get_pdfjs_res('web/viewer.html').decode('utf-8')
|
||||||
|
|
||||||
|
script = _generate_pdfjs_script(filename)
|
||||||
|
html = html.replace('</body>',
|
||||||
|
'</body><script>{}</script>'.format(script))
|
||||||
|
# WORKAROUND for the fact that PDF.js tries to use the Fetch API even with
|
||||||
|
# qute:// URLs.
|
||||||
|
pdfjs_script = '<script src="../build/pdf.js"></script>'
|
||||||
|
html = html.replace(pdfjs_script,
|
||||||
|
'<script>window.Response = undefined;</script>\n' +
|
||||||
|
pdfjs_script)
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
def _generate_pdfjs_script(url):
|
def _generate_pdfjs_script(filename):
|
||||||
"""Generate the script that shows the pdf with pdf.js.
|
"""Generate the script that shows the pdf with pdf.js.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: The url of the pdf page as QUrl.
|
filename: The name of the file to open.
|
||||||
"""
|
"""
|
||||||
return (
|
url = QUrl('qute://pdfjs/file')
|
||||||
'document.addEventListener("DOMContentLoaded", function() {{\n'
|
url_query = QUrlQuery()
|
||||||
' PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
|
url_query.addQueryItem('filename', filename)
|
||||||
' (window.PDFView || window.PDFViewerApplication).open("{url}");\n'
|
url.setQuery(url_query)
|
||||||
'}});\n'
|
|
||||||
).format(url=javascript.string_escape(url.toString(QUrl.FullyEncoded)))
|
|
||||||
|
|
||||||
|
return jinja.js_environment.from_string("""
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
if (typeof window.PDFJS !== 'undefined') {
|
||||||
|
// v1.x
|
||||||
|
{% if disable_create_object_url %}
|
||||||
|
window.PDFJS.disableCreateObjectURL = true;
|
||||||
|
{% endif %}
|
||||||
|
window.PDFJS.verbosity = window.PDFJS.VERBOSITY_LEVELS.info;
|
||||||
|
} else {
|
||||||
|
// v2.x
|
||||||
|
const options = window.PDFViewerApplicationOptions;
|
||||||
|
{% if disable_create_object_url %}
|
||||||
|
options.set('disableCreateObjectURL', true);
|
||||||
|
{% endif %}
|
||||||
|
options.set('verbosity', pdfjsLib.VerbosityLevel.INFOS);
|
||||||
|
}
|
||||||
|
|
||||||
def fix_urls(asset):
|
const viewer = window.PDFView || window.PDFViewerApplication;
|
||||||
"""Take an html page and replace each relative URL with an absolute.
|
viewer.open({{ url }});
|
||||||
|
});
|
||||||
This is specialized for pdf.js files and not a general purpose function.
|
""").render(
|
||||||
|
url=javascript.to_js(url.toString(QUrl.FullyEncoded)),
|
||||||
Args:
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70420
|
||||||
asset: js file or html page as string.
|
disable_create_object_url=(
|
||||||
"""
|
not qtutils.version_check('5.12') and
|
||||||
new_urls = [
|
not qtutils.version_check('5.7.1', exact=True, compiled=False) and
|
||||||
('viewer.css', 'qute://pdfjs/web/viewer.css'),
|
objects.backend == usertypes.Backend.QtWebEngine))
|
||||||
('compatibility.js', 'qute://pdfjs/web/compatibility.js'),
|
|
||||||
('locale/locale.properties',
|
|
||||||
'qute://pdfjs/web/locale/locale.properties'),
|
|
||||||
('l10n.js', 'qute://pdfjs/web/l10n.js'),
|
|
||||||
('../build/pdf.js', 'qute://pdfjs/build/pdf.js'),
|
|
||||||
('debugger.js', 'qute://pdfjs/web/debugger.js'),
|
|
||||||
('viewer.js', 'qute://pdfjs/web/viewer.js'),
|
|
||||||
('compressed.tracemonkey-pldi-09.pdf', ''),
|
|
||||||
('./images/', 'qute://pdfjs/web/images/'),
|
|
||||||
('../build/pdf.worker.js', 'qute://pdfjs/build/pdf.worker.js'),
|
|
||||||
('../web/cmaps/', 'qute://pdfjs/web/cmaps/'),
|
|
||||||
]
|
|
||||||
for original, new in new_urls:
|
|
||||||
asset = asset.replace(original, new)
|
|
||||||
return asset
|
|
||||||
|
|
||||||
|
|
||||||
SYSTEM_PDFJS_PATHS = [
|
|
||||||
# Debian pdf.js-common
|
|
||||||
# Arch Linux pdfjs (AUR)
|
|
||||||
'/usr/share/pdf.js/',
|
|
||||||
# Arch Linux pdf.js (AUR)
|
|
||||||
'/usr/share/javascript/pdf.js/',
|
|
||||||
# Debian libjs-pdf
|
|
||||||
'/usr/share/javascript/pdf/',
|
|
||||||
# fallback
|
|
||||||
os.path.expanduser('~/.local/share/qutebrowser/pdfjs/'),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_pdfjs_res_and_path(path):
|
def get_pdfjs_res_and_path(path):
|
||||||
@ -124,11 +127,25 @@ def get_pdfjs_res_and_path(path):
|
|||||||
content = None
|
content = None
|
||||||
file_path = None
|
file_path = None
|
||||||
|
|
||||||
|
system_paths = [
|
||||||
|
# Debian pdf.js-common
|
||||||
|
# Arch Linux pdfjs (AUR)
|
||||||
|
'/usr/share/pdf.js/',
|
||||||
|
# Arch Linux pdf.js (AUR)
|
||||||
|
'/usr/share/javascript/pdf.js/',
|
||||||
|
# Debian libjs-pdf
|
||||||
|
'/usr/share/javascript/pdf/',
|
||||||
|
# fallback
|
||||||
|
os.path.join(standarddir.data(), 'pdfjs'),
|
||||||
|
# hardcoded fallback for --temp-basedir
|
||||||
|
os.path.expanduser('~/.local/share/qutebrowser/pdfjs/'),
|
||||||
|
]
|
||||||
|
|
||||||
# First try a system wide installation
|
# First try a system wide installation
|
||||||
# System installations might strip off the 'build/' or 'web/' prefixes.
|
# System installations might strip off the 'build/' or 'web/' prefixes.
|
||||||
# qute expects them, so we need to adjust for it.
|
# qute expects them, so we need to adjust for it.
|
||||||
names_to_try = [path, _remove_prefix(path)]
|
names_to_try = [path, _remove_prefix(path)]
|
||||||
for system_path in SYSTEM_PDFJS_PATHS:
|
for system_path in system_paths:
|
||||||
content, file_path = _read_from_system(system_path, names_to_try)
|
content, file_path = _read_from_system(system_path, names_to_try)
|
||||||
if content is not None:
|
if content is not None:
|
||||||
break
|
break
|
||||||
@ -140,14 +157,11 @@ def get_pdfjs_res_and_path(path):
|
|||||||
content = utils.read_file(res_path, binary=True)
|
content = utils.read_file(res_path, binary=True)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise PDFJSNotFound(path) from None
|
raise PDFJSNotFound(path) from None
|
||||||
|
except OSError as e:
|
||||||
|
log.misc.warning("OSError while reading PDF.js file: {}".format(e))
|
||||||
|
raise PDFJSNotFound(path) from None
|
||||||
|
|
||||||
try:
|
return content, file_path
|
||||||
# Might be script/html or might be binary
|
|
||||||
text_content = content.decode('utf-8')
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
return (content, file_path)
|
|
||||||
text_content = fix_urls(text_content)
|
|
||||||
return (text_content.encode('utf-8'), file_path)
|
|
||||||
|
|
||||||
|
|
||||||
def get_pdfjs_res(path):
|
def get_pdfjs_res(path):
|
||||||
@ -193,7 +207,10 @@ def _read_from_system(system_path, names):
|
|||||||
full_path = os.path.join(system_path, name)
|
full_path = os.path.join(system_path, name)
|
||||||
with open(full_path, 'rb') as f:
|
with open(full_path, 'rb') as f:
|
||||||
return (f.read(), full_path)
|
return (f.read(), full_path)
|
||||||
except OSError:
|
except FileNotFoundError:
|
||||||
|
continue
|
||||||
|
except OSError as e:
|
||||||
|
log.misc.warning("OSError while reading PDF.js file: {}".format(e))
|
||||||
continue
|
continue
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
@ -206,3 +223,22 @@ def is_available():
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def should_use_pdfjs(mimetype, url):
|
||||||
|
"""Check whether PDF.js should be used."""
|
||||||
|
# e.g. 'blob:qute%3A///b45250b3-787e-44d1-a8d8-c2c90f81f981'
|
||||||
|
is_download_url = (url.scheme() == 'blob' and
|
||||||
|
QUrl(url.path()).scheme() == 'qute')
|
||||||
|
is_pdf = mimetype in ['application/pdf', 'application/x-pdf']
|
||||||
|
return is_pdf and not is_download_url and config.val.content.pdfjs
|
||||||
|
|
||||||
|
|
||||||
|
def get_main_url(filename):
|
||||||
|
"""Get the URL to be opened to view a local PDF."""
|
||||||
|
url = QUrl('qute://pdfjs/web/viewer.html')
|
||||||
|
query = QUrlQuery()
|
||||||
|
query.addQueryItem('filename', filename) # read from our JS
|
||||||
|
query.addQueryItem('file', '') # to avoid pdfjs opening the default PDF
|
||||||
|
url.setQuery(query)
|
||||||
|
return url
|
||||||
|
@ -29,7 +29,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
|
|||||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import message, usertypes, log, urlutils, utils
|
from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug
|
||||||
from qutebrowser.browser import downloads
|
from qutebrowser.browser import downloads
|
||||||
from qutebrowser.browser.webkit import http
|
from qutebrowser.browser.webkit import http
|
||||||
from qutebrowser.browser.webkit.network import networkmanager
|
from qutebrowser.browser.webkit.network import networkmanager
|
||||||
@ -307,7 +307,14 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||||||
"""Handle QNetworkReply errors."""
|
"""Handle QNetworkReply errors."""
|
||||||
if code == QNetworkReply.OperationCanceledError:
|
if code == QNetworkReply.OperationCanceledError:
|
||||||
return
|
return
|
||||||
self._die(self._reply.errorString())
|
|
||||||
|
if self._reply is None:
|
||||||
|
error = "Unknown error: {}".format(
|
||||||
|
debug.qenum_key(QNetworkReply, code))
|
||||||
|
else:
|
||||||
|
error = self._reply.errorString()
|
||||||
|
|
||||||
|
self._die(error)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _on_read_timer_timeout(self):
|
def _on_read_timer_timeout(self):
|
||||||
|
@ -24,62 +24,72 @@ Module attributes:
|
|||||||
_HANDLERS: The handlers registered via decorators.
|
_HANDLERS: The handlers registered via decorators.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import html
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import textwrap
|
import textwrap
|
||||||
import mimetypes
|
|
||||||
import urllib
|
import urllib
|
||||||
import collections
|
import collections
|
||||||
|
import base64
|
||||||
|
|
||||||
import pkg_resources
|
try:
|
||||||
import sip
|
import secrets
|
||||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
except ImportError:
|
||||||
|
# New in Python 3.6
|
||||||
|
secrets = None
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QUrlQuery, QUrl, qVersion
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
|
from qutebrowser.browser import pdfjs, downloads
|
||||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
from qutebrowser.config import config, configdata, configexc, configdiff
|
||||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||||
objreg, urlutils)
|
objreg, urlutils)
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.qt import sip
|
||||||
|
|
||||||
|
|
||||||
pyeval_output = ":pyeval was never called"
|
pyeval_output = ":pyeval was never called"
|
||||||
spawn_output = ":spawn was never called"
|
spawn_output = ":spawn was never called"
|
||||||
|
csrf_token = None
|
||||||
|
|
||||||
|
|
||||||
_HANDLERS = {}
|
_HANDLERS = {}
|
||||||
|
|
||||||
|
|
||||||
class NoHandlerFound(Exception):
|
class Error(Exception):
|
||||||
|
|
||||||
"""Raised when no handler was found for the given URL."""
|
"""Exception for generic errors on a qute:// page."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QuteSchemeOSError(Exception):
|
class NotFoundError(Error):
|
||||||
|
|
||||||
"""Called when there was an OSError inside a handler."""
|
"""Raised when the given URL was not found."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QuteSchemeError(Exception):
|
class SchemeOSError(Error):
|
||||||
|
|
||||||
"""Exception to signal that a handler should return an ErrorReply.
|
"""Raised when there was an OSError inside a handler."""
|
||||||
|
|
||||||
Attributes correspond to the arguments in
|
pass
|
||||||
networkreply.ErrorNetworkReply.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
errorstring: Error string to print.
|
|
||||||
error: Numerical error value.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, errorstring, error):
|
class UrlInvalidError(Error):
|
||||||
self.errorstring = errorstring
|
|
||||||
self.error = error
|
"""Raised when an invalid URL was opened."""
|
||||||
super().__init__(errorstring)
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RequestDeniedError(Error):
|
||||||
|
|
||||||
|
"""Raised when the request is forbidden."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Redirect(Exception):
|
class Redirect(Exception):
|
||||||
@ -101,12 +111,10 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
_name: The 'foo' part of qute://foo
|
_name: The 'foo' part of qute://foo
|
||||||
backend: Limit which backends the handler can run with.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, backend=None):
|
def __init__(self, name):
|
||||||
self._name = name
|
self._name = name
|
||||||
self._backend = backend
|
|
||||||
self._function = None
|
self._function = None
|
||||||
|
|
||||||
def __call__(self, function):
|
def __call__(self, function):
|
||||||
@ -116,19 +124,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
|||||||
|
|
||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self, *args, **kwargs):
|
||||||
"""Call the underlying function."""
|
"""Call the underlying function."""
|
||||||
if self._backend is not None and objects.backend != self._backend:
|
return self._function(*args, **kwargs)
|
||||||
return self.wrong_backend_handler(*args, **kwargs)
|
|
||||||
else:
|
|
||||||
return self._function(*args, **kwargs)
|
|
||||||
|
|
||||||
def wrong_backend_handler(self, url):
|
|
||||||
"""Show an error page about using the invalid backend."""
|
|
||||||
html = jinja.render('error.html',
|
|
||||||
title="Error while opening qute://url",
|
|
||||||
url=url.toDisplayString(),
|
|
||||||
error='{} is not available with this '
|
|
||||||
'backend'.format(url.toDisplayString()))
|
|
||||||
return 'text/html', html
|
|
||||||
|
|
||||||
|
|
||||||
def data_for_url(url):
|
def data_for_url(url):
|
||||||
@ -170,15 +166,13 @@ def data_for_url(url):
|
|||||||
try:
|
try:
|
||||||
handler = _HANDLERS[host]
|
handler = _HANDLERS[host]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NoHandlerFound(url)
|
raise NotFoundError("No handler found for {}".format(
|
||||||
|
url.toDisplayString()))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mimetype, data = handler(url)
|
mimetype, data = handler(url)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
# FIXME:qtwebengine how to handle this?
|
raise SchemeOSError(e)
|
||||||
raise QuteSchemeOSError(e)
|
|
||||||
except QuteSchemeError:
|
|
||||||
raise
|
|
||||||
|
|
||||||
assert mimetype is not None, url
|
assert mimetype is not None, url
|
||||||
if mimetype == 'text/html' and isinstance(data, str):
|
if mimetype == 'text/html' and isinstance(data, str):
|
||||||
@ -196,11 +190,11 @@ def qute_bookmarks(_url):
|
|||||||
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
|
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
|
||||||
key=lambda x: x[0]) # Sort by name
|
key=lambda x: x[0]) # Sort by name
|
||||||
|
|
||||||
html = jinja.render('bookmarks.html',
|
src = jinja.render('bookmarks.html',
|
||||||
title='Bookmarks',
|
title='Bookmarks',
|
||||||
bookmarks=bookmarks,
|
bookmarks=bookmarks,
|
||||||
quickmarks=quickmarks)
|
quickmarks=quickmarks)
|
||||||
return 'text/html', html
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
@add_handler('tabs')
|
@add_handler('tabs')
|
||||||
@ -218,10 +212,10 @@ def qute_tabs(_url):
|
|||||||
urlstr = tab.url().toDisplayString()
|
urlstr = tab.url().toDisplayString()
|
||||||
tabs[str(win_id)].append((tab.title(), urlstr))
|
tabs[str(win_id)].append((tab.title(), urlstr))
|
||||||
|
|
||||||
html = jinja.render('tabs.html',
|
src = jinja.render('tabs.html',
|
||||||
title='Tabs',
|
title='Tabs',
|
||||||
tab_list_by_window=tabs)
|
tab_list_by_window=tabs)
|
||||||
return 'text/html', html
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
def history_data(start_time, offset=None):
|
def history_data(start_time, offset=None):
|
||||||
@ -241,8 +235,9 @@ def history_data(start_time, offset=None):
|
|||||||
end_time = start_time - 24*60*60
|
end_time = start_time - 24*60*60
|
||||||
entries = hist.entries_between(end_time, start_time)
|
entries = hist.entries_between(end_time, start_time)
|
||||||
|
|
||||||
return [{"url": e.url, "title": e.title or e.url, "time": e.atime}
|
return [{"url": e.url,
|
||||||
for e in entries]
|
"title": html.escape(e.title) or html.escape(e.url),
|
||||||
|
"time": e.atime} for e in entries]
|
||||||
|
|
||||||
|
|
||||||
@add_handler('history')
|
@add_handler('history')
|
||||||
@ -252,14 +247,14 @@ def qute_history(url):
|
|||||||
try:
|
try:
|
||||||
offset = QUrlQuery(url).queryItemValue("offset")
|
offset = QUrlQuery(url).queryItemValue("offset")
|
||||||
offset = int(offset) if offset else None
|
offset = int(offset) if offset else None
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
raise QuteSchemeError("Query parameter offset is invalid", e)
|
raise UrlInvalidError("Query parameter offset is invalid")
|
||||||
# Use start_time in query or current time.
|
# Use start_time in query or current time.
|
||||||
try:
|
try:
|
||||||
start_time = QUrlQuery(url).queryItemValue("start_time")
|
start_time = QUrlQuery(url).queryItemValue("start_time")
|
||||||
start_time = float(start_time) if start_time else time.time()
|
start_time = float(start_time) if start_time else time.time()
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
raise QuteSchemeError("Query parameter start_time is invalid", e)
|
raise UrlInvalidError("Query parameter start_time is invalid")
|
||||||
|
|
||||||
return 'text/html', json.dumps(history_data(start_time, offset))
|
return 'text/html', json.dumps(history_data(start_time, offset))
|
||||||
else:
|
else:
|
||||||
@ -281,31 +276,31 @@ def qute_javascript(url):
|
|||||||
path = "javascript" + os.sep.join(path.split('/'))
|
path = "javascript" + os.sep.join(path.split('/'))
|
||||||
return 'text/html', utils.read_file(path, binary=False)
|
return 'text/html', utils.read_file(path, binary=False)
|
||||||
else:
|
else:
|
||||||
raise QuteSchemeError("No file specified", ValueError())
|
raise UrlInvalidError("No file specified")
|
||||||
|
|
||||||
|
|
||||||
@add_handler('pyeval')
|
@add_handler('pyeval')
|
||||||
def qute_pyeval(_url):
|
def qute_pyeval(_url):
|
||||||
"""Handler for qute://pyeval."""
|
"""Handler for qute://pyeval."""
|
||||||
html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
|
src = jinja.render('pre.html', title='pyeval', content=pyeval_output)
|
||||||
return 'text/html', html
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
@add_handler('spawn-output')
|
@add_handler('spawn-output')
|
||||||
def qute_spawn_output(_url):
|
def qute_spawn_output(_url):
|
||||||
"""Handler for qute://spawn-output."""
|
"""Handler for qute://spawn-output."""
|
||||||
html = jinja.render('pre.html', title='spawn output', content=spawn_output)
|
src = jinja.render('pre.html', title='spawn output', content=spawn_output)
|
||||||
return 'text/html', html
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
@add_handler('version')
|
@add_handler('version')
|
||||||
@add_handler('verizon')
|
@add_handler('verizon')
|
||||||
def qute_version(_url):
|
def qute_version(_url):
|
||||||
"""Handler for qute://version."""
|
"""Handler for qute://version."""
|
||||||
html = jinja.render('version.html', title='Version info',
|
src = jinja.render('version.html', title='Version info',
|
||||||
version=version.version(),
|
version=version.version(),
|
||||||
copyright=qutebrowser.__copyright__)
|
copyright=qutebrowser.__copyright__)
|
||||||
return 'text/html', html
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
@add_handler('plainlog')
|
@add_handler('plainlog')
|
||||||
@ -323,8 +318,8 @@ def qute_plainlog(url):
|
|||||||
if not level:
|
if not level:
|
||||||
level = 'vdebug'
|
level = 'vdebug'
|
||||||
text = log.ram_handler.dump_log(html=False, level=level)
|
text = log.ram_handler.dump_log(html=False, level=level)
|
||||||
html = jinja.render('pre.html', title='log', content=text)
|
src = jinja.render('pre.html', title='log', content=text)
|
||||||
return 'text/html', html
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
@add_handler('log')
|
@add_handler('log')
|
||||||
@ -343,8 +338,8 @@ def qute_log(url):
|
|||||||
level = 'vdebug'
|
level = 'vdebug'
|
||||||
html_log = log.ram_handler.dump_log(html=True, level=level)
|
html_log = log.ram_handler.dump_log(html=True, level=level)
|
||||||
|
|
||||||
html = jinja.render('log.html', title='log', content=html_log)
|
src = jinja.render('log.html', title='log', content=html_log)
|
||||||
return 'text/html', html
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
@add_handler('gpl')
|
@add_handler('gpl')
|
||||||
@ -353,6 +348,23 @@ def qute_gpl(_url):
|
|||||||
return 'text/html', utils.read_file('html/license.html')
|
return 'text/html', utils.read_file('html/license.html')
|
||||||
|
|
||||||
|
|
||||||
|
def _asciidoc_fallback_path(html_path):
|
||||||
|
"""Fall back to plaintext asciidoc if the HTML is unavailable."""
|
||||||
|
asciidoc_path = html_path.replace('.html', '.asciidoc')
|
||||||
|
asciidoc_paths = [asciidoc_path]
|
||||||
|
if asciidoc_path.startswith('html/doc/'):
|
||||||
|
asciidoc_paths += [asciidoc_path.replace('html/doc/', '../doc/help/'),
|
||||||
|
asciidoc_path.replace('html/doc/', '../doc/')]
|
||||||
|
|
||||||
|
for path in asciidoc_paths:
|
||||||
|
try:
|
||||||
|
return utils.read_file(path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@add_handler('help')
|
@add_handler('help')
|
||||||
def qute_help(url):
|
def qute_help(url):
|
||||||
"""Handler for qute://help."""
|
"""Handler for qute://help."""
|
||||||
@ -370,23 +382,14 @@ def qute_help(url):
|
|||||||
try:
|
try:
|
||||||
bdata = utils.read_file(path, binary=True)
|
bdata = utils.read_file(path, binary=True)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise QuteSchemeOSError(e)
|
raise SchemeOSError(e)
|
||||||
mimetype, _encoding = mimetypes.guess_type(urlpath)
|
mimetype = utils.guess_mimetype(urlpath)
|
||||||
assert mimetype is not None, url
|
|
||||||
return mimetype, bdata
|
return mimetype, bdata
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = utils.read_file(path)
|
data = utils.read_file(path)
|
||||||
except OSError:
|
except OSError:
|
||||||
# No .html around, let's see if we find the asciidoc
|
asciidoc = _asciidoc_fallback_path(path)
|
||||||
asciidoc_path = path.replace('.html', '.asciidoc')
|
|
||||||
if asciidoc_path.startswith('html/doc/'):
|
|
||||||
asciidoc_path = asciidoc_path.replace('html/doc/', '../doc/help/')
|
|
||||||
|
|
||||||
try:
|
|
||||||
asciidoc = utils.read_file(asciidoc_path)
|
|
||||||
except OSError:
|
|
||||||
asciidoc = None
|
|
||||||
|
|
||||||
if asciidoc is None:
|
if asciidoc is None:
|
||||||
raise
|
raise
|
||||||
@ -412,17 +415,6 @@ def qute_help(url):
|
|||||||
return 'text/html', data
|
return 'text/html', data
|
||||||
|
|
||||||
|
|
||||||
@add_handler('backend-warning')
|
|
||||||
def qute_backend_warning(_url):
|
|
||||||
"""Handler for qute://backend-warning."""
|
|
||||||
html = jinja.render('backend-warning.html',
|
|
||||||
distribution=version.distribution(),
|
|
||||||
Distribution=version.Distribution,
|
|
||||||
version=pkg_resources.parse_version,
|
|
||||||
title="Legacy backend warning")
|
|
||||||
return 'text/html', html
|
|
||||||
|
|
||||||
|
|
||||||
def _qute_settings_set(url):
|
def _qute_settings_set(url):
|
||||||
"""Handler for qute://settings/set."""
|
"""Handler for qute://settings/set."""
|
||||||
query = QUrlQuery(url)
|
query = QUrlQuery(url)
|
||||||
@ -447,13 +439,29 @@ def _qute_settings_set(url):
|
|||||||
@add_handler('settings')
|
@add_handler('settings')
|
||||||
def qute_settings(url):
|
def qute_settings(url):
|
||||||
"""Handler for qute://settings. View/change qute configuration."""
|
"""Handler for qute://settings. View/change qute configuration."""
|
||||||
|
global csrf_token
|
||||||
|
|
||||||
if url.path() == '/set':
|
if url.path() == '/set':
|
||||||
|
if url.password() != csrf_token:
|
||||||
|
message.error("Invalid CSRF token for qute://settings!")
|
||||||
|
raise RequestDeniedError("Invalid CSRF token!")
|
||||||
return _qute_settings_set(url)
|
return _qute_settings_set(url)
|
||||||
|
|
||||||
html = jinja.render('settings.html', title='settings',
|
# Requests to qute://settings/set should only be allowed from
|
||||||
configdata=configdata,
|
# qute://settings. As an additional security precaution, we generate a CSRF
|
||||||
confget=config.instance.get_str)
|
# token to use here.
|
||||||
return 'text/html', html
|
if secrets:
|
||||||
|
csrf_token = secrets.token_urlsafe()
|
||||||
|
else:
|
||||||
|
# On Python < 3.6, from secrets.py
|
||||||
|
token = base64.urlsafe_b64encode(os.urandom(32))
|
||||||
|
csrf_token = token.rstrip(b'=').decode('ascii')
|
||||||
|
|
||||||
|
src = jinja.render('settings.html', title='settings',
|
||||||
|
configdata=configdata,
|
||||||
|
confget=config.instance.get_str,
|
||||||
|
csrf_token=csrf_token)
|
||||||
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
@add_handler('bindings')
|
@add_handler('bindings')
|
||||||
@ -467,9 +475,9 @@ def qute_bindings(_url):
|
|||||||
for mode in modes:
|
for mode in modes:
|
||||||
bindings[mode] = config.key_instance.get_bindings_for(mode)
|
bindings[mode] = config.key_instance.get_bindings_for(mode)
|
||||||
|
|
||||||
html = jinja.render('bindings.html', title='Bindings',
|
src = jinja.render('bindings.html', title='Bindings',
|
||||||
bindings=bindings)
|
bindings=bindings)
|
||||||
return 'text/html', html
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
@add_handler('back')
|
@add_handler('back')
|
||||||
@ -478,10 +486,10 @@ def qute_back(url):
|
|||||||
|
|
||||||
Simple page to free ram / lazy load a site, goes back on focusing the tab.
|
Simple page to free ram / lazy load a site, goes back on focusing the tab.
|
||||||
"""
|
"""
|
||||||
html = jinja.render(
|
src = jinja.render(
|
||||||
'back.html',
|
'back.html',
|
||||||
title='Suspended: ' + urllib.parse.unquote(url.fragment()))
|
title='Suspended: ' + urllib.parse.unquote(url.fragment()))
|
||||||
return 'text/html', html
|
return 'text/html', src
|
||||||
|
|
||||||
|
|
||||||
@add_handler('configdiff')
|
@add_handler('configdiff')
|
||||||
@ -504,3 +512,59 @@ def qute_pastebin_version(_url):
|
|||||||
"""Handler that pastebins the version string."""
|
"""Handler that pastebins the version string."""
|
||||||
version.pastebin_version()
|
version.pastebin_version()
|
||||||
return 'text/plain', b'Paste called.'
|
return 'text/plain', b'Paste called.'
|
||||||
|
|
||||||
|
|
||||||
|
@add_handler('pdfjs')
|
||||||
|
def qute_pdfjs(url):
|
||||||
|
"""Handler for qute://pdfjs. Return the pdf.js viewer."""
|
||||||
|
if url.path() == '/file':
|
||||||
|
filename = QUrlQuery(url).queryItemValue('filename')
|
||||||
|
if not filename:
|
||||||
|
raise UrlInvalidError("Missing filename")
|
||||||
|
if '/' in filename or os.sep in filename:
|
||||||
|
raise RequestDeniedError("Path separator in filename.")
|
||||||
|
|
||||||
|
path = os.path.join(downloads.temp_download_manager.get_tmpdir().name,
|
||||||
|
filename)
|
||||||
|
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
mimetype = utils.guess_mimetype(filename, fallback=True)
|
||||||
|
return mimetype, data
|
||||||
|
|
||||||
|
if url.path() == '/web/viewer.html':
|
||||||
|
filename = QUrlQuery(url).queryItemValue("filename")
|
||||||
|
if not filename:
|
||||||
|
raise UrlInvalidError("Missing filename")
|
||||||
|
data = pdfjs.generate_pdfjs_page(filename, url)
|
||||||
|
return 'text/html', data
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = pdfjs.get_pdfjs_res(url.path())
|
||||||
|
except pdfjs.PDFJSNotFound as e:
|
||||||
|
# Logging as the error might get lost otherwise since we're not showing
|
||||||
|
# the error page if a single asset is missing. This way we don't lose
|
||||||
|
# information, as the failed pdfjs requests are still in the log.
|
||||||
|
log.misc.warning(
|
||||||
|
"pdfjs resource requested but not found: {}".format(e.path))
|
||||||
|
raise NotFoundError("Can't find pdfjs resource '{}'".format(e.path))
|
||||||
|
else:
|
||||||
|
mimetype = utils.guess_mimetype(url.fileName(), fallback=True)
|
||||||
|
return mimetype, data
|
||||||
|
|
||||||
|
|
||||||
|
@add_handler('warning')
|
||||||
|
def qute_warning(url):
|
||||||
|
"""Handler for qute://warning."""
|
||||||
|
path = url.path()
|
||||||
|
if path == '/old-qt':
|
||||||
|
src = jinja.render('warning-old-qt.html',
|
||||||
|
title='Old Qt warning',
|
||||||
|
qt_version=qVersion())
|
||||||
|
elif path == '/webkit':
|
||||||
|
src = jinja.render('warning-webkit.html',
|
||||||
|
title='QtWebKit backend warning')
|
||||||
|
else:
|
||||||
|
raise NotFoundError("Invalid warning page {}".format(path))
|
||||||
|
return 'text/html', src
|
||||||
|
@ -34,21 +34,22 @@ class CallSuper(Exception):
|
|||||||
"""Raised when the caller should call the superclass instead."""
|
"""Raised when the caller should call the superclass instead."""
|
||||||
|
|
||||||
|
|
||||||
def custom_headers():
|
def custom_headers(url):
|
||||||
"""Get the combined custom headers."""
|
"""Get the combined custom headers."""
|
||||||
headers = {}
|
headers = {}
|
||||||
|
|
||||||
dnt_config = config.val.content.headers.do_not_track
|
dnt_config = config.instance.get('content.headers.do_not_track', url=url)
|
||||||
if dnt_config is not None:
|
if dnt_config is not None:
|
||||||
dnt = b'1' if dnt_config else b'0'
|
dnt = b'1' if dnt_config else b'0'
|
||||||
headers[b'DNT'] = dnt
|
headers[b'DNT'] = dnt
|
||||||
headers[b'X-Do-Not-Track'] = dnt
|
headers[b'X-Do-Not-Track'] = dnt
|
||||||
|
|
||||||
conf_headers = config.val.content.headers.custom
|
conf_headers = config.instance.get('content.headers.custom', url=url)
|
||||||
for header, value in conf_headers.items():
|
for header, value in conf_headers.items():
|
||||||
headers[header.encode('ascii')] = value.encode('ascii')
|
headers[header.encode('ascii')] = value.encode('ascii')
|
||||||
|
|
||||||
accept_language = config.val.content.headers.accept_language
|
accept_language = config.instance.get('content.headers.accept_language',
|
||||||
|
url=url)
|
||||||
if accept_language is not None:
|
if accept_language is not None:
|
||||||
headers[b'Accept-Language'] = accept_language.encode('ascii')
|
headers[b'Accept-Language'] = accept_language.encode('ascii')
|
||||||
|
|
||||||
@ -156,7 +157,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
|||||||
Return:
|
Return:
|
||||||
True if the error should be ignored, False otherwise.
|
True if the error should be ignored, False otherwise.
|
||||||
"""
|
"""
|
||||||
ssl_strict = config.val.content.ssl_strict
|
ssl_strict = config.instance.get('content.ssl_strict', url=url)
|
||||||
log.webview.debug("Certificate errors {!r}, strict {}".format(
|
log.webview.debug("Certificate errors {!r}, strict {}".format(
|
||||||
errors, ssl_strict))
|
errors, ssl_strict))
|
||||||
|
|
||||||
@ -213,7 +214,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
|||||||
The Question object if a question was asked (and blocking=False),
|
The Question object if a question was asked (and blocking=False),
|
||||||
None otherwise.
|
None otherwise.
|
||||||
"""
|
"""
|
||||||
config_val = config.instance.get(option)
|
config_val = config.instance.get(option, url=url)
|
||||||
if config_val == 'ask':
|
if config_val == 'ask':
|
||||||
if url.isValid():
|
if url.isValid():
|
||||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||||
@ -273,7 +274,7 @@ def get_tab(win_id, target):
|
|||||||
return tabbed_browser.tabopen(url=None, background=bg_tab)
|
return tabbed_browser.tabopen(url=None, background=bg_tab)
|
||||||
|
|
||||||
|
|
||||||
def get_user_stylesheet(url=None):
|
def get_user_stylesheet(searching=False, url=None):
|
||||||
"""Get the combined user-stylesheet.
|
"""Get the combined user-stylesheet.
|
||||||
|
|
||||||
If `url` is given and there's no overridden stylesheet, return
|
If `url` is given and there's no overridden stylesheet, return
|
||||||
@ -289,7 +290,8 @@ def get_user_stylesheet(url=None):
|
|||||||
with open(filename, 'r', encoding='utf-8') as f:
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
css += f.read()
|
css += f.read()
|
||||||
|
|
||||||
if not config.val.scrolling.bar:
|
if (config.val.scrolling.bar == 'never' or
|
||||||
|
config.val.scrolling.bar == 'when-searching' and not searching):
|
||||||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||||
|
|
||||||
return css
|
return css
|
||||||
@ -319,10 +321,10 @@ def netrc_authentication(url, authenticator):
|
|||||||
(user, _account, password) = authenticators
|
(user, _account, password) = authenticators
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
log.misc.debug("No .netrc file found")
|
log.misc.debug("No .netrc file found")
|
||||||
except OSError:
|
except OSError as e:
|
||||||
log.misc.exception("Unable to read the netrc file")
|
log.misc.exception("Unable to read the netrc file: {}".format(e))
|
||||||
except netrc.NetrcParseError:
|
except netrc.NetrcParseError as e:
|
||||||
log.misc.exception("Error when parsing the netrc file")
|
log.misc.exception("Error when parsing the netrc file: {}".format(e))
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
return False
|
return False
|
||||||
|
@ -240,8 +240,7 @@ class BookmarkManager(UrlMarkManager):
|
|||||||
|
|
||||||
def _init_lineparser(self):
|
def _init_lineparser(self):
|
||||||
bookmarks_directory = os.path.join(standarddir.config(), 'bookmarks')
|
bookmarks_directory = os.path.join(standarddir.config(), 'bookmarks')
|
||||||
if not os.path.isdir(bookmarks_directory):
|
os.makedirs(bookmarks_directory, exist_ok=True)
|
||||||
os.makedirs(bookmarks_directory)
|
|
||||||
|
|
||||||
bookmarks_subdir = os.path.join('bookmarks', 'urls')
|
bookmarks_subdir = os.path.join('bookmarks', 'urls')
|
||||||
self._lineparser = lineparser.LineParser(
|
self._lineparser = lineparser.LineParser(
|
||||||
|
@ -17,14 +17,8 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""Generic web element related code.
|
"""Generic web element related code."""
|
||||||
|
|
||||||
Module attributes:
|
|
||||||
Group: Enum for different kinds of groups.
|
|
||||||
SELECTORS: CSS selectors for different groups of elements.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import enum
|
|
||||||
import collections.abc
|
import collections.abc
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer
|
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer
|
||||||
@ -36,25 +30,6 @@ from qutebrowser.mainwindow import mainwindow
|
|||||||
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
|
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
|
||||||
|
|
||||||
|
|
||||||
Group = enum.Enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
|
|
||||||
|
|
||||||
|
|
||||||
SELECTORS = {
|
|
||||||
Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, '
|
|
||||||
'frame, iframe, link, summary, [onclick], [onmousedown], '
|
|
||||||
'[role=link], [role=option], [role=button], img, '
|
|
||||||
# Angular 1 selectors
|
|
||||||
'[ng-click], [ngClick], [data-ng-click], [x-ng-click]'),
|
|
||||||
Group.links: 'a[href], area[href], link[href], [role=link][href]',
|
|
||||||
Group.images: 'img',
|
|
||||||
Group.url: '[src], [href]',
|
|
||||||
Group.inputs: ('input[type=text], input[type=email], input[type=url], '
|
|
||||||
'input[type=tel], input[type=number], '
|
|
||||||
'input[type=password], input[type=search], '
|
|
||||||
'input:not([type]), textarea'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
|
|
||||||
"""Base class for WebElement errors."""
|
"""Base class for WebElement errors."""
|
||||||
@ -69,6 +44,18 @@ class OrphanedError(Error):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def css_selector(group, url):
|
||||||
|
"""Get a CSS selector for the given group/URL."""
|
||||||
|
selectors = config.instance.get('hints.selectors', url)
|
||||||
|
if group not in selectors:
|
||||||
|
selectors = config.val.hints.selectors
|
||||||
|
|
||||||
|
if group not in selectors:
|
||||||
|
raise Error("Undefined hinting group {!r}".format(group))
|
||||||
|
|
||||||
|
return ','.join(selectors[group])
|
||||||
|
|
||||||
|
|
||||||
class AbstractWebElement(collections.abc.MutableMapping):
|
class AbstractWebElement(collections.abc.MutableMapping):
|
||||||
|
|
||||||
"""A wrapper around QtWebKit/QtWebEngine web element.
|
"""A wrapper around QtWebKit/QtWebEngine web element.
|
||||||
@ -139,6 +126,18 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
"""Set the element value."""
|
"""Set the element value."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def dispatch_event(self, event, bubbles=False,
|
||||||
|
cancelable=False, composed=False):
|
||||||
|
"""Dispatch an event to the element.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bubbles: Whether this event should bubble.
|
||||||
|
cancelable: Whether this event can be cancelled.
|
||||||
|
composed: Whether the event will trigger listeners outside of a
|
||||||
|
shadow root.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def insert_text(self, text):
|
def insert_text(self, text):
|
||||||
"""Insert the given text into the element."""
|
"""Insert the given text into the element."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -28,6 +28,10 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
|||||||
|
|
||||||
"""A wrapper over a QWebEngineCertificateError."""
|
"""A wrapper over a QWebEngineCertificateError."""
|
||||||
|
|
||||||
|
def __init__(self, error):
|
||||||
|
super().__init__(error)
|
||||||
|
self.ignore = False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self._error.errorDescription()
|
return self._error.errorDescription()
|
||||||
|
|
||||||
@ -37,5 +41,8 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
|||||||
self._error.error()),
|
self._error.error()),
|
||||||
string=str(self))
|
string=str(self))
|
||||||
|
|
||||||
|
def url(self):
|
||||||
|
return self._error.url()
|
||||||
|
|
||||||
def is_overridable(self):
|
def is_overridable(self):
|
||||||
return self._error.isOverridable()
|
return self._error.isOverridable()
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
"""A request interceptor taking care of adblocking and custom headers."""
|
"""A request interceptor taking care of adblocking and custom headers."""
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QUrl
|
||||||
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
|
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
|
||||||
QWebEngineUrlRequestInfo)
|
QWebEngineUrlRequestInfo)
|
||||||
|
|
||||||
@ -68,15 +69,29 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
|||||||
info.firstPartyUrl().toDisplayString(),
|
info.firstPartyUrl().toDisplayString(),
|
||||||
resource_type, navigation_type))
|
resource_type, navigation_type))
|
||||||
|
|
||||||
|
url = info.requestUrl()
|
||||||
|
first_party = info.firstPartyUrl()
|
||||||
|
|
||||||
|
if ((url.scheme(), url.host(), url.path()) ==
|
||||||
|
('qute', 'settings', '/set')):
|
||||||
|
if (first_party != QUrl('qute://settings/') or
|
||||||
|
info.resourceType() !=
|
||||||
|
QWebEngineUrlRequestInfo.ResourceTypeXhr):
|
||||||
|
log.webview.warning("Blocking malicious request from {} to {}"
|
||||||
|
.format(first_party.toDisplayString(),
|
||||||
|
url.toDisplayString()))
|
||||||
|
info.block(True)
|
||||||
|
return
|
||||||
|
|
||||||
# FIXME:qtwebengine only block ads for NavigationTypeOther?
|
# FIXME:qtwebengine only block ads for NavigationTypeOther?
|
||||||
if self._host_blocker.is_blocked(info.requestUrl()):
|
if self._host_blocker.is_blocked(url, first_party):
|
||||||
log.webview.info("Request to {} blocked by host blocker.".format(
|
log.webview.info("Request to {} blocked by host blocker.".format(
|
||||||
info.requestUrl().host()))
|
url.host()))
|
||||||
info.block(True)
|
info.block(True)
|
||||||
|
|
||||||
for header, value in shared.custom_headers():
|
for header, value in shared.custom_headers(url=url):
|
||||||
info.setHttpHeader(header, value)
|
info.setHttpHeader(header, value)
|
||||||
|
|
||||||
user_agent = config.val.content.headers.user_agent
|
user_agent = config.instance.get('content.headers.user_agent', url=url)
|
||||||
if user_agent is not None:
|
if user_agent is not None:
|
||||||
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
|
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
|
||||||
|
@ -21,10 +21,12 @@
|
|||||||
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
|
|
||||||
from PyQt5.QtCore import QLibraryInfo
|
from PyQt5.QtCore import QLibraryInfo
|
||||||
from qutebrowser.utils import log, message
|
from qutebrowser.utils import log, message, standarddir, qtutils
|
||||||
|
|
||||||
dict_version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
|
dict_version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
|
||||||
|
|
||||||
@ -39,9 +41,12 @@ def version(filename):
|
|||||||
return tuple(int(n) for n in match.group('version').split('-'))
|
return tuple(int(n) for n in match.group('version').split('-'))
|
||||||
|
|
||||||
|
|
||||||
def dictionary_dir():
|
def dictionary_dir(old=False):
|
||||||
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
|
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
|
||||||
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
|
if qtutils.version_check('5.10', compiled=False) and not old:
|
||||||
|
datapath = standarddir.data()
|
||||||
|
else:
|
||||||
|
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
|
||||||
return os.path.join(datapath, 'qtwebengine_dictionaries')
|
return os.path.join(datapath, 'qtwebengine_dictionaries')
|
||||||
|
|
||||||
|
|
||||||
@ -73,3 +78,16 @@ def local_filename(code):
|
|||||||
"""
|
"""
|
||||||
all_installed = local_files(code)
|
all_installed = local_files(code)
|
||||||
return os.path.splitext(all_installed[0])[0] if all_installed else None
|
return os.path.splitext(all_installed[0])[0] if all_installed else None
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
"""Initialize the dictionary path if supported."""
|
||||||
|
if qtutils.version_check('5.10', compiled=False):
|
||||||
|
new_dir = dictionary_dir()
|
||||||
|
old_dir = dictionary_dir(old=True)
|
||||||
|
os.environ['QTWEBENGINE_DICTIONARIES_PATH'] = new_dir
|
||||||
|
try:
|
||||||
|
if os.path.exists(old_dir) and not os.path.exists(new_dir):
|
||||||
|
shutil.copytree(old_dir, new_dir)
|
||||||
|
except OSError:
|
||||||
|
log.misc.exception("Failed to copy old dictionaries")
|
||||||
|
@ -27,7 +27,7 @@ import functools
|
|||||||
from PyQt5.QtCore import pyqtSlot, Qt
|
from PyQt5.QtCore import pyqtSlot, Qt
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
|
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
|
||||||
|
|
||||||
from qutebrowser.browser import downloads
|
from qutebrowser.browser import downloads, pdfjs
|
||||||
from qutebrowser.utils import debug, usertypes, message, log, qtutils
|
from qutebrowser.utils import debug, usertypes, message, log, qtutils
|
||||||
|
|
||||||
|
|
||||||
@ -212,15 +212,19 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
|||||||
def handle_download(self, qt_item):
|
def handle_download(self, qt_item):
|
||||||
"""Start a download coming from a QWebEngineProfile."""
|
"""Start a download coming from a QWebEngineProfile."""
|
||||||
suggested_filename = _get_suggested_filename(qt_item.path())
|
suggested_filename = _get_suggested_filename(qt_item.path())
|
||||||
|
use_pdfjs = pdfjs.should_use_pdfjs(qt_item.mimeType(), qt_item.url())
|
||||||
|
|
||||||
download = DownloadItem(qt_item)
|
download = DownloadItem(qt_item)
|
||||||
self._init_item(download, auto_remove=False,
|
self._init_item(download, auto_remove=use_pdfjs,
|
||||||
suggested_filename=suggested_filename)
|
suggested_filename=suggested_filename)
|
||||||
|
|
||||||
if self._mhtml_target is not None:
|
if self._mhtml_target is not None:
|
||||||
download.set_target(self._mhtml_target)
|
download.set_target(self._mhtml_target)
|
||||||
self._mhtml_target = None
|
self._mhtml_target = None
|
||||||
return
|
return
|
||||||
|
if use_pdfjs:
|
||||||
|
download.set_target(downloads.PDFJSDownloadTarget())
|
||||||
|
return
|
||||||
|
|
||||||
filename = downloads.immediate_download_path()
|
filename = downloads.immediate_download_path()
|
||||||
if filename is not None:
|
if filename is not None:
|
||||||
|
@ -135,6 +135,10 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self._js_call('set_value', value)
|
self._js_call('set_value', value)
|
||||||
|
|
||||||
|
def dispatch_event(self, event, bubbles=False,
|
||||||
|
cancelable=False, composed=False):
|
||||||
|
self._js_call('dispatch_event', event, bubbles, cancelable, composed)
|
||||||
|
|
||||||
def caret_position(self):
|
def caret_position(self):
|
||||||
"""Get the text caret position for the current element.
|
"""Get the text caret position for the current element.
|
||||||
|
|
||||||
@ -203,6 +207,8 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
url = self.resolve_url(baseurl)
|
url = self.resolve_url(baseurl)
|
||||||
if url is None:
|
if url is None:
|
||||||
return True
|
return True
|
||||||
|
if baseurl.scheme() == url.scheme(): # e.g. a qute:// link
|
||||||
|
return False
|
||||||
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
|
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
|
||||||
|
|
||||||
def _click_editable(self, click_target):
|
def _click_editable(self, click_target):
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
"""QtWebEngine specific qute://* handlers and glue code."""
|
"""QtWebEngine specific qute://* handlers and glue code."""
|
||||||
|
|
||||||
from PyQt5.QtCore import QBuffer, QIODevice
|
from PyQt5.QtCore import QBuffer, QIODevice, QUrl
|
||||||
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
|
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
|
||||||
QWebEngineUrlRequestJob)
|
QWebEngineUrlRequestJob)
|
||||||
|
|
||||||
@ -37,6 +37,38 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||||||
if qtutils.version_check('5.11', compiled=False):
|
if qtutils.version_check('5.11', compiled=False):
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
||||||
profile.installUrlSchemeHandler(b'chrome-error', self)
|
profile.installUrlSchemeHandler(b'chrome-error', self)
|
||||||
|
profile.installUrlSchemeHandler(b'chrome-extension', self)
|
||||||
|
|
||||||
|
def _check_initiator(self, job):
|
||||||
|
"""Check whether the initiator of the job should be allowed.
|
||||||
|
|
||||||
|
Only the browser itself or qute:// pages should access any of those
|
||||||
|
URLs. The request interceptor further locks down qute://settings/set.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
job: QWebEngineUrlRequestJob
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if the initiator is allowed, False if it was blocked.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
initiator = job.initiator()
|
||||||
|
except AttributeError:
|
||||||
|
# Added in Qt 5.11
|
||||||
|
return True
|
||||||
|
|
||||||
|
if initiator == QUrl('null') and not qtutils.version_check('5.12'):
|
||||||
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70421
|
||||||
|
return True
|
||||||
|
|
||||||
|
if initiator.isValid() and initiator.scheme() != 'qute':
|
||||||
|
log.misc.warning("Blocking malicious request from {} to {}".format(
|
||||||
|
initiator.toDisplayString(),
|
||||||
|
job.requestUrl().toDisplayString()))
|
||||||
|
job.fail(QWebEngineUrlRequestJob.RequestDenied)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def requestStarted(self, job):
|
def requestStarted(self, job):
|
||||||
"""Handle a request for a qute: scheme.
|
"""Handle a request for a qute: scheme.
|
||||||
@ -49,28 +81,40 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
|||||||
"""
|
"""
|
||||||
url = job.requestUrl()
|
url = job.requestUrl()
|
||||||
|
|
||||||
if url.scheme() == 'chrome-error':
|
if url.scheme() in ['chrome-error', 'chrome-extension']:
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
||||||
job.fail(QWebEngineUrlRequestJob.UrlInvalid)
|
job.fail(QWebEngineUrlRequestJob.UrlInvalid)
|
||||||
return
|
return
|
||||||
|
|
||||||
assert job.requestMethod() == b'GET'
|
if not self._check_initiator(job):
|
||||||
|
return
|
||||||
|
|
||||||
|
if job.requestMethod() != b'GET':
|
||||||
|
job.fail(QWebEngineUrlRequestJob.RequestDenied)
|
||||||
|
return
|
||||||
|
|
||||||
assert url.scheme() == 'qute'
|
assert url.scheme() == 'qute'
|
||||||
|
|
||||||
log.misc.debug("Got request for {}".format(url.toDisplayString()))
|
log.misc.debug("Got request for {}".format(url.toDisplayString()))
|
||||||
try:
|
try:
|
||||||
mimetype, data = qutescheme.data_for_url(url)
|
mimetype, data = qutescheme.data_for_url(url)
|
||||||
except qutescheme.NoHandlerFound:
|
except qutescheme.Error as e:
|
||||||
log.misc.debug("No handler found for {}".format(
|
errors = {
|
||||||
url.toDisplayString()))
|
qutescheme.NotFoundError:
|
||||||
job.fail(QWebEngineUrlRequestJob.UrlNotFound)
|
QWebEngineUrlRequestJob.UrlNotFound,
|
||||||
except qutescheme.QuteSchemeOSError:
|
qutescheme.UrlInvalidError:
|
||||||
# FIXME:qtwebengine how do we show a better error here?
|
QWebEngineUrlRequestJob.UrlInvalid,
|
||||||
log.misc.exception("OSError while handling qute://* URL")
|
qutescheme.RequestDeniedError:
|
||||||
job.fail(QWebEngineUrlRequestJob.UrlNotFound)
|
QWebEngineUrlRequestJob.RequestDenied,
|
||||||
except qutescheme.QuteSchemeError:
|
qutescheme.SchemeOSError:
|
||||||
# FIXME:qtwebengine how do we show a better error here?
|
QWebEngineUrlRequestJob.UrlNotFound,
|
||||||
log.misc.exception("Error while handling qute://* URL")
|
qutescheme.Error:
|
||||||
job.fail(QWebEngineUrlRequestJob.RequestFailed)
|
QWebEngineUrlRequestJob.RequestFailed,
|
||||||
|
}
|
||||||
|
exctype = type(e)
|
||||||
|
log.misc.error("{} while handling qute://* URL".format(
|
||||||
|
exctype.__name__))
|
||||||
|
job.fail(errors[exctype])
|
||||||
except qutescheme.Redirect as e:
|
except qutescheme.Redirect as e:
|
||||||
qtutils.ensure_valid(e.url)
|
qtutils.ensure_valid(e.url)
|
||||||
job.redirect(e.url)
|
job.redirect(e.url)
|
||||||
|
@ -298,6 +298,8 @@ def init(args):
|
|||||||
not hasattr(QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11
|
not hasattr(QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11
|
||||||
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
|
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
|
||||||
|
|
||||||
|
spell.init()
|
||||||
|
|
||||||
_init_profiles()
|
_init_profiles()
|
||||||
config.instance.changed.connect(_update_settings)
|
config.instance.changed.connect(_update_settings)
|
||||||
|
|
||||||
|
@ -21,27 +21,26 @@
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
import functools
|
import functools
|
||||||
import sys
|
|
||||||
import re
|
import re
|
||||||
import html as html_utils
|
import html as html_utils
|
||||||
|
|
||||||
import sip
|
|
||||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
|
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
|
||||||
QUrl, QTimer, QObject, qVersion)
|
QUrl, QTimer, QObject)
|
||||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||||
from PyQt5.QtNetwork import QAuthenticator
|
from PyQt5.QtNetwork import QAuthenticator
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
|
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
|
||||||
|
|
||||||
from qutebrowser.config import configdata, config, configutils
|
from qutebrowser.config import configdata, config, configutils
|
||||||
from qutebrowser.browser import browsertab, mouse, shared
|
from qutebrowser.browser import browsertab, mouse, shared, webelem
|
||||||
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
||||||
interceptor, webenginequtescheme,
|
interceptor, webenginequtescheme,
|
||||||
cookies, webenginedownloads,
|
cookies, webenginedownloads,
|
||||||
webenginesettings)
|
webenginesettings, certificateerror)
|
||||||
from qutebrowser.misc import miscwidgets
|
from qutebrowser.misc import miscwidgets
|
||||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||||
message, objreg, jinja, debug)
|
message, objreg, jinja, debug)
|
||||||
|
from qutebrowser.qt import sip
|
||||||
|
|
||||||
|
|
||||||
_qute_scheme_handler = None
|
_qute_scheme_handler = None
|
||||||
@ -163,8 +162,8 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||||||
back yet.
|
back yet.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, tab, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(tab, parent)
|
||||||
self._flags = QWebEnginePage.FindFlags(0)
|
self._flags = QWebEnginePage.FindFlags(0)
|
||||||
self._pending_searches = 0
|
self._pending_searches = 0
|
||||||
|
|
||||||
@ -184,6 +183,13 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||||||
self._pending_searches))
|
self._pending_searches))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if sip.isdeleted(self._widget):
|
||||||
|
# This happens when starting a search, and closing the tab
|
||||||
|
# before results arrive.
|
||||||
|
log.webview.debug("Ignoring finished search for deleted "
|
||||||
|
"widget")
|
||||||
|
return
|
||||||
|
|
||||||
found_text = 'found' if found else "didn't find"
|
found_text = 'found' if found else "didn't find"
|
||||||
if flags:
|
if flags:
|
||||||
flag_text = 'with flags {}'.format(debug.qflags_key(
|
flag_text = 'with flags {}'.format(debug.qflags_key(
|
||||||
@ -192,8 +198,11 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||||||
flag_text = ''
|
flag_text = ''
|
||||||
log.webview.debug(' '.join([caller, found_text, text, flag_text])
|
log.webview.debug(' '.join([caller, found_text, text, flag_text])
|
||||||
.strip())
|
.strip())
|
||||||
|
|
||||||
if callback is not None:
|
if callback is not None:
|
||||||
callback(found)
|
callback(found)
|
||||||
|
self.finished.emit(found)
|
||||||
|
|
||||||
self._widget.findText(text, flags, wrapped_callback)
|
self._widget.findText(text, flags, wrapped_callback)
|
||||||
|
|
||||||
def search(self, text, *, ignore_case='never', reverse=False,
|
def search(self, text, *, ignore_case='never', reverse=False,
|
||||||
@ -214,6 +223,8 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
|||||||
self._find(text, self._flags, result_cb, 'search')
|
self._find(text, self._flags, result_cb, 'search')
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
if self.search_displayed:
|
||||||
|
self.cleared.emit()
|
||||||
self.search_displayed = False
|
self.search_displayed = False
|
||||||
self._widget.findText('')
|
self._widget.findText('')
|
||||||
|
|
||||||
@ -234,6 +245,15 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||||||
|
|
||||||
"""QtWebEngine implementations related to moving the cursor/selection."""
|
"""QtWebEngine implementations related to moving the cursor/selection."""
|
||||||
|
|
||||||
|
def _flags(self):
|
||||||
|
"""Get flags to pass to JS."""
|
||||||
|
flags = set()
|
||||||
|
if qtutils.version_check('5.7.1', compiled=False):
|
||||||
|
flags.add('filter-prefix')
|
||||||
|
if utils.is_windows:
|
||||||
|
flags.add('windows')
|
||||||
|
return list(flags)
|
||||||
|
|
||||||
@pyqtSlot(usertypes.KeyMode)
|
@pyqtSlot(usertypes.KeyMode)
|
||||||
def _on_mode_entered(self, mode):
|
def _on_mode_entered(self, mode):
|
||||||
if mode != usertypes.KeyMode.caret:
|
if mode != usertypes.KeyMode.caret:
|
||||||
@ -246,9 +266,9 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||||||
self._tab.search.clear()
|
self._tab.search.clear()
|
||||||
|
|
||||||
self._tab.run_js_async(
|
self._tab.run_js_async(
|
||||||
javascript.assemble('caret',
|
javascript.assemble('caret', 'setFlags', self._flags()))
|
||||||
'setPlatform', sys.platform, qVersion()))
|
|
||||||
self._js_call('setInitialCursor', self._selection_cb)
|
self._js_call('setInitialCursor', callback=self._selection_cb)
|
||||||
|
|
||||||
def _selection_cb(self, enabled):
|
def _selection_cb(self, enabled):
|
||||||
"""Emit selection_toggled based on setInitialCursor."""
|
"""Emit selection_toggled based on setInitialCursor."""
|
||||||
@ -266,32 +286,25 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||||||
self._js_call('disableCaret')
|
self._js_call('disableCaret')
|
||||||
|
|
||||||
def move_to_next_line(self, count=1):
|
def move_to_next_line(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveDown', count)
|
||||||
self._js_call('moveDown')
|
|
||||||
|
|
||||||
def move_to_prev_line(self, count=1):
|
def move_to_prev_line(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveUp', count)
|
||||||
self._js_call('moveUp')
|
|
||||||
|
|
||||||
def move_to_next_char(self, count=1):
|
def move_to_next_char(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveRight', count)
|
||||||
self._js_call('moveRight')
|
|
||||||
|
|
||||||
def move_to_prev_char(self, count=1):
|
def move_to_prev_char(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveLeft', count)
|
||||||
self._js_call('moveLeft')
|
|
||||||
|
|
||||||
def move_to_end_of_word(self, count=1):
|
def move_to_end_of_word(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveToEndOfWord', count)
|
||||||
self._js_call('moveToEndOfWord')
|
|
||||||
|
|
||||||
def move_to_next_word(self, count=1):
|
def move_to_next_word(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveToNextWord', count)
|
||||||
self._js_call('moveToNextWord')
|
|
||||||
|
|
||||||
def move_to_prev_word(self, count=1):
|
def move_to_prev_word(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveToPreviousWord', count)
|
||||||
self._js_call('moveToPreviousWord')
|
|
||||||
|
|
||||||
def move_to_start_of_line(self):
|
def move_to_start_of_line(self):
|
||||||
self._js_call('moveToStartOfLine')
|
self._js_call('moveToStartOfLine')
|
||||||
@ -300,20 +313,16 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||||||
self._js_call('moveToEndOfLine')
|
self._js_call('moveToEndOfLine')
|
||||||
|
|
||||||
def move_to_start_of_next_block(self, count=1):
|
def move_to_start_of_next_block(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveToStartOfNextBlock', count)
|
||||||
self._js_call('moveToStartOfNextBlock')
|
|
||||||
|
|
||||||
def move_to_start_of_prev_block(self, count=1):
|
def move_to_start_of_prev_block(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveToStartOfPrevBlock', count)
|
||||||
self._js_call('moveToStartOfPrevBlock')
|
|
||||||
|
|
||||||
def move_to_end_of_next_block(self, count=1):
|
def move_to_end_of_next_block(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveToEndOfNextBlock', count)
|
||||||
self._js_call('moveToEndOfNextBlock')
|
|
||||||
|
|
||||||
def move_to_end_of_prev_block(self, count=1):
|
def move_to_end_of_prev_block(self, count=1):
|
||||||
for _ in range(count):
|
self._js_call('moveToEndOfPrevBlock', count)
|
||||||
self._js_call('moveToEndOfPrevBlock')
|
|
||||||
|
|
||||||
def move_to_start_of_document(self):
|
def move_to_start_of_document(self):
|
||||||
self._js_call('moveToStartOfDocument')
|
self._js_call('moveToStartOfDocument')
|
||||||
@ -322,7 +331,7 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||||||
self._js_call('moveToEndOfDocument')
|
self._js_call('moveToEndOfDocument')
|
||||||
|
|
||||||
def toggle_selection(self):
|
def toggle_selection(self):
|
||||||
self._js_call('toggleSelection', self.selection_toggled.emit)
|
self._js_call('toggleSelection', callback=self.selection_toggled.emit)
|
||||||
|
|
||||||
def drop_selection(self):
|
def drop_selection(self):
|
||||||
self._js_call('dropSelection')
|
self._js_call('dropSelection')
|
||||||
@ -335,7 +344,13 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||||||
self._tab.run_js_async(javascript.assemble('caret', 'getSelection'),
|
self._tab.run_js_async(javascript.assemble('caret', 'getSelection'),
|
||||||
callback)
|
callback)
|
||||||
|
|
||||||
def _follow_selected_cb(self, js_elem, tab=False):
|
def _follow_selected_cb_wrapped(self, js_elem, tab):
|
||||||
|
try:
|
||||||
|
self._follow_selected_cb(js_elem, tab)
|
||||||
|
finally:
|
||||||
|
self.follow_selected_done.emit()
|
||||||
|
|
||||||
|
def _follow_selected_cb(self, js_elem, tab):
|
||||||
"""Callback for javascript which clicks the selected element.
|
"""Callback for javascript which clicks the selected element.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -344,6 +359,7 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||||||
"""
|
"""
|
||||||
if js_elem is None:
|
if js_elem is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if js_elem == "focused":
|
if js_elem == "focused":
|
||||||
# we had a focused element, not a selected one. Just send <enter>
|
# we had a focused element, not a selected one. Just send <enter>
|
||||||
self._follow_enter(tab)
|
self._follow_enter(tab)
|
||||||
@ -360,7 +376,10 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||||||
if elem.is_link():
|
if elem.is_link():
|
||||||
log.webview.debug("Found link in selection, clicking. ClickTarget "
|
log.webview.debug("Found link in selection, clicking. ClickTarget "
|
||||||
"{}, elem {}".format(click_type, elem))
|
"{}, elem {}".format(click_type, elem))
|
||||||
elem.click(click_type)
|
try:
|
||||||
|
elem.click(click_type)
|
||||||
|
except webelem.Error as e:
|
||||||
|
message.error(str(e))
|
||||||
|
|
||||||
def follow_selected(self, *, tab=False):
|
def follow_selected(self, *, tab=False):
|
||||||
if self._tab.search.search_displayed:
|
if self._tab.search.search_displayed:
|
||||||
@ -376,11 +395,13 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
|||||||
# click an existing blue selection
|
# click an existing blue selection
|
||||||
js_code = javascript.assemble('webelem',
|
js_code = javascript.assemble('webelem',
|
||||||
'find_selected_focused_link')
|
'find_selected_focused_link')
|
||||||
self._tab.run_js_async(js_code, lambda jsret:
|
self._tab.run_js_async(
|
||||||
self._follow_selected_cb(jsret, tab))
|
js_code,
|
||||||
|
lambda jsret: self._follow_selected_cb_wrapped(jsret, tab))
|
||||||
|
|
||||||
def _js_call(self, command, callback=None):
|
def _js_call(self, command, *args, callback=None):
|
||||||
self._tab.run_js_async(javascript.assemble('caret', command), callback)
|
code = javascript.assemble('caret', command, *args)
|
||||||
|
self._tab.run_js_async(code, callback)
|
||||||
|
|
||||||
|
|
||||||
class WebEngineScroller(browsertab.AbstractScroller):
|
class WebEngineScroller(browsertab.AbstractScroller):
|
||||||
@ -575,9 +596,12 @@ class WebEngineElements(browsertab.AbstractElements):
|
|||||||
if js_elems is None:
|
if js_elems is None:
|
||||||
callback(None)
|
callback(None)
|
||||||
return
|
return
|
||||||
|
elif not js_elems['success']:
|
||||||
|
callback(webelem.Error(js_elems['error']))
|
||||||
|
return
|
||||||
|
|
||||||
elems = []
|
elems = []
|
||||||
for js_elem in js_elems:
|
for js_elem in js_elems['result']:
|
||||||
elem = webengineelem.WebEngineElement(js_elem, tab=self._tab)
|
elem = webengineelem.WebEngineElement(js_elem, tab=self._tab)
|
||||||
elems.append(elem)
|
elems.append(elem)
|
||||||
callback(elems)
|
callback(elems)
|
||||||
@ -617,8 +641,8 @@ class WebEngineElements(browsertab.AbstractElements):
|
|||||||
self._tab.run_js_async(js_code, js_cb)
|
self._tab.run_js_async(js_code, js_cb)
|
||||||
|
|
||||||
def find_at_pos(self, pos, callback):
|
def find_at_pos(self, pos, callback):
|
||||||
assert pos.x() >= 0
|
assert pos.x() >= 0, pos
|
||||||
assert pos.y() >= 0
|
assert pos.y() >= 0, pos
|
||||||
pos /= self._tab.zoom.factor()
|
pos /= self._tab.zoom.factor()
|
||||||
js_code = javascript.assemble('webelem', 'find_at_pos',
|
js_code = javascript.assemble('webelem', 'find_at_pos',
|
||||||
pos.x(), pos.y())
|
pos.x(), pos.y())
|
||||||
@ -628,14 +652,26 @@ class WebEngineElements(browsertab.AbstractElements):
|
|||||||
|
|
||||||
class WebEngineAudio(browsertab.AbstractAudio):
|
class WebEngineAudio(browsertab.AbstractAudio):
|
||||||
|
|
||||||
"""QtWebEngine implemementations related to audio/muting."""
|
"""QtWebEngine implemementations related to audio/muting.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_overridden: Whether the user toggled muting manually.
|
||||||
|
If that's the case, we leave it alone.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, tab, parent=None):
|
||||||
|
super().__init__(tab, parent)
|
||||||
|
self._overridden = False
|
||||||
|
|
||||||
def _connect_signals(self):
|
def _connect_signals(self):
|
||||||
page = self._widget.page()
|
page = self._widget.page()
|
||||||
page.audioMutedChanged.connect(self.muted_changed)
|
page.audioMutedChanged.connect(self.muted_changed)
|
||||||
page.recentlyAudibleChanged.connect(self.recently_audible_changed)
|
page.recentlyAudibleChanged.connect(self.recently_audible_changed)
|
||||||
|
self._tab.url_changed.connect(self._on_url_changed)
|
||||||
|
config.instance.changed.connect(self._on_config_changed)
|
||||||
|
|
||||||
def set_muted(self, muted: bool):
|
def set_muted(self, muted: bool, override: bool = False):
|
||||||
|
self._overridden = override
|
||||||
page = self._widget.page()
|
page = self._widget.page()
|
||||||
page.setAudioMuted(muted)
|
page.setAudioMuted(muted)
|
||||||
|
|
||||||
@ -647,6 +683,17 @@ class WebEngineAudio(browsertab.AbstractAudio):
|
|||||||
page = self._widget.page()
|
page = self._widget.page()
|
||||||
return page.recentlyAudible()
|
return page.recentlyAudible()
|
||||||
|
|
||||||
|
@pyqtSlot(QUrl)
|
||||||
|
def _on_url_changed(self, url):
|
||||||
|
if self._overridden:
|
||||||
|
return
|
||||||
|
mute = config.instance.get('content.mute', url=url)
|
||||||
|
self.set_muted(mute)
|
||||||
|
|
||||||
|
@config.change_filter('content.mute')
|
||||||
|
def _on_config_changed(self):
|
||||||
|
self._on_url_changed(self._tab.url())
|
||||||
|
|
||||||
|
|
||||||
class _WebEnginePermissions(QObject):
|
class _WebEnginePermissions(QObject):
|
||||||
|
|
||||||
@ -703,6 +750,18 @@ class _WebEnginePermissions(QObject):
|
|||||||
QWebEnginePage.MediaVideoCapture: 'record video',
|
QWebEnginePage.MediaVideoCapture: 'record video',
|
||||||
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
|
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
|
||||||
}
|
}
|
||||||
|
try:
|
||||||
|
options.update({
|
||||||
|
QWebEnginePage.MouseLock:
|
||||||
|
'content.mouse_lock',
|
||||||
|
})
|
||||||
|
messages.update({
|
||||||
|
QWebEnginePage.MouseLock:
|
||||||
|
'hide your mouse pointer',
|
||||||
|
})
|
||||||
|
except AttributeError:
|
||||||
|
# Added in Qt 5.8
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
options.update({
|
options.update({
|
||||||
QWebEnginePage.DesktopVideoCapture:
|
QWebEnginePage.DesktopVideoCapture:
|
||||||
@ -788,12 +847,18 @@ class _WebEngineScripts(QObject):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._tab = tab
|
self._tab = tab
|
||||||
self._widget = None
|
self._widget = None
|
||||||
|
self._greasemonkey = objreg.get('greasemonkey')
|
||||||
|
|
||||||
def connect_signals(self):
|
def connect_signals(self):
|
||||||
|
"""Connect signals to our private slots."""
|
||||||
config.instance.changed.connect(self._on_config_changed)
|
config.instance.changed.connect(self._on_config_changed)
|
||||||
self._tab.url_changed.connect(self._update_stylesheet)
|
self._tab.url_changed.connect(self._update_stylesheet)
|
||||||
self._tab.load_finished.connect(self._on_load_finished)
|
self._tab.load_finished.connect(self._on_load_finished)
|
||||||
|
|
||||||
|
self._tab.search.cleared.connect(functools.partial(
|
||||||
|
self._update_stylesheet, searching=False))
|
||||||
|
self._tab.search.finished.connect(self._on_load_finished)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def _on_config_changed(self, option):
|
def _on_config_changed(self, option):
|
||||||
if option in ['scrolling.bar', 'content.user_stylesheets']:
|
if option in ['scrolling.bar', 'content.user_stylesheets']:
|
||||||
@ -805,14 +870,14 @@ class _WebEngineScripts(QObject):
|
|||||||
self._update_stylesheet(url)
|
self._update_stylesheet(url)
|
||||||
|
|
||||||
@pyqtSlot(QUrl)
|
@pyqtSlot(QUrl)
|
||||||
def _update_stylesheet(self, url, force=False):
|
def _update_stylesheet(self, url, searching=False, force=False):
|
||||||
"""Update the custom stylesheet in existing tabs.
|
"""Update the custom stylesheet in existing tabs.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
url: The url to get the stylesheet for.
|
url: The url to get the stylesheet for.
|
||||||
force: Also update the global stylesheet.
|
force: Also update the global stylesheet.
|
||||||
"""
|
"""
|
||||||
css = shared.get_user_stylesheet(url=url)
|
css = shared.get_user_stylesheet(searching=searching, url=url)
|
||||||
if css is configutils.UNSET and force:
|
if css is configutils.UNSET and force:
|
||||||
css = shared.get_user_stylesheet(url=None)
|
css = shared.get_user_stylesheet(url=None)
|
||||||
|
|
||||||
@ -869,9 +934,16 @@ class _WebEngineScripts(QObject):
|
|||||||
self._inject_early_js('js', js_code, subframes=True)
|
self._inject_early_js('js', js_code, subframes=True)
|
||||||
self._init_stylesheet()
|
self._init_stylesheet()
|
||||||
|
|
||||||
greasemonkey = objreg.get('greasemonkey')
|
# The Greasemonkey metadata block support in QtWebEngine only starts at
|
||||||
greasemonkey.scripts_reloaded.connect(self._inject_userscripts)
|
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
|
||||||
self._inject_userscripts()
|
# response to urlChanged.
|
||||||
|
if not qtutils.version_check('5.8'):
|
||||||
|
self._tab.url_changed.connect(
|
||||||
|
self._inject_greasemonkey_scripts_for_url)
|
||||||
|
else:
|
||||||
|
self._greasemonkey.scripts_reloaded.connect(
|
||||||
|
self._inject_all_greasemonkey_scripts)
|
||||||
|
self._inject_all_greasemonkey_scripts()
|
||||||
|
|
||||||
def _init_stylesheet(self):
|
def _init_stylesheet(self):
|
||||||
"""Initialize custom stylesheets.
|
"""Initialize custom stylesheets.
|
||||||
@ -888,40 +960,90 @@ class _WebEngineScripts(QObject):
|
|||||||
)
|
)
|
||||||
self._inject_early_js('stylesheet', js_code, subframes=True)
|
self._inject_early_js('stylesheet', js_code, subframes=True)
|
||||||
|
|
||||||
def _inject_userscripts(self):
|
@pyqtSlot(QUrl)
|
||||||
"""Register user JavaScript files with the global profiles."""
|
def _inject_greasemonkey_scripts_for_url(self, url):
|
||||||
# The Greasemonkey metadata block support in QtWebEngine only starts at
|
matching_scripts = self._greasemonkey.scripts_for(url)
|
||||||
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
|
self._inject_greasemonkey_scripts(
|
||||||
# response to urlChanged.
|
matching_scripts.start, QWebEngineScript.DocumentCreation, True)
|
||||||
if not qtutils.version_check('5.8'):
|
self._inject_greasemonkey_scripts(
|
||||||
return
|
matching_scripts.end, QWebEngineScript.DocumentReady, False)
|
||||||
|
self._inject_greasemonkey_scripts(
|
||||||
|
matching_scripts.idle, QWebEngineScript.Deferred, False)
|
||||||
|
|
||||||
# Since we are inserting scripts into profile.scripts they won't
|
@pyqtSlot()
|
||||||
# just get replaced by new gm scripts like if we were injecting them
|
def _inject_all_greasemonkey_scripts(self):
|
||||||
# ourselves so we need to remove all gm scripts, while not removing
|
scripts = self._greasemonkey.all_scripts()
|
||||||
# any other stuff that might have been added. Like the one for
|
self._inject_greasemonkey_scripts(scripts)
|
||||||
# stylesheets.
|
|
||||||
greasemonkey = objreg.get('greasemonkey')
|
def _remove_all_greasemonkey_scripts(self):
|
||||||
scripts = self._widget.page().scripts()
|
page_scripts = self._widget.page().scripts()
|
||||||
for script in scripts.toList():
|
for script in page_scripts.toList():
|
||||||
if script.name().startswith("GM-"):
|
if script.name().startswith("GM-"):
|
||||||
log.greasemonkey.debug('Removing script: {}'
|
log.greasemonkey.debug('Removing script: {}'
|
||||||
.format(script.name()))
|
.format(script.name()))
|
||||||
removed = scripts.remove(script)
|
removed = page_scripts.remove(script)
|
||||||
assert removed, script.name()
|
assert removed, script.name()
|
||||||
|
|
||||||
# Then add the new scripts.
|
def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None,
|
||||||
for script in greasemonkey.all_scripts():
|
remove_first=True):
|
||||||
# @run-at (and @include/@exclude/@match) is parsed by
|
"""Register user JavaScript files with the current tab.
|
||||||
# QWebEngineScript.
|
|
||||||
|
Args:
|
||||||
|
scripts: A list of GreasemonkeyScripts, or None to add all
|
||||||
|
known by the Greasemonkey subsystem.
|
||||||
|
injection_point: The QWebEngineScript::InjectionPoint stage
|
||||||
|
to inject the script into, None to use
|
||||||
|
auto-detection.
|
||||||
|
remove_first: Whether to remove all previously injected
|
||||||
|
scripts before adding these ones.
|
||||||
|
"""
|
||||||
|
if sip.isdeleted(self._widget):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Since we are inserting scripts into a per-tab collection,
|
||||||
|
# rather than just injecting scripts on page load, we need to
|
||||||
|
# make sure we replace existing scripts, not just add new ones.
|
||||||
|
# While, taking care not to remove any other scripts that might
|
||||||
|
# have been added elsewhere, like the one for stylesheets.
|
||||||
|
page_scripts = self._widget.page().scripts()
|
||||||
|
if remove_first:
|
||||||
|
self._remove_all_greasemonkey_scripts()
|
||||||
|
|
||||||
|
if not scripts:
|
||||||
|
return
|
||||||
|
|
||||||
|
for script in scripts:
|
||||||
new_script = QWebEngineScript()
|
new_script = QWebEngineScript()
|
||||||
new_script.setWorldId(QWebEngineScript.MainWorld)
|
try:
|
||||||
|
world = int(script.jsworld)
|
||||||
|
if not 0 <= world <= qtutils.MAX_WORLD_ID:
|
||||||
|
log.greasemonkey.error(
|
||||||
|
"script {} has invalid value for '@qute-js-world'"
|
||||||
|
": {}, should be between 0 and {}"
|
||||||
|
.format(
|
||||||
|
script.name,
|
||||||
|
script.jsworld,
|
||||||
|
qtutils.MAX_WORLD_ID))
|
||||||
|
continue
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
world = _JS_WORLD_MAP[usertypes.JsWorld[
|
||||||
|
script.jsworld.lower()]]
|
||||||
|
except KeyError:
|
||||||
|
log.greasemonkey.error(
|
||||||
|
"script {} has invalid value for '@qute-js-world'"
|
||||||
|
": {}".format(script.name, script.jsworld))
|
||||||
|
continue
|
||||||
|
new_script.setWorldId(world)
|
||||||
new_script.setSourceCode(script.code())
|
new_script.setSourceCode(script.code())
|
||||||
new_script.setName("GM-{}".format(script.name))
|
new_script.setName("GM-{}".format(script.name))
|
||||||
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||||
|
# Override the @run-at value parsed by QWebEngineScript if desired.
|
||||||
|
if injection_point:
|
||||||
|
new_script.setInjectionPoint(injection_point)
|
||||||
log.greasemonkey.debug('adding script: {}'
|
log.greasemonkey.debug('adding script: {}'
|
||||||
.format(new_script.name()))
|
.format(new_script.name()))
|
||||||
scripts.insert(new_script)
|
page_scripts.insert(new_script)
|
||||||
|
|
||||||
|
|
||||||
class WebEngineTab(browsertab.AbstractTab):
|
class WebEngineTab(browsertab.AbstractTab):
|
||||||
@ -941,16 +1063,16 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
private=private, parent=parent)
|
private=private, parent=parent)
|
||||||
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
|
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
|
||||||
private=private)
|
private=private)
|
||||||
self.history = WebEngineHistory(self)
|
self.history = WebEngineHistory(tab=self)
|
||||||
self.scroller = WebEngineScroller(self, parent=self)
|
self.scroller = WebEngineScroller(tab=self, parent=self)
|
||||||
self.caret = WebEngineCaret(mode_manager=mode_manager,
|
self.caret = WebEngineCaret(mode_manager=mode_manager,
|
||||||
tab=self, parent=self)
|
tab=self, parent=self)
|
||||||
self.zoom = WebEngineZoom(tab=self, parent=self)
|
self.zoom = WebEngineZoom(tab=self, parent=self)
|
||||||
self.search = WebEngineSearch(parent=self)
|
self.search = WebEngineSearch(tab=self, parent=self)
|
||||||
self.printing = WebEnginePrinting()
|
self.printing = WebEnginePrinting(tab=self)
|
||||||
self.elements = WebEngineElements(tab=self)
|
self.elements = WebEngineElements(tab=self)
|
||||||
self.action = WebEngineAction(tab=self)
|
self.action = WebEngineAction(tab=self)
|
||||||
self.audio = WebEngineAudio(parent=self)
|
self.audio = WebEngineAudio(tab=self, parent=self)
|
||||||
self._permissions = _WebEnginePermissions(tab=self, parent=self)
|
self._permissions = _WebEnginePermissions(tab=self, parent=self)
|
||||||
self._scripts = _WebEngineScripts(tab=self, parent=self)
|
self._scripts = _WebEngineScripts(tab=self, parent=self)
|
||||||
# We're assigning settings in _set_widget
|
# We're assigning settings in _set_widget
|
||||||
@ -975,7 +1097,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
fp.installEventFilter(self._mouse_event_filter)
|
fp.installEventFilter(self._mouse_event_filter)
|
||||||
self._child_event_filter = mouse.ChildEventFilter(
|
self._child_event_filter = mouse.ChildEventFilter(
|
||||||
eventfilter=self._mouse_event_filter, widget=self._widget,
|
eventfilter=self._mouse_event_filter, widget=self._widget,
|
||||||
parent=self)
|
win_id=self.win_id, parent=self)
|
||||||
self._widget.installEventFilter(self._child_event_filter)
|
self._widget.installEventFilter(self._child_event_filter)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@ -995,6 +1117,9 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
url: The QUrl to open.
|
url: The QUrl to open.
|
||||||
predict: If set to False, predicted_navigation is not emitted.
|
predict: If set to False, predicted_navigation is not emitted.
|
||||||
"""
|
"""
|
||||||
|
if sip.isdeleted(self._widget):
|
||||||
|
# https://github.com/qutebrowser/qutebrowser/issues/3896
|
||||||
|
return
|
||||||
self._saved_zoom = self.zoom.factor()
|
self._saved_zoom = self.zoom.factor()
|
||||||
self._openurl_prepare(url, predict=predict)
|
self._openurl_prepare(url, predict=predict)
|
||||||
self._widget.load(url)
|
self._widget.load(url)
|
||||||
@ -1017,6 +1142,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
world_id = QWebEngineScript.ApplicationWorld
|
world_id = QWebEngineScript.ApplicationWorld
|
||||||
elif isinstance(world, int):
|
elif isinstance(world, int):
|
||||||
world_id = world
|
world_id = world
|
||||||
|
if not 0 <= world_id <= qtutils.MAX_WORLD_ID:
|
||||||
|
raise browsertab.WebTabError(
|
||||||
|
"World ID should be between 0 and {}"
|
||||||
|
.format(qtutils.MAX_WORLD_ID))
|
||||||
else:
|
else:
|
||||||
world_id = _JS_WORLD_MAP[world]
|
world_id = _JS_WORLD_MAP[world]
|
||||||
|
|
||||||
@ -1153,11 +1282,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _on_load_started(self):
|
def _on_load_started(self):
|
||||||
"""Clear search when a new load is started if needed."""
|
"""Clear search when a new load is started if needed."""
|
||||||
if (qtutils.version_check('5.9', compiled=False) and
|
# WORKAROUND for
|
||||||
not qtutils.version_check('5.9.2', compiled=False)):
|
# https://bugreports.qt.io/browse/QTBUG-61506
|
||||||
# WORKAROUND for
|
# (seems to be back in later Qt versions as well)
|
||||||
# https://bugreports.qt.io/browse/QTBUG-61506
|
self.search.clear()
|
||||||
self.search.clear()
|
|
||||||
super()._on_load_started()
|
super()._on_load_started()
|
||||||
self.data.netrc_used = False
|
self.data.netrc_used = False
|
||||||
|
|
||||||
@ -1241,6 +1369,34 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
# the old icon is still displayed.
|
# the old icon is still displayed.
|
||||||
self.icon_changed.emit(QIcon())
|
self.icon_changed.emit(QIcon())
|
||||||
|
|
||||||
|
@pyqtSlot(certificateerror.CertificateErrorWrapper)
|
||||||
|
def _on_ssl_errors(self, error):
|
||||||
|
self._has_ssl_errors = True
|
||||||
|
|
||||||
|
url = error.url()
|
||||||
|
log.webview.debug("Certificate error: {}".format(error))
|
||||||
|
|
||||||
|
if error.is_overridable():
|
||||||
|
error.ignore = shared.ignore_certificate_errors(
|
||||||
|
url, [error], abort_on=[self.shutting_down, self.load_started])
|
||||||
|
else:
|
||||||
|
log.webview.error("Non-overridable certificate error: "
|
||||||
|
"{}".format(error))
|
||||||
|
|
||||||
|
log.webview.debug("ignore {}, URL {}, requested {}".format(
|
||||||
|
error.ignore, url, self.url(requested=True)))
|
||||||
|
|
||||||
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207
|
||||||
|
# We can't really know when to show an error page, as the error might
|
||||||
|
# have happened when loading some resource.
|
||||||
|
# However, self.url() is not available yet and the requested URL
|
||||||
|
# might not match the URL we get from the error - so we just apply a
|
||||||
|
# heuristic here.
|
||||||
|
if (not qtutils.version_check('5.9') and
|
||||||
|
not error.ignore and
|
||||||
|
url.matches(self.url(requested=True), QUrl.RemoveScheme)):
|
||||||
|
self._show_error_page(url, str(error))
|
||||||
|
|
||||||
@pyqtSlot(QUrl)
|
@pyqtSlot(QUrl)
|
||||||
def _on_predicted_navigation(self, url):
|
def _on_predicted_navigation(self, url):
|
||||||
"""If we know we're going to visit an URL soon, change the settings.
|
"""If we know we're going to visit an URL soon, change the settings.
|
||||||
@ -1256,10 +1412,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
super()._on_navigation_request(navigation)
|
super()._on_navigation_request(navigation)
|
||||||
|
|
||||||
if navigation.url == QUrl('qute://print'):
|
if navigation.url == QUrl('qute://print'):
|
||||||
command_dispatcher = objreg.get('command-dispatcher',
|
try:
|
||||||
scope='window',
|
self.printing.show_dialog()
|
||||||
window=self.win_id)
|
except browsertab.WebTabError as e:
|
||||||
command_dispatcher.printpage()
|
message.error(str(e))
|
||||||
navigation.accepted = False
|
navigation.accepted = False
|
||||||
|
|
||||||
if not navigation.accepted or not navigation.is_main_frame:
|
if not navigation.accepted or not navigation.is_main_frame:
|
||||||
|
@ -19,18 +19,17 @@
|
|||||||
|
|
||||||
"""The main browser widget for QtWebEngine."""
|
"""The main browser widget for QtWebEngine."""
|
||||||
|
|
||||||
import sip
|
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
|
|
||||||
from PyQt5.QtGui import QPalette
|
from PyQt5.QtGui import QPalette
|
||||||
from PyQt5.QtQuickWidgets import QQuickWidget
|
from PyQt5.QtWidgets import QWidget
|
||||||
from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
|
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||||
QWebEngineScript)
|
|
||||||
|
|
||||||
from qutebrowser.browser import shared
|
from qutebrowser.browser import shared
|
||||||
from qutebrowser.browser.webengine import certificateerror, webenginesettings
|
from qutebrowser.browser.webengine import webenginesettings, certificateerror
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
|
from qutebrowser.utils import log, debug, usertypes, objreg, qtutils
|
||||||
from qutebrowser.misc import miscwidgets
|
from qutebrowser.misc import miscwidgets
|
||||||
|
from qutebrowser.qt import sip
|
||||||
|
|
||||||
|
|
||||||
class WebEngineView(QWebEngineView):
|
class WebEngineView(QWebEngineView):
|
||||||
@ -71,10 +70,10 @@ class WebEngineView(QWebEngineView):
|
|||||||
if proxy is not None:
|
if proxy is not None:
|
||||||
return proxy
|
return proxy
|
||||||
|
|
||||||
# This should only find the RenderWidgetHostViewQtDelegateWidget,
|
# We don't want e.g. a QMenu.
|
||||||
# but not e.g. a QMenu
|
rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget'
|
||||||
children = [c for c in self.findChildren(QQuickWidget)
|
children = [c for c in self.findChildren(QWidget)
|
||||||
if c.isVisible()]
|
if c.isVisible() and c.inherits(rwhv_class)]
|
||||||
|
|
||||||
log.webview.debug("Found possibly lost focusProxy: {}"
|
log.webview.debug("Found possibly lost focusProxy: {}"
|
||||||
.format(children))
|
.format(children))
|
||||||
@ -152,11 +151,13 @@ class WebEnginePage(QWebEnginePage):
|
|||||||
|
|
||||||
Signals:
|
Signals:
|
||||||
certificate_error: Emitted on certificate errors.
|
certificate_error: Emitted on certificate errors.
|
||||||
|
Needs to be directly connected to a slot setting the
|
||||||
|
'ignore' attribute.
|
||||||
shutting_down: Emitted when the page is shutting down.
|
shutting_down: Emitted when the page is shutting down.
|
||||||
navigation_request: Emitted on acceptNavigationRequest.
|
navigation_request: Emitted on acceptNavigationRequest.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
certificate_error = pyqtSignal()
|
certificate_error = pyqtSignal(certificateerror.CertificateErrorWrapper)
|
||||||
shutting_down = pyqtSignal()
|
shutting_down = pyqtSignal()
|
||||||
navigation_request = pyqtSignal(usertypes.NavigationRequest)
|
navigation_request = pyqtSignal(usertypes.NavigationRequest)
|
||||||
|
|
||||||
@ -166,7 +167,6 @@ class WebEnginePage(QWebEnginePage):
|
|||||||
self._theme_color = theme_color
|
self._theme_color = theme_color
|
||||||
self._set_bg_color()
|
self._set_bg_color()
|
||||||
config.instance.changed.connect(self._set_bg_color)
|
config.instance.changed.connect(self._set_bg_color)
|
||||||
self.urlChanged.connect(self._inject_userjs)
|
|
||||||
|
|
||||||
@config.change_filter('colors.webpage.bg')
|
@config.change_filter('colors.webpage.bg')
|
||||||
def _set_bg_color(self):
|
def _set_bg_color(self):
|
||||||
@ -181,36 +181,9 @@ class WebEnginePage(QWebEnginePage):
|
|||||||
|
|
||||||
def certificateError(self, error):
|
def certificateError(self, error):
|
||||||
"""Handle certificate errors coming from Qt."""
|
"""Handle certificate errors coming from Qt."""
|
||||||
self.certificate_error.emit()
|
|
||||||
url = error.url()
|
|
||||||
error = certificateerror.CertificateErrorWrapper(error)
|
error = certificateerror.CertificateErrorWrapper(error)
|
||||||
log.webview.debug("Certificate error: {}".format(error))
|
self.certificate_error.emit(error)
|
||||||
|
return error.ignore
|
||||||
url_string = url.toDisplayString()
|
|
||||||
error_page = jinja.render(
|
|
||||||
'error.html', title="Error loading page: {}".format(url_string),
|
|
||||||
url=url_string, error=str(error))
|
|
||||||
|
|
||||||
if error.is_overridable():
|
|
||||||
ignore = shared.ignore_certificate_errors(
|
|
||||||
url, [error], abort_on=[self.loadStarted, self.shutting_down])
|
|
||||||
else:
|
|
||||||
log.webview.error("Non-overridable certificate error: "
|
|
||||||
"{}".format(error))
|
|
||||||
ignore = False
|
|
||||||
|
|
||||||
# We can't really know when to show an error page, as the error might
|
|
||||||
# have happened when loading some resource.
|
|
||||||
# However, self.url() is not available yet and self.requestedUrl()
|
|
||||||
# might not match the URL we get from the error - so we just apply a
|
|
||||||
# heuristic here.
|
|
||||||
# See https://bugreports.qt.io/browse/QTBUG-56207
|
|
||||||
log.webview.debug("ignore {}, URL {}, requested {}".format(
|
|
||||||
ignore, url, self.requestedUrl()))
|
|
||||||
if not ignore and url.matches(self.requestedUrl(), QUrl.RemoveScheme):
|
|
||||||
self.setHtml(error_page)
|
|
||||||
|
|
||||||
return ignore
|
|
||||||
|
|
||||||
def javaScriptConfirm(self, url, js_msg):
|
def javaScriptConfirm(self, url, js_msg):
|
||||||
"""Override javaScriptConfirm to use qutebrowser prompts."""
|
"""Override javaScriptConfirm to use qutebrowser prompts."""
|
||||||
@ -288,43 +261,3 @@ class WebEnginePage(QWebEnginePage):
|
|||||||
is_main_frame=is_main_frame)
|
is_main_frame=is_main_frame)
|
||||||
self.navigation_request.emit(navigation)
|
self.navigation_request.emit(navigation)
|
||||||
return navigation.accepted
|
return navigation.accepted
|
||||||
|
|
||||||
@pyqtSlot('QUrl')
|
|
||||||
def _inject_userjs(self, url):
|
|
||||||
"""Inject userscripts registered for `url` into the current page."""
|
|
||||||
if qtutils.version_check('5.8'):
|
|
||||||
# Handled in webenginetab with the builtin Greasemonkey
|
|
||||||
# support.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Using QWebEnginePage.scripts() to hold the user scripts means
|
|
||||||
# we don't have to worry ourselves about where to inject the
|
|
||||||
# page but also means scripts hang around for the tab lifecycle.
|
|
||||||
# So clear them here.
|
|
||||||
scripts = self.scripts()
|
|
||||||
for script in scripts.toList():
|
|
||||||
if script.name().startswith("GM-"):
|
|
||||||
log.greasemonkey.debug("Removing script: {}"
|
|
||||||
.format(script.name()))
|
|
||||||
removed = scripts.remove(script)
|
|
||||||
assert removed, script.name()
|
|
||||||
|
|
||||||
def _add_script(script, injection_point):
|
|
||||||
new_script = QWebEngineScript()
|
|
||||||
new_script.setInjectionPoint(injection_point)
|
|
||||||
new_script.setWorldId(QWebEngineScript.MainWorld)
|
|
||||||
new_script.setSourceCode(script.code())
|
|
||||||
new_script.setName("GM-{}".format(script.name))
|
|
||||||
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
|
||||||
log.greasemonkey.debug("Adding script: {}"
|
|
||||||
.format(new_script.name()))
|
|
||||||
scripts.insert(new_script)
|
|
||||||
|
|
||||||
greasemonkey = objreg.get('greasemonkey')
|
|
||||||
matching_scripts = greasemonkey.scripts_for(url)
|
|
||||||
for script in matching_scripts.start:
|
|
||||||
_add_script(script, QWebEngineScript.DocumentCreation)
|
|
||||||
for script in matching_scripts.end:
|
|
||||||
_add_script(script, QWebEngineScript.DocumentReady)
|
|
||||||
for script in matching_scripts.idle:
|
|
||||||
_add_script(script, QWebEngineScript.Deferred)
|
|
||||||
|
@ -312,9 +312,9 @@ class _Downloader:
|
|||||||
for style in styles:
|
for style in styles:
|
||||||
style = webkitelem.WebKitElement(style, tab=self.tab)
|
style = webkitelem.WebKitElement(style, tab=self.tab)
|
||||||
# The Mozilla Developer Network says:
|
# The Mozilla Developer Network says:
|
||||||
# type: This attribute defines the styling language as a MIME type
|
# > type: This attribute defines the styling language as a MIME
|
||||||
# (charset should not be specified). This attribute is optional and
|
# > type (charset should not be specified). This attribute is
|
||||||
# default to text/css if it's missing.
|
# > optional and default to text/css if it's missing.
|
||||||
# https://developer.mozilla.org/en/docs/Web/HTML/Element/style
|
# https://developer.mozilla.org/en/docs/Web/HTML/Element/style
|
||||||
if 'type' in style and style['type'] != 'text/css':
|
if 'type' in style and style['type'] != 'text/css':
|
||||||
continue
|
continue
|
||||||
|
@ -111,11 +111,13 @@ def dirbrowser_html(path):
|
|||||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||||
|
|
||||||
|
|
||||||
def handler(request):
|
def handler(request, _operation, _current_url):
|
||||||
"""Handler for a file:// URL.
|
"""Handler for a file:// URL.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: QNetworkRequest to answer to.
|
request: QNetworkRequest to answer to.
|
||||||
|
_operation: The HTTP operation being done.
|
||||||
|
_current_url: The page we're on currently.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
A QNetworkReply for directories, None for files.
|
A QNetworkReply for directories, None for files.
|
||||||
|
@ -373,24 +373,9 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
req, proxy_error, QNetworkReply.UnknownProxyError,
|
req, proxy_error, QNetworkReply.UnknownProxyError,
|
||||||
self)
|
self)
|
||||||
|
|
||||||
scheme = req.url().scheme()
|
for header, value in shared.custom_headers(url=req.url()):
|
||||||
if scheme in self._scheme_handlers:
|
|
||||||
result = self._scheme_handlers[scheme](req)
|
|
||||||
if result is not None:
|
|
||||||
result.setParent(self)
|
|
||||||
return result
|
|
||||||
|
|
||||||
for header, value in shared.custom_headers():
|
|
||||||
req.setRawHeader(header, value)
|
req.setRawHeader(header, value)
|
||||||
|
|
||||||
host_blocker = objreg.get('host-blocker')
|
|
||||||
if host_blocker.is_blocked(req.url()):
|
|
||||||
log.webview.info("Request to {} blocked by host blocker.".format(
|
|
||||||
req.url().host()))
|
|
||||||
return networkreply.ErrorNetworkReply(
|
|
||||||
req, HOSTBLOCK_ERROR_STRING, QNetworkReply.ContentAccessDenied,
|
|
||||||
self)
|
|
||||||
|
|
||||||
# There are some scenarios where we can't figure out current_url:
|
# There are some scenarios where we can't figure out current_url:
|
||||||
# - There's a generic NetworkManager, e.g. for downloads
|
# - There's a generic NetworkManager, e.g. for downloads
|
||||||
# - The download was in a tab which is now closed.
|
# - The download was in a tab which is now closed.
|
||||||
@ -408,6 +393,14 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
# the webpage shutdown here.
|
# the webpage shutdown here.
|
||||||
current_url = QUrl()
|
current_url = QUrl()
|
||||||
|
|
||||||
|
host_blocker = objreg.get('host-blocker')
|
||||||
|
if host_blocker.is_blocked(req.url(), current_url):
|
||||||
|
log.webview.info("Request to {} blocked by host blocker.".format(
|
||||||
|
req.url().host()))
|
||||||
|
return networkreply.ErrorNetworkReply(
|
||||||
|
req, HOSTBLOCK_ERROR_STRING, QNetworkReply.ContentAccessDenied,
|
||||||
|
self)
|
||||||
|
|
||||||
if 'log-requests' in self._args.debug_flags:
|
if 'log-requests' in self._args.debug_flags:
|
||||||
operation = debug.qenum_key(QNetworkAccessManager, op)
|
operation = debug.qenum_key(QNetworkAccessManager, op)
|
||||||
operation = operation.replace('Operation', '').upper()
|
operation = operation.replace('Operation', '').upper()
|
||||||
@ -416,5 +409,12 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
req.url().toDisplayString(),
|
req.url().toDisplayString(),
|
||||||
current_url.toDisplayString()))
|
current_url.toDisplayString()))
|
||||||
|
|
||||||
|
scheme = req.url().scheme()
|
||||||
|
if scheme in self._scheme_handlers:
|
||||||
|
result = self._scheme_handlers[scheme](req, op, current_url)
|
||||||
|
if result is not None:
|
||||||
|
result.setParent(self)
|
||||||
|
return result
|
||||||
|
|
||||||
self.set_referer(req, current_url)
|
self.set_referer(req, current_url)
|
||||||
return super().createRequest(op, req, outgoing_data)
|
return super().createRequest(op, req, outgoing_data)
|
||||||
|
@ -19,58 +19,63 @@
|
|||||||
|
|
||||||
"""QtWebKit specific qute://* handlers and glue code."""
|
"""QtWebKit specific qute://* handlers and glue code."""
|
||||||
|
|
||||||
import mimetypes
|
from PyQt5.QtCore import QUrl
|
||||||
|
from PyQt5.QtNetwork import QNetworkReply, QNetworkAccessManager
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QNetworkReply
|
from qutebrowser.browser import qutescheme
|
||||||
|
|
||||||
from qutebrowser.browser import pdfjs, qutescheme
|
|
||||||
from qutebrowser.browser.webkit.network import networkreply
|
from qutebrowser.browser.webkit.network import networkreply
|
||||||
from qutebrowser.utils import log, usertypes, qtutils
|
from qutebrowser.utils import log, qtutils
|
||||||
|
|
||||||
|
|
||||||
def handler(request):
|
def handler(request, operation, current_url):
|
||||||
"""Scheme handler for qute:// URLs.
|
"""Scheme handler for qute:// URLs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: QNetworkRequest to answer to.
|
request: QNetworkRequest to answer to.
|
||||||
|
operation: The HTTP operation being done.
|
||||||
|
current_url: The page we're on currently.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
A QNetworkReply.
|
A QNetworkReply.
|
||||||
"""
|
"""
|
||||||
|
if operation != QNetworkAccessManager.GetOperation:
|
||||||
|
return networkreply.ErrorNetworkReply(
|
||||||
|
request, "Unsupported request type",
|
||||||
|
QNetworkReply.ContentOperationNotPermittedError)
|
||||||
|
|
||||||
|
url = request.url()
|
||||||
|
|
||||||
|
if ((url.scheme(), url.host(), url.path()) ==
|
||||||
|
('qute', 'settings', '/set')):
|
||||||
|
if current_url != QUrl('qute://settings/'):
|
||||||
|
log.webview.warning("Blocking malicious request from {} to {}"
|
||||||
|
.format(current_url.toDisplayString(),
|
||||||
|
url.toDisplayString()))
|
||||||
|
return networkreply.ErrorNetworkReply(
|
||||||
|
request, "Invalid qute://settings request",
|
||||||
|
QNetworkReply.ContentAccessDenied)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mimetype, data = qutescheme.data_for_url(request.url())
|
mimetype, data = qutescheme.data_for_url(url)
|
||||||
except qutescheme.NoHandlerFound:
|
except qutescheme.Error as e:
|
||||||
errorstr = "No handler found for {}!".format(
|
errors = {
|
||||||
request.url().toDisplayString())
|
qutescheme.NotFoundError:
|
||||||
return networkreply.ErrorNetworkReply(
|
QNetworkReply.ContentNotFoundError,
|
||||||
request, errorstr, QNetworkReply.ContentNotFoundError)
|
qutescheme.UrlInvalidError:
|
||||||
except qutescheme.QuteSchemeOSError as e:
|
QNetworkReply.ContentOperationNotPermittedError,
|
||||||
return networkreply.ErrorNetworkReply(
|
qutescheme.RequestDeniedError:
|
||||||
request, str(e), QNetworkReply.ContentNotFoundError)
|
QNetworkReply.ContentAccessDenied,
|
||||||
except qutescheme.QuteSchemeError as e:
|
qutescheme.SchemeOSError:
|
||||||
return networkreply.ErrorNetworkReply(request, e.errorstring, e.error)
|
QNetworkReply.ContentNotFoundError,
|
||||||
|
qutescheme.Error:
|
||||||
|
QNetworkReply.InternalServerError,
|
||||||
|
}
|
||||||
|
exctype = type(e)
|
||||||
|
log.misc.error("{} while handling qute://* URL".format(
|
||||||
|
exctype.__name__))
|
||||||
|
return networkreply.ErrorNetworkReply(request, str(e), errors[exctype])
|
||||||
except qutescheme.Redirect as e:
|
except qutescheme.Redirect as e:
|
||||||
qtutils.ensure_valid(e.url)
|
qtutils.ensure_valid(e.url)
|
||||||
return networkreply.RedirectNetworkReply(e.url)
|
return networkreply.RedirectNetworkReply(e.url)
|
||||||
|
|
||||||
return networkreply.FixedDataNetworkReply(request, data, mimetype)
|
return networkreply.FixedDataNetworkReply(request, data, mimetype)
|
||||||
|
|
||||||
|
|
||||||
@qutescheme.add_handler('pdfjs', backend=usertypes.Backend.QtWebKit)
|
|
||||||
def qute_pdfjs(url):
|
|
||||||
"""Handler for qute://pdfjs. Return the pdf.js viewer."""
|
|
||||||
try:
|
|
||||||
data = pdfjs.get_pdfjs_res(url.path())
|
|
||||||
except pdfjs.PDFJSNotFound as e:
|
|
||||||
# Logging as the error might get lost otherwise since we're not showing
|
|
||||||
# the error page if a single asset is missing. This way we don't lose
|
|
||||||
# information, as the failed pdfjs requests are still in the log.
|
|
||||||
log.misc.warning(
|
|
||||||
"pdfjs resource requested but not found: {}".format(e.path))
|
|
||||||
raise qutescheme.QuteSchemeError("Can't find pdfjs resource "
|
|
||||||
"'{}'".format(e.path),
|
|
||||||
QNetworkReply.ContentNotFoundError)
|
|
||||||
else:
|
|
||||||
mimetype, _encoding = mimetypes.guess_type(url.fileName())
|
|
||||||
assert mimetype is not None, url
|
|
||||||
return mimetype, data
|
|
||||||
|
@ -125,8 +125,20 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
self._elem.setPlainText(value)
|
self._elem.setPlainText(value)
|
||||||
else:
|
else:
|
||||||
log.webelem.debug("Filling {!r} via javascript.".format(self))
|
log.webelem.debug("Filling {!r} via javascript.".format(self))
|
||||||
value = javascript.string_escape(value)
|
value = javascript.to_js(value)
|
||||||
self._elem.evaluateJavaScript("this.value='{}'".format(value))
|
self._elem.evaluateJavaScript("this.value={}".format(value))
|
||||||
|
|
||||||
|
def dispatch_event(self, event, bubbles=False,
|
||||||
|
cancelable=False, composed=False):
|
||||||
|
self._check_vanished()
|
||||||
|
log.webelem.debug("Firing event on {!r} via javascript.".format(self))
|
||||||
|
self._elem.evaluateJavaScript(
|
||||||
|
"this.dispatchEvent(new Event({}, "
|
||||||
|
"{{'bubbles': {}, 'cancelable': {}, 'composed': {}}}))"
|
||||||
|
.format(javascript.to_js(event),
|
||||||
|
javascript.to_js(bubbles),
|
||||||
|
javascript.to_js(cancelable),
|
||||||
|
javascript.to_js(composed)))
|
||||||
|
|
||||||
def caret_position(self):
|
def caret_position(self):
|
||||||
"""Get the text caret position for the current element."""
|
"""Get the text caret position for the current element."""
|
||||||
@ -142,11 +154,11 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
raise webelem.Error("Element is not editable!")
|
raise webelem.Error("Element is not editable!")
|
||||||
log.webelem.debug("Inserting text into element {!r}".format(self))
|
log.webelem.debug("Inserting text into element {!r}".format(self))
|
||||||
self._elem.evaluateJavaScript("""
|
self._elem.evaluateJavaScript("""
|
||||||
var text = "{}";
|
var text = {};
|
||||||
var event = document.createEvent("TextEvent");
|
var event = document.createEvent("TextEvent");
|
||||||
event.initTextEvent("textInput", true, true, null, text);
|
event.initTextEvent("textInput", true, true, null, text);
|
||||||
this.dispatchEvent(event);
|
this.dispatchEvent(event);
|
||||||
""".format(javascript.string_escape(text)))
|
""".format(javascript.to_js(text)))
|
||||||
|
|
||||||
def _parent(self):
|
def _parent(self):
|
||||||
"""Get the parent element of this element."""
|
"""Get the parent element of this element."""
|
||||||
|
@ -23,7 +23,6 @@ import re
|
|||||||
import functools
|
import functools
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
import sip
|
|
||||||
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
|
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
|
||||||
QSize)
|
QSize)
|
||||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||||
@ -35,6 +34,7 @@ from qutebrowser.browser import browsertab, shared
|
|||||||
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
|
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
|
||||||
webkitsettings)
|
webkitsettings)
|
||||||
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
|
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
|
||||||
|
from qutebrowser.qt import sip
|
||||||
|
|
||||||
|
|
||||||
class WebKitAction(browsertab.AbstractAction):
|
class WebKitAction(browsertab.AbstractAction):
|
||||||
@ -84,8 +84,8 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||||||
|
|
||||||
"""QtWebKit implementations related to searching on the page."""
|
"""QtWebKit implementations related to searching on the page."""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, tab, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(tab, parent)
|
||||||
self._flags = QWebPage.FindFlags(0)
|
self._flags = QWebPage.FindFlags(0)
|
||||||
|
|
||||||
def _call_cb(self, callback, found, text, flags, caller):
|
def _call_cb(self, callback, found, text, flags, caller):
|
||||||
@ -115,7 +115,11 @@ class WebKitSearch(browsertab.AbstractSearch):
|
|||||||
if callback is not None:
|
if callback is not None:
|
||||||
QTimer.singleShot(0, functools.partial(callback, found))
|
QTimer.singleShot(0, functools.partial(callback, found))
|
||||||
|
|
||||||
|
self.finished.emit(found)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
if self.search_displayed:
|
||||||
|
self.cleared.emit()
|
||||||
self.search_displayed = False
|
self.search_displayed = False
|
||||||
# We first clear the marked text, then the highlights
|
# We first clear the marked text, then the highlights
|
||||||
self._widget.findText('')
|
self._widget.findText('')
|
||||||
@ -348,7 +352,7 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||||||
def selection(self, callback):
|
def selection(self, callback):
|
||||||
callback(self._widget.selectedText())
|
callback(self._widget.selectedText())
|
||||||
|
|
||||||
def follow_selected(self, *, tab=False):
|
def _follow_selected(self, *, tab=False):
|
||||||
if QWebSettings.globalSettings().testAttribute(
|
if QWebSettings.globalSettings().testAttribute(
|
||||||
QWebSettings.JavascriptEnabled):
|
QWebSettings.JavascriptEnabled):
|
||||||
if tab:
|
if tab:
|
||||||
@ -389,6 +393,12 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||||||
else:
|
else:
|
||||||
self._tab.openurl(url)
|
self._tab.openurl(url)
|
||||||
|
|
||||||
|
def follow_selected(self, *, tab=False):
|
||||||
|
try:
|
||||||
|
self._follow_selected(tab=tab)
|
||||||
|
finally:
|
||||||
|
self.follow_selected_done.emit()
|
||||||
|
|
||||||
|
|
||||||
class WebKitZoom(browsertab.AbstractZoom):
|
class WebKitZoom(browsertab.AbstractZoom):
|
||||||
|
|
||||||
@ -631,7 +641,7 @@ class WebKitAudio(browsertab.AbstractAudio):
|
|||||||
|
|
||||||
"""Dummy handling of audio status for QtWebKit."""
|
"""Dummy handling of audio status for QtWebKit."""
|
||||||
|
|
||||||
def set_muted(self, muted: bool):
|
def set_muted(self, muted: bool, override: bool = False):
|
||||||
raise browsertab.WebTabError('Muting is not supported on QtWebKit!')
|
raise browsertab.WebTabError('Muting is not supported on QtWebKit!')
|
||||||
|
|
||||||
def is_muted(self):
|
def is_muted(self):
|
||||||
@ -652,16 +662,16 @@ class WebKitTab(browsertab.AbstractTab):
|
|||||||
private=private, tab=self)
|
private=private, tab=self)
|
||||||
if private:
|
if private:
|
||||||
self._make_private(widget)
|
self._make_private(widget)
|
||||||
self.history = WebKitHistory(self)
|
self.history = WebKitHistory(tab=self)
|
||||||
self.scroller = WebKitScroller(self, parent=self)
|
self.scroller = WebKitScroller(tab=self, parent=self)
|
||||||
self.caret = WebKitCaret(mode_manager=mode_manager,
|
self.caret = WebKitCaret(mode_manager=mode_manager,
|
||||||
tab=self, parent=self)
|
tab=self, parent=self)
|
||||||
self.zoom = WebKitZoom(tab=self, parent=self)
|
self.zoom = WebKitZoom(tab=self, parent=self)
|
||||||
self.search = WebKitSearch(parent=self)
|
self.search = WebKitSearch(tab=self, parent=self)
|
||||||
self.printing = WebKitPrinting()
|
self.printing = WebKitPrinting(tab=self)
|
||||||
self.elements = WebKitElements(tab=self)
|
self.elements = WebKitElements(tab=self)
|
||||||
self.action = WebKitAction(tab=self)
|
self.action = WebKitAction(tab=self)
|
||||||
self.audio = WebKitAudio(parent=self)
|
self.audio = WebKitAudio(tab=self, parent=self)
|
||||||
# We're assigning settings in _set_widget
|
# We're assigning settings in _set_widget
|
||||||
self.settings = webkitsettings.WebKitSettings(settings=None)
|
self.settings = webkitsettings.WebKitSettings(settings=None)
|
||||||
self._set_widget(widget)
|
self._set_widget(widget)
|
||||||
@ -808,6 +818,10 @@ class WebKitTab(browsertab.AbstractTab):
|
|||||||
if navigation.is_main_frame:
|
if navigation.is_main_frame:
|
||||||
self.settings.update_for_url(navigation.url)
|
self.settings.update_for_url(navigation.url)
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def _on_ssl_errors(self):
|
||||||
|
self._has_ssl_errors = True
|
||||||
|
|
||||||
def _connect_signals(self):
|
def _connect_signals(self):
|
||||||
view = self._widget
|
view = self._widget
|
||||||
page = view.page()
|
page = view.page()
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
import html
|
import html
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
import sip
|
|
||||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||||
@ -31,10 +30,11 @@ from PyQt5.QtPrintSupport import QPrintDialog
|
|||||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.browser import pdfjs, shared
|
from qutebrowser.browser import pdfjs, shared, downloads
|
||||||
from qutebrowser.browser.webkit import http
|
from qutebrowser.browser.webkit import http
|
||||||
from qutebrowser.browser.webkit.network import networkmanager
|
from qutebrowser.browser.webkit.network import networkmanager
|
||||||
from qutebrowser.utils import message, usertypes, log, jinja, objreg
|
from qutebrowser.utils import message, usertypes, log, jinja, objreg
|
||||||
|
from qutebrowser.qt import sip
|
||||||
|
|
||||||
|
|
||||||
class BrowserPage(QWebPage):
|
class BrowserPage(QWebPage):
|
||||||
@ -206,17 +206,6 @@ class BrowserPage(QWebPage):
|
|||||||
suggested_file)
|
suggested_file)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _show_pdfjs(self, reply):
|
|
||||||
"""Show the reply with pdfjs."""
|
|
||||||
try:
|
|
||||||
page = pdfjs.generate_pdfjs_page(reply.url())
|
|
||||||
except pdfjs.PDFJSNotFound:
|
|
||||||
page = jinja.render('no_pdfjs.html',
|
|
||||||
url=reply.url().toDisplayString())
|
|
||||||
self.mainFrame().setContent(page.encode('utf-8'), 'text/html',
|
|
||||||
reply.url())
|
|
||||||
reply.deleteLater()
|
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
"""Prepare the web page for being deleted."""
|
"""Prepare the web page for being deleted."""
|
||||||
self._is_shutting_down = True
|
self._is_shutting_down = True
|
||||||
@ -279,10 +268,10 @@ class BrowserPage(QWebPage):
|
|||||||
else:
|
else:
|
||||||
reply.finished.connect(functools.partial(
|
reply.finished.connect(functools.partial(
|
||||||
self.display_content, reply, 'image/jpeg'))
|
self.display_content, reply, 'image/jpeg'))
|
||||||
elif (mimetype in ['application/pdf', 'application/x-pdf'] and
|
elif pdfjs.should_use_pdfjs(mimetype, reply.url()):
|
||||||
config.val.content.pdfjs):
|
download_manager.fetch(reply,
|
||||||
# Use pdf.js to display the page
|
target=downloads.PDFJSDownloadTarget(),
|
||||||
self._show_pdfjs(reply)
|
auto_remove=True)
|
||||||
else:
|
else:
|
||||||
# Unknown mimetype, so download anyways.
|
# Unknown mimetype, so download anyways.
|
||||||
download_manager.fetch(reply,
|
download_manager.fetch(reply,
|
||||||
@ -415,7 +404,7 @@ class BrowserPage(QWebPage):
|
|||||||
|
|
||||||
def userAgentForUrl(self, url):
|
def userAgentForUrl(self, url):
|
||||||
"""Override QWebPage::userAgentForUrl to customize the user agent."""
|
"""Override QWebPage::userAgentForUrl to customize the user agent."""
|
||||||
ua = config.val.content.headers.user_agent
|
ua = config.instance.get('content.headers.user_agent', url=url)
|
||||||
if ua is None:
|
if ua is None:
|
||||||
return super().userAgentForUrl(url)
|
return super().userAgentForUrl(url)
|
||||||
else:
|
else:
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
"""The main browser widgets."""
|
"""The main browser widgets."""
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
|
from PyQt5.QtCore import pyqtSignal, Qt, QUrl
|
||||||
from PyQt5.QtGui import QPalette
|
from PyQt5.QtGui import QPalette
|
||||||
from PyQt5.QtWidgets import QStyleFactory
|
from PyQt5.QtWidgets import QStyleFactory
|
||||||
from PyQt5.QtWebKit import QWebSettings
|
from PyQt5.QtWebKit import QWebSettings
|
||||||
@ -78,10 +78,6 @@ class WebView(QWebView):
|
|||||||
|
|
||||||
self.setPage(page)
|
self.setPage(page)
|
||||||
|
|
||||||
mode_manager = objreg.get('mode-manager', scope='window',
|
|
||||||
window=win_id)
|
|
||||||
mode_manager.entered.connect(self.on_mode_entered)
|
|
||||||
mode_manager.left.connect(self.on_mode_left)
|
|
||||||
config.instance.changed.connect(self._set_bg_color)
|
config.instance.changed.connect(self._set_bg_color)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -130,32 +126,6 @@ class WebView(QWebView):
|
|||||||
"""
|
"""
|
||||||
self.load(url)
|
self.load(url)
|
||||||
|
|
||||||
@pyqtSlot(usertypes.KeyMode)
|
|
||||||
def on_mode_entered(self, mode):
|
|
||||||
"""Ignore attempts to focus the widget if in any status-input mode.
|
|
||||||
|
|
||||||
FIXME:qtwebengine
|
|
||||||
For QtWebEngine, doing the same has no effect, so we do it in here.
|
|
||||||
"""
|
|
||||||
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
|
|
||||||
usertypes.KeyMode.yesno]:
|
|
||||||
log.webview.debug("Ignoring focus because mode {} was "
|
|
||||||
"entered.".format(mode))
|
|
||||||
self.setFocusPolicy(Qt.NoFocus)
|
|
||||||
|
|
||||||
@pyqtSlot(usertypes.KeyMode)
|
|
||||||
def on_mode_left(self, mode):
|
|
||||||
"""Restore focus policy if status-input modes were left.
|
|
||||||
|
|
||||||
FIXME:qtwebengine
|
|
||||||
For QtWebEngine, doing the same has no effect, so we do it in here.
|
|
||||||
"""
|
|
||||||
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
|
|
||||||
usertypes.KeyMode.yesno]:
|
|
||||||
log.webview.debug("Restoring focus policy because mode {} was "
|
|
||||||
"left.".format(mode))
|
|
||||||
self.setFocusPolicy(Qt.WheelFocus)
|
|
||||||
|
|
||||||
def createWindow(self, wintype):
|
def createWindow(self, wintype):
|
||||||
"""Called by Qt when a page wants to create a new window.
|
"""Called by Qt when a page wants to create a new window.
|
||||||
|
|
||||||
|
@ -432,7 +432,7 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
|
|||||||
cmd_path = os.path.expanduser(cmd)
|
cmd_path = os.path.expanduser(cmd)
|
||||||
|
|
||||||
# if cmd is not given as an absolute path, look it up
|
# if cmd is not given as an absolute path, look it up
|
||||||
# ~/.local/share/qutebrowser/userscripts (or $XDG_DATA_DIR)
|
# ~/.local/share/qutebrowser/userscripts (or $XDG_DATA_HOME)
|
||||||
if not os.path.isabs(cmd_path):
|
if not os.path.isabs(cmd_path):
|
||||||
log.misc.debug("{} is no absolute path".format(cmd_path))
|
log.misc.debug("{} is no absolute path".format(cmd_path))
|
||||||
cmd_path = _lookup_path(cmd)
|
cmd_path = _lookup_path(cmd)
|
||||||
|
@ -28,13 +28,27 @@ import html
|
|||||||
from PyQt5.QtWidgets import QStyle, QStyleOptionViewItem, QStyledItemDelegate
|
from PyQt5.QtWidgets import QStyle, QStyleOptionViewItem, QStyledItemDelegate
|
||||||
from PyQt5.QtCore import QRectF, QSize, Qt
|
from PyQt5.QtCore import QRectF, QSize, Qt
|
||||||
from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption,
|
from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption,
|
||||||
QAbstractTextDocumentLayout)
|
QAbstractTextDocumentLayout, QSyntaxHighlighter,
|
||||||
|
QTextCharFormat)
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import qtutils, jinja
|
from qutebrowser.utils import qtutils
|
||||||
|
|
||||||
|
|
||||||
_cached_stylesheet = None
|
class _Highlighter(QSyntaxHighlighter):
|
||||||
|
|
||||||
|
def __init__(self, doc, pattern, color):
|
||||||
|
super().__init__(doc)
|
||||||
|
self._format = QTextCharFormat()
|
||||||
|
self._format.setForeground(color)
|
||||||
|
self._pattern = pattern
|
||||||
|
|
||||||
|
def highlightBlock(self, text):
|
||||||
|
"""Override highlightBlock for custom highlighting."""
|
||||||
|
for match in re.finditer(self._pattern, text, re.IGNORECASE):
|
||||||
|
start, end = match.span()
|
||||||
|
length = end - start
|
||||||
|
self.setFormat(start, length, self._format)
|
||||||
|
|
||||||
|
|
||||||
class CompletionItemDelegate(QStyledItemDelegate):
|
class CompletionItemDelegate(QStyledItemDelegate):
|
||||||
@ -194,21 +208,15 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
self._doc.setDefaultTextOption(text_option)
|
self._doc.setDefaultTextOption(text_option)
|
||||||
self._doc.setDocumentMargin(2)
|
self._doc.setDocumentMargin(2)
|
||||||
|
|
||||||
assert _cached_stylesheet is not None
|
|
||||||
self._doc.setDefaultStyleSheet(_cached_stylesheet)
|
|
||||||
|
|
||||||
if index.parent().isValid():
|
if index.parent().isValid():
|
||||||
view = self.parent()
|
view = self.parent()
|
||||||
pattern = view.pattern
|
pattern = view.pattern
|
||||||
columns_to_filter = index.model().columns_to_filter(index)
|
columns_to_filter = index.model().columns_to_filter(index)
|
||||||
|
self._doc.setPlainText(self._opt.text)
|
||||||
if index.column() in columns_to_filter and pattern:
|
if index.column() in columns_to_filter and pattern:
|
||||||
repl = r'<span class="highlight">\g<0></span>'
|
pat = re.escape(pattern).replace(r'\ ', r'|')
|
||||||
pat = html.escape(re.escape(pattern)).replace(r'\ ', r'|')
|
_Highlighter(self._doc, pat,
|
||||||
txt = html.escape(self._opt.text)
|
config.val.colors.completion.match.fg)
|
||||||
text = re.sub(pat, repl, txt, flags=re.IGNORECASE)
|
|
||||||
self._doc.setHtml(text)
|
|
||||||
else:
|
|
||||||
self._doc.setPlainText(self._opt.text)
|
|
||||||
else:
|
else:
|
||||||
self._doc.setHtml(
|
self._doc.setHtml(
|
||||||
'<span style="font: {};">{}</span>'.format(
|
'<span style="font: {};">{}</span>'.format(
|
||||||
@ -283,24 +291,3 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
self._draw_focus_rect()
|
self._draw_focus_rect()
|
||||||
|
|
||||||
self._painter.restore()
|
self._painter.restore()
|
||||||
|
|
||||||
|
|
||||||
@config.change_filter('colors.completion.match.fg', function=True)
|
|
||||||
def _update_stylesheet():
|
|
||||||
"""Update the cached stylesheet."""
|
|
||||||
stylesheet = """
|
|
||||||
.highlight {
|
|
||||||
color: {{ conf.colors.completion.match.fg }};
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
with jinja.environment.no_autoescape():
|
|
||||||
template = jinja.environment.from_string(stylesheet)
|
|
||||||
|
|
||||||
global _cached_stylesheet
|
|
||||||
_cached_stylesheet = template.render(conf=config.val)
|
|
||||||
|
|
||||||
|
|
||||||
def init():
|
|
||||||
"""Initialize the cached stylesheet."""
|
|
||||||
_update_stylesheet()
|
|
||||||
config.instance.changed.connect(_update_stylesheet)
|
|
||||||
|
@ -27,12 +27,7 @@ from qutebrowser.keyinput import keyutils
|
|||||||
|
|
||||||
def option(*, info):
|
def option(*, info):
|
||||||
"""A CompletionModel filled with settings and their descriptions."""
|
"""A CompletionModel filled with settings and their descriptions."""
|
||||||
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
|
return _option(info, "Options", lambda opt: not opt.no_autoconfig)
|
||||||
options = ((opt.name, opt.description, info.config.get_str(opt.name))
|
|
||||||
for opt in configdata.DATA.values()
|
|
||||||
if not opt.no_autoconfig)
|
|
||||||
model.add_category(listcategory.ListCategory("Options", options))
|
|
||||||
return model
|
|
||||||
|
|
||||||
|
|
||||||
def customized_option(*, info):
|
def customized_option(*, info):
|
||||||
@ -47,6 +42,37 @@ def customized_option(*, info):
|
|||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
|
def list_option(*, info):
|
||||||
|
"""A CompletionModel filled with settings whose values are lists."""
|
||||||
|
predicate = lambda opt: (isinstance(info.config.get_obj(opt.name),
|
||||||
|
list) and not opt.no_autoconfig)
|
||||||
|
return _option(info, "List options", predicate)
|
||||||
|
|
||||||
|
|
||||||
|
def dict_option(*, info):
|
||||||
|
"""A CompletionModel filled with settings whose values are dicts."""
|
||||||
|
predicate = lambda opt: (isinstance(info.config.get_obj(opt.name),
|
||||||
|
dict) and not opt.no_autoconfig)
|
||||||
|
return _option(info, "Dict options", predicate)
|
||||||
|
|
||||||
|
|
||||||
|
def _option(info, title, predicate):
|
||||||
|
"""A CompletionModel that is generated for several option sets.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
info: The config info that can be passed through.
|
||||||
|
title: The title of the options.
|
||||||
|
predicate: The function for filtering out the options. Takes a single
|
||||||
|
argument.
|
||||||
|
"""
|
||||||
|
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
|
||||||
|
options = ((opt.name, opt.description, info.config.get_str(opt.name))
|
||||||
|
for opt in configdata.DATA.values()
|
||||||
|
if predicate(opt))
|
||||||
|
model.add_category(listcategory.ListCategory(title, options))
|
||||||
|
return model
|
||||||
|
|
||||||
|
|
||||||
def value(optname, *values, info):
|
def value(optname, *values, info):
|
||||||
"""A CompletionModel filled with setting values.
|
"""A CompletionModel filled with setting values.
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class HistoryCategory(QSqlQueryModel):
|
|||||||
|
|
||||||
def _atime_expr(self):
|
def _atime_expr(self):
|
||||||
"""If max_items is set, return an expression to limit the query."""
|
"""If max_items is set, return an expression to limit the query."""
|
||||||
max_items = config.val.completion.web_history_max_items
|
max_items = config.val.completion.web_history.max_items
|
||||||
# HistoryCategory should not be added to the completion in that case.
|
# HistoryCategory should not be added to the completion in that case.
|
||||||
assert max_items != 0
|
assert max_items != 0
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ class HistoryCategory(QSqlQueryModel):
|
|||||||
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
||||||
.format(timestamp_format.replace("'", "`")))
|
.format(timestamp_format.replace("'", "`")))
|
||||||
|
|
||||||
if not self._query or len(words) != len(self._query.boundValues()):
|
if not self._query or len(words) != len(self._query.bound_values()):
|
||||||
# if the number of words changed, we need to generate a new query
|
# if the number of words changed, we need to generate a new query
|
||||||
# otherwise, we can reuse the prepared query for performance
|
# otherwise, we can reuse the prepared query for performance
|
||||||
self._query = sql.Query(' '.join([
|
self._query = sql.Query(' '.join([
|
||||||
@ -100,14 +100,14 @@ class HistoryCategory(QSqlQueryModel):
|
|||||||
with debug.log_time('sql', 'Running completion query'):
|
with debug.log_time('sql', 'Running completion query'):
|
||||||
self._query.run(**{
|
self._query.run(**{
|
||||||
str(i): w for i, w in enumerate(words)})
|
str(i): w for i, w in enumerate(words)})
|
||||||
self.setQuery(self._query)
|
self.setQuery(self._query.query)
|
||||||
|
|
||||||
def removeRows(self, row, _count, _parent=None):
|
def removeRows(self, row, _count, _parent=None):
|
||||||
"""Override QAbstractItemModel::removeRows to re-run sql query."""
|
"""Override QAbstractItemModel::removeRows to re-run sql query."""
|
||||||
# re-run query to reload updated table
|
# re-run query to reload updated table
|
||||||
with debug.log_time('sql', 'Re-running completion query post-delete'):
|
with debug.log_time('sql', 'Re-running completion query post-delete'):
|
||||||
self._query.run()
|
self._query.run()
|
||||||
self.setQuery(self._query)
|
self.setQuery(self._query.query)
|
||||||
while self.rowCount() < row:
|
while self.rowCount() < row:
|
||||||
self.fetchMore()
|
self.fetchMore()
|
||||||
return True
|
return True
|
||||||
|
@ -24,7 +24,7 @@ import re
|
|||||||
from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp
|
from PyQt5.QtCore import Qt, QSortFilterProxyModel, QRegExp
|
||||||
from PyQt5.QtGui import QStandardItem, QStandardItemModel
|
from PyQt5.QtGui import QStandardItem, QStandardItemModel
|
||||||
|
|
||||||
from qutebrowser.utils import qtutils
|
from qutebrowser.utils import qtutils, log
|
||||||
|
|
||||||
|
|
||||||
class ListCategory(QSortFilterProxyModel):
|
class ListCategory(QSortFilterProxyModel):
|
||||||
@ -80,6 +80,13 @@ class ListCategory(QSortFilterProxyModel):
|
|||||||
left = self.srcmodel.data(lindex)
|
left = self.srcmodel.data(lindex)
|
||||||
right = self.srcmodel.data(rindex)
|
right = self.srcmodel.data(rindex)
|
||||||
|
|
||||||
|
if left is None or right is None: # pragma: no cover
|
||||||
|
log.completion.warning("Got unexpected None value, "
|
||||||
|
"left={!r} right={!r} "
|
||||||
|
"lindex={!r} rindex={!r}"
|
||||||
|
.format(left, right, lindex, rindex))
|
||||||
|
return False
|
||||||
|
|
||||||
leftstart = left.startswith(self._pattern)
|
leftstart = left.startswith(self._pattern)
|
||||||
rightstart = right.startswith(self._pattern)
|
rightstart = right.startswith(self._pattern)
|
||||||
|
|
||||||
|
@ -122,8 +122,8 @@ def _buffer(skip_win_id=None):
|
|||||||
tabs.append(("{}/{}".format(win_id, idx + 1),
|
tabs.append(("{}/{}".format(win_id, idx + 1),
|
||||||
tab.url().toDisplayString(),
|
tab.url().toDisplayString(),
|
||||||
tabbed_browser.widget.page_title(idx)))
|
tabbed_browser.widget.page_title(idx)))
|
||||||
cat = listcategory.ListCategory("{}".format(win_id), tabs,
|
cat = listcategory.ListCategory(
|
||||||
delete_func=delete_buffer)
|
str(win_id), tabs, delete_func=delete_buffer, sort=False)
|
||||||
model.add_category(cat)
|
model.add_category(cat)
|
||||||
|
|
||||||
return model
|
return model
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
from qutebrowser.completion.models import (completionmodel, listcategory,
|
from qutebrowser.completion.models import (completionmodel, listcategory,
|
||||||
histcategory)
|
histcategory)
|
||||||
from qutebrowser.utils import log, objreg
|
from qutebrowser.utils import log, objreg
|
||||||
|
from qutebrowser.config import config
|
||||||
|
|
||||||
|
|
||||||
_URLCOL = 0
|
_URLCOL = 0
|
||||||
@ -50,25 +51,48 @@ def _delete_quickmark(data):
|
|||||||
|
|
||||||
|
|
||||||
def url(*, info):
|
def url(*, info):
|
||||||
"""A model which combines bookmarks, quickmarks and web history URLs.
|
"""A model which combines various URLs.
|
||||||
|
|
||||||
|
This combines:
|
||||||
|
- bookmarks
|
||||||
|
- quickmarks
|
||||||
|
- search engines
|
||||||
|
- web history URLs
|
||||||
|
|
||||||
Used for the `open` command.
|
Used for the `open` command.
|
||||||
"""
|
"""
|
||||||
model = completionmodel.CompletionModel(column_widths=(40, 50, 10))
|
model = completionmodel.CompletionModel(column_widths=(40, 50, 10))
|
||||||
|
|
||||||
|
# pylint: disable=bad-config-option
|
||||||
quickmarks = [(url, name) for (name, url)
|
quickmarks = [(url, name) for (name, url)
|
||||||
in objreg.get('quickmark-manager').marks.items()]
|
in objreg.get('quickmark-manager').marks.items()]
|
||||||
bookmarks = objreg.get('bookmark-manager').marks.items()
|
bookmarks = objreg.get('bookmark-manager').marks.items()
|
||||||
|
searchengines = [(k, v) for k, v
|
||||||
|
in sorted(config.val.url.searchengines.items())
|
||||||
|
if k != 'DEFAULT']
|
||||||
|
# pylint: enable=bad-config-option
|
||||||
|
categories = config.val.completion.open_categories
|
||||||
|
models = {}
|
||||||
|
|
||||||
if quickmarks:
|
if searchengines and 'searchengines' in categories:
|
||||||
model.add_category(listcategory.ListCategory(
|
models['searchengines'] = listcategory.ListCategory(
|
||||||
|
'Search engines', searchengines, sort=False)
|
||||||
|
|
||||||
|
if quickmarks and 'quickmarks' in categories:
|
||||||
|
models['quickmarks'] = listcategory.ListCategory(
|
||||||
'Quickmarks', quickmarks, delete_func=_delete_quickmark,
|
'Quickmarks', quickmarks, delete_func=_delete_quickmark,
|
||||||
sort=False))
|
sort=False)
|
||||||
if bookmarks:
|
if bookmarks and 'bookmarks' in categories:
|
||||||
model.add_category(listcategory.ListCategory(
|
models['bookmarks'] = listcategory.ListCategory(
|
||||||
'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False))
|
'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False)
|
||||||
|
|
||||||
if info.config.get('completion.web_history_max_items') != 0:
|
history_disabled = info.config.get('completion.web_history.max_items') == 0
|
||||||
|
if not history_disabled and 'history' in categories:
|
||||||
hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)
|
hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)
|
||||||
model.add_category(hist_cat)
|
models['history'] = hist_cat
|
||||||
|
|
||||||
|
for category in categories:
|
||||||
|
if category in models:
|
||||||
|
model.add_category(models[category])
|
||||||
|
|
||||||
return model
|
return model
|
||||||
|
@ -34,6 +34,7 @@ from qutebrowser.keyinput import keyutils
|
|||||||
val = None
|
val = None
|
||||||
instance = None
|
instance = None
|
||||||
key_instance = None
|
key_instance = None
|
||||||
|
cache = None
|
||||||
|
|
||||||
# Keeping track of all change filters to validate them later.
|
# Keeping track of all change filters to validate them later.
|
||||||
change_filters = []
|
change_filters = []
|
||||||
|
50
qutebrowser/config/configcache.py
Normal file
50
qutebrowser/config/configcache.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2018 Jay Kamat <jaygkamat@gmail.com>
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
|
||||||
|
"""Implementation of a basic config cache."""
|
||||||
|
|
||||||
|
from qutebrowser.config import config
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigCache:
|
||||||
|
|
||||||
|
"""A 'high-performance' cache for the config system.
|
||||||
|
|
||||||
|
Useful for areas which call out to the config system very frequently, DO
|
||||||
|
NOT modify the value returned, DO NOT require per-url settings, do not
|
||||||
|
change frequently, and do not require partially 'expanded' config paths.
|
||||||
|
|
||||||
|
If any of these requirements are broken, you will get incorrect or slow
|
||||||
|
behavior.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._cache = {}
|
||||||
|
config.instance.changed.connect(self._on_config_changed)
|
||||||
|
|
||||||
|
def _on_config_changed(self, attr: str) -> None:
|
||||||
|
if attr in self._cache:
|
||||||
|
self._cache[attr] = config.instance.get(attr)
|
||||||
|
|
||||||
|
def __getitem__(self, attr: str):
|
||||||
|
if attr not in self._cache:
|
||||||
|
assert not config.instance.get_opt(attr).supports_pattern
|
||||||
|
self._cache[attr] = config.instance.get(attr)
|
||||||
|
return self._cache[attr]
|
@ -85,8 +85,8 @@ class ConfigCommands:
|
|||||||
*, pattern=None):
|
*, pattern=None):
|
||||||
"""Set an option.
|
"""Set an option.
|
||||||
|
|
||||||
If the option name ends with '?', the value of the option is shown
|
If the option name ends with '?' or no value is provided, the
|
||||||
instead.
|
value of the option is shown instead.
|
||||||
|
|
||||||
Using :set without any arguments opens a page where settings can be
|
Using :set without any arguments opens a page where settings can be
|
||||||
changed interactively.
|
changed interactively.
|
||||||
@ -116,8 +116,7 @@ class ConfigCommands:
|
|||||||
|
|
||||||
with self._handle_config_error():
|
with self._handle_config_error():
|
||||||
if value is None:
|
if value is None:
|
||||||
raise cmdexc.CommandError("set: The following arguments "
|
self._print_value(option, pattern=pattern)
|
||||||
"are required: value")
|
|
||||||
else:
|
else:
|
||||||
self._config.set_str(option, value, pattern=pattern,
|
self._config.set_str(option, value, pattern=pattern,
|
||||||
save_yaml=not temp)
|
save_yaml=not temp)
|
||||||
@ -246,11 +245,114 @@ class ConfigCommands:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
option: The name of the option.
|
option: The name of the option.
|
||||||
temp: Don't touch autoconfig.yml.
|
temp: Set value temporarily until qutebrowser is closed.
|
||||||
"""
|
"""
|
||||||
with self._handle_config_error():
|
with self._handle_config_error():
|
||||||
self._config.unset(option, save_yaml=not temp)
|
self._config.unset(option, save_yaml=not temp)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='config-commands')
|
||||||
|
@cmdutils.argument('option', completion=configmodel.list_option)
|
||||||
|
def config_list_add(self, option, value, temp=False):
|
||||||
|
"""Append a value to a config option that is a list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option: The name of the option.
|
||||||
|
value: The value to append to the end of the list.
|
||||||
|
temp: Add value temporarily until qutebrowser is closed.
|
||||||
|
"""
|
||||||
|
opt = self._config.get_opt(option)
|
||||||
|
valid_list_types = (configtypes.List, configtypes.ListOrValue)
|
||||||
|
if not isinstance(opt.typ, valid_list_types):
|
||||||
|
raise cmdexc.CommandError(":config-list-add can only be used for "
|
||||||
|
"lists")
|
||||||
|
|
||||||
|
with self._handle_config_error():
|
||||||
|
option_value = self._config.get_mutable_obj(option)
|
||||||
|
option_value.append(value)
|
||||||
|
self._config.update_mutables(save_yaml=not temp)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='config-commands')
|
||||||
|
@cmdutils.argument('option', completion=configmodel.dict_option)
|
||||||
|
def config_dict_add(self, option, key, value, temp=False, replace=False):
|
||||||
|
"""Add a key/value pair to a dictionary option.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option: The name of the option.
|
||||||
|
key: The key to use.
|
||||||
|
value: The value to place in the dictionary.
|
||||||
|
temp: Add value temporarily until qutebrowser is closed.
|
||||||
|
replace: Replace existing values. By default, existing values are
|
||||||
|
not overwritten.
|
||||||
|
"""
|
||||||
|
opt = self._config.get_opt(option)
|
||||||
|
if not isinstance(opt.typ, configtypes.Dict):
|
||||||
|
raise cmdexc.CommandError(":config-dict-add can only be used for "
|
||||||
|
"dicts")
|
||||||
|
|
||||||
|
with self._handle_config_error():
|
||||||
|
option_value = self._config.get_mutable_obj(option)
|
||||||
|
|
||||||
|
if key in option_value and not replace:
|
||||||
|
raise cmdexc.CommandError("{} already exists in {} - use "
|
||||||
|
"--replace to overwrite!"
|
||||||
|
.format(key, option))
|
||||||
|
|
||||||
|
option_value[key] = value
|
||||||
|
self._config.update_mutables(save_yaml=not temp)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='config-commands')
|
||||||
|
@cmdutils.argument('option', completion=configmodel.list_option)
|
||||||
|
def config_list_remove(self, option, value, temp=False):
|
||||||
|
"""Remove a value from a list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option: The name of the option.
|
||||||
|
value: The value to remove from the list.
|
||||||
|
temp: Remove value temporarily until qutebrowser is closed.
|
||||||
|
"""
|
||||||
|
opt = self._config.get_opt(option)
|
||||||
|
valid_list_types = (configtypes.List, configtypes.ListOrValue)
|
||||||
|
if not isinstance(opt.typ, valid_list_types):
|
||||||
|
raise cmdexc.CommandError(":config-list-remove can only be used "
|
||||||
|
"for lists")
|
||||||
|
|
||||||
|
with self._handle_config_error():
|
||||||
|
option_value = self._config.get_mutable_obj(option)
|
||||||
|
|
||||||
|
if value not in option_value:
|
||||||
|
raise cmdexc.CommandError("{} is not in {}!".format(value,
|
||||||
|
option))
|
||||||
|
|
||||||
|
option_value.remove(value)
|
||||||
|
|
||||||
|
self._config.update_mutables(save_yaml=not temp)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='config-commands')
|
||||||
|
@cmdutils.argument('option', completion=configmodel.dict_option)
|
||||||
|
def config_dict_remove(self, option, key, temp=False):
|
||||||
|
"""Remove a key from a dict.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option: The name of the option.
|
||||||
|
key: The key to remove from the dict.
|
||||||
|
temp: Remove value temporarily until qutebrowser is closed.
|
||||||
|
"""
|
||||||
|
opt = self._config.get_opt(option)
|
||||||
|
if not isinstance(opt.typ, configtypes.Dict):
|
||||||
|
raise cmdexc.CommandError(":config-dict-remove can only be used "
|
||||||
|
"for dicts")
|
||||||
|
|
||||||
|
with self._handle_config_error():
|
||||||
|
option_value = self._config.get_mutable_obj(option)
|
||||||
|
|
||||||
|
if key not in option_value:
|
||||||
|
raise cmdexc.CommandError("{} is not in {}!".format(key,
|
||||||
|
option))
|
||||||
|
|
||||||
|
del option_value[key]
|
||||||
|
|
||||||
|
self._config.update_mutables(save_yaml=not temp)
|
||||||
|
|
||||||
@cmdutils.register(instance='config-commands')
|
@cmdutils.register(instance='config-commands')
|
||||||
def config_clear(self, save=False):
|
def config_clear(self, save=False):
|
||||||
"""Set all settings back to their default.
|
"""Set all settings back to their default.
|
||||||
|
@ -56,7 +56,7 @@ class Option:
|
|||||||
@attr.s
|
@attr.s
|
||||||
class Migrations:
|
class Migrations:
|
||||||
|
|
||||||
"""Nigrated options in configdata.yml.
|
"""Migrated options in configdata.yml.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
renamed: A dict mapping old option names to new names.
|
renamed: A dict mapping old option names to new names.
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
aliases:
|
aliases:
|
||||||
default:
|
default:
|
||||||
w: session-save
|
w: session-save
|
||||||
q: quit
|
q: close
|
||||||
|
qa: quit
|
||||||
wq: quit --save
|
wq: quit --save
|
||||||
|
wqa: quit --save
|
||||||
type:
|
type:
|
||||||
name: Dict
|
name: Dict
|
||||||
keytype:
|
keytype:
|
||||||
@ -181,6 +183,51 @@ qt.force_platform:
|
|||||||
This sets the `QT_QPA_PLATFORM` environment variable and is useful to force
|
This sets the `QT_QPA_PLATFORM` environment variable and is useful to force
|
||||||
using the XCB plugin when running QtWebEngine on Wayland.
|
using the XCB plugin when running QtWebEngine on Wayland.
|
||||||
|
|
||||||
|
qt.process_model:
|
||||||
|
type:
|
||||||
|
name: String
|
||||||
|
valid_values:
|
||||||
|
- process-per-site-instance: Pages from separate sites are put into
|
||||||
|
separate processes and separate visits to the same site are also
|
||||||
|
isolated.
|
||||||
|
- process-per-site: Pages from separate sites are put into separate
|
||||||
|
processes. Unlike Process per Site Instance, all visits to the same
|
||||||
|
site will share an OS process. The benefit of this model is reduced
|
||||||
|
memory consumption, because more web pages will share processes.
|
||||||
|
The drawbacks include reduced security, robustness, and
|
||||||
|
responsiveness.
|
||||||
|
- single-process: Run all tabs in a single process. This should be used
|
||||||
|
for debugging purposes only, and it disables `:open --private`.
|
||||||
|
default: process-per-site-instance
|
||||||
|
backend: QtWebEngine
|
||||||
|
restart: true
|
||||||
|
desc: >-
|
||||||
|
Which Chromium process model to use.
|
||||||
|
|
||||||
|
Alternative process models use less resources, but decrease security and
|
||||||
|
robustness.
|
||||||
|
|
||||||
|
See the following pages for more details:
|
||||||
|
|
||||||
|
- https://www.chromium.org/developers/design-documents/process-models
|
||||||
|
- https://doc.qt.io/qt-5/qtwebengine-features.html#process-models
|
||||||
|
|
||||||
|
qt.low_end_device_mode:
|
||||||
|
type:
|
||||||
|
name: String
|
||||||
|
valid_values:
|
||||||
|
- always: Always use low-end device mode.
|
||||||
|
- auto: Decide automatically (uses low-end mode with < 1 GB available
|
||||||
|
RAM).
|
||||||
|
- never: Never use low-end device mode.
|
||||||
|
default: auto
|
||||||
|
backend: QtWebEngine
|
||||||
|
restart: true
|
||||||
|
desc: >-
|
||||||
|
When to use Chromium's low-end device mode.
|
||||||
|
|
||||||
|
This improves the RAM usage of renderer processes, at the expense of
|
||||||
|
performance.
|
||||||
|
|
||||||
qt.highdpi:
|
qt.highdpi:
|
||||||
type: Bool
|
type: Bool
|
||||||
@ -220,10 +267,12 @@ content.autoplay:
|
|||||||
backend:
|
backend:
|
||||||
QtWebEngine: Qt 5.10
|
QtWebEngine: Qt 5.10
|
||||||
QtWebKit: false
|
QtWebKit: false
|
||||||
|
supports_pattern: true
|
||||||
desc: >-
|
desc: >-
|
||||||
Automatically start playing `<video>` elements.
|
Automatically start playing `<video>` elements.
|
||||||
|
|
||||||
Note this option needs a restart with QtWebEngine on Qt < 5.11.
|
Note: On Qt < 5.11, this option needs a restart and does not support URL
|
||||||
|
patterns.
|
||||||
|
|
||||||
content.cache.size:
|
content.cache.size:
|
||||||
default: null
|
default: null
|
||||||
@ -321,6 +370,7 @@ content.windowed_fullscreen:
|
|||||||
content.desktop_capture:
|
content.desktop_capture:
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
default: ask
|
default: ask
|
||||||
|
supports_pattern: true
|
||||||
desc: >-
|
desc: >-
|
||||||
Allow websites to share screen content.
|
Allow websites to share screen content.
|
||||||
|
|
||||||
@ -350,14 +400,28 @@ content.frame_flattening:
|
|||||||
content.geolocation:
|
content.geolocation:
|
||||||
default: ask
|
default: ask
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
|
supports_pattern: true
|
||||||
desc: Allow websites to request geolocations.
|
desc: Allow websites to request geolocations.
|
||||||
|
|
||||||
|
content.mouse_lock:
|
||||||
|
default: ask
|
||||||
|
type: BoolAsk
|
||||||
|
supports_pattern: true
|
||||||
|
backend:
|
||||||
|
QtWebKit: false
|
||||||
|
QtWebEngine: Qt 5.8
|
||||||
|
desc: Allow websites to lock your mouse pointer.
|
||||||
|
|
||||||
content.headers.accept_language:
|
content.headers.accept_language:
|
||||||
type:
|
type:
|
||||||
name: String
|
name: String
|
||||||
none_ok: true
|
none_ok: true
|
||||||
|
supports_pattern: true
|
||||||
default: en-US,en
|
default: en-US,en
|
||||||
desc: Value to send in the `Accept-Language` header.
|
desc: >-
|
||||||
|
Value to send in the `Accept-Language` header.
|
||||||
|
|
||||||
|
Note that the value read from JavaScript is always the global value.
|
||||||
|
|
||||||
content.headers.custom:
|
content.headers.custom:
|
||||||
default: {}
|
default: {}
|
||||||
@ -370,6 +434,7 @@ content.headers.custom:
|
|||||||
name: String
|
name: String
|
||||||
encoding: ascii
|
encoding: ascii
|
||||||
none_ok: true
|
none_ok: true
|
||||||
|
supports_pattern: true
|
||||||
desc: Custom headers for qutebrowser HTTP requests.
|
desc: Custom headers for qutebrowser HTTP requests.
|
||||||
|
|
||||||
content.headers.do_not_track:
|
content.headers.do_not_track:
|
||||||
@ -377,6 +442,7 @@ content.headers.do_not_track:
|
|||||||
name: Bool
|
name: Bool
|
||||||
none_ok: true
|
none_ok: true
|
||||||
default: true
|
default: true
|
||||||
|
supports_pattern: true
|
||||||
desc: >-
|
desc: >-
|
||||||
Value to send in the `DNT` header.
|
Value to send in the `DNT` header.
|
||||||
|
|
||||||
@ -392,14 +458,18 @@ content.headers.referer:
|
|||||||
- never: "Never send the Referer. This is not recommended, as some sites
|
- never: "Never send the Referer. This is not recommended, as some sites
|
||||||
may break."
|
may break."
|
||||||
- same-domain: "Only send the Referer for the same domain. This will
|
- same-domain: "Only send the Referer for the same domain. This will
|
||||||
still protect your privacy, but shouldn't break any sites."
|
still protect your privacy, but shouldn't break any sites. With
|
||||||
backend: QtWebKit
|
QtWebEngine, the referer will still be sent for other domains, but
|
||||||
|
with stripped path information."
|
||||||
|
restart: true
|
||||||
desc: >-
|
desc: >-
|
||||||
When to send the Referer header.
|
When to send the Referer header.
|
||||||
|
|
||||||
The Referer header tells websites from which website you were coming from
|
The Referer header tells websites from which website you were coming from
|
||||||
when visiting them.
|
when visiting them.
|
||||||
|
|
||||||
|
No restart is needed with QtWebKit.
|
||||||
|
|
||||||
content.headers.user_agent:
|
content.headers.user_agent:
|
||||||
default: null
|
default: null
|
||||||
type:
|
type:
|
||||||
@ -451,10 +521,15 @@ content.headers.user_agent:
|
|||||||
Gecko"
|
Gecko"
|
||||||
- IE 11.0 for Desktop Win7 64-bit
|
- IE 11.0 for Desktop Win7 64-bit
|
||||||
|
|
||||||
desc: User agent to send. Unset to send the default.
|
supports_pattern: true
|
||||||
|
desc: >-
|
||||||
|
User agent to send. Unset to send the default.
|
||||||
|
|
||||||
|
Note that the value read from JavaScript is always the global value.
|
||||||
|
|
||||||
content.host_blocking.enabled:
|
content.host_blocking.enabled:
|
||||||
default: true
|
default: true
|
||||||
|
supports_pattern: true
|
||||||
type: Bool
|
type: Bool
|
||||||
desc: Enable host blocking.
|
desc: Enable host blocking.
|
||||||
|
|
||||||
@ -475,18 +550,26 @@ content.host_blocking.lists:
|
|||||||
- A zip-file of any of the above, with either only one file, or a file
|
- A zip-file of any of the above, with either only one file, or a file
|
||||||
named `hosts` (with any extension).
|
named `hosts` (with any extension).
|
||||||
|
|
||||||
|
It's also possible to add a local file or directory via a `file://` URL. In
|
||||||
|
case of a directory, all files in the directory are read as adblock lists.
|
||||||
|
|
||||||
|
The file `~/.config/qutebrowser/blocked-hosts` is always read if it exists.
|
||||||
|
|
||||||
content.host_blocking.whitelist:
|
content.host_blocking.whitelist:
|
||||||
default:
|
default:
|
||||||
- piwik.org
|
- piwik.org
|
||||||
type:
|
type:
|
||||||
name: List
|
name: List
|
||||||
valtype: String
|
valtype: UrlPattern
|
||||||
none_ok: true
|
none_ok: true
|
||||||
desc: >-
|
desc: >-
|
||||||
List of domains that should always be loaded, despite being ad-blocked.
|
A list of patterns that should always be loaded, despite being ad-blocked.
|
||||||
|
|
||||||
Domains may contain * and ? wildcards and are otherwise required to exactly
|
Note this whitelists blocked hosts, not first-party URLs. As an example, if
|
||||||
match the requested domain.
|
`example.org` loads an ad from `ads.example.org`, the whitelisted host
|
||||||
|
should be `ads.example.org`. If you want to disable the adblocker on a
|
||||||
|
given page, use the `content.host_blocking.enabled` setting with a URL
|
||||||
|
pattern instead.
|
||||||
|
|
||||||
Local domains are always exempt from hostblocking.
|
Local domains are always exempt from hostblocking.
|
||||||
|
|
||||||
@ -594,6 +677,7 @@ content.local_storage:
|
|||||||
content.media_capture:
|
content.media_capture:
|
||||||
default: ask
|
default: ask
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
|
supports_pattern: true
|
||||||
backend: QtWebEngine
|
backend: QtWebEngine
|
||||||
desc: Allow websites to record audio/video.
|
desc: Allow websites to record audio/video.
|
||||||
|
|
||||||
@ -610,13 +694,13 @@ content.netrc_file:
|
|||||||
content.notifications:
|
content.notifications:
|
||||||
default: ask
|
default: ask
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
|
supports_pattern: true
|
||||||
backend: QtWebKit
|
backend: QtWebKit
|
||||||
desc: Allow websites to show notifications.
|
desc: Allow websites to show notifications.
|
||||||
|
|
||||||
content.pdfjs:
|
content.pdfjs:
|
||||||
default: false
|
default: false
|
||||||
type: Bool
|
type: Bool
|
||||||
backend: QtWebKit
|
|
||||||
desc: >-
|
desc: >-
|
||||||
Allow pdf.js to view PDF files in the browser.
|
Allow pdf.js to view PDF files in the browser.
|
||||||
|
|
||||||
@ -626,6 +710,7 @@ content.pdfjs:
|
|||||||
content.persistent_storage:
|
content.persistent_storage:
|
||||||
default: ask
|
default: ask
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
|
supports_pattern: true
|
||||||
backend:
|
backend:
|
||||||
QtWebKit: false
|
QtWebKit: false
|
||||||
QtWebEngine: Qt 5.11
|
QtWebEngine: Qt 5.11
|
||||||
@ -672,6 +757,7 @@ content.proxy_dns_requests:
|
|||||||
content.register_protocol_handler:
|
content.register_protocol_handler:
|
||||||
default: ask
|
default: ask
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
|
supports_pattern: true
|
||||||
backend:
|
backend:
|
||||||
QtWebKit: false
|
QtWebKit: false
|
||||||
QtWebEngine: Qt 5.11
|
QtWebEngine: Qt 5.11
|
||||||
@ -681,6 +767,7 @@ content.register_protocol_handler:
|
|||||||
content.ssl_strict:
|
content.ssl_strict:
|
||||||
default: ask
|
default: ask
|
||||||
type: BoolAsk
|
type: BoolAsk
|
||||||
|
supports_pattern: true
|
||||||
desc: Validate SSL handshakes.
|
desc: Validate SSL handshakes.
|
||||||
|
|
||||||
content.user_stylesheets:
|
content.user_stylesheets:
|
||||||
@ -698,29 +785,52 @@ content.webgl:
|
|||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
desc: Enable WebGL.
|
desc: Enable WebGL.
|
||||||
|
|
||||||
content.webrtc_public_interfaces_only:
|
content.webrtc_ip_handling_policy:
|
||||||
default: false
|
default: all-interfaces
|
||||||
type: Bool
|
type:
|
||||||
|
name: String
|
||||||
|
valid_values:
|
||||||
|
- all-interfaces: WebRTC has the right to enumerate all interfaces and
|
||||||
|
bind them to discover public interfaces.
|
||||||
|
- default-public-and-private-interfaces: WebRTC should only use the
|
||||||
|
default route used by http. This also exposes the associated
|
||||||
|
default private address. Default route is the route chosen by the
|
||||||
|
OS on a multi-homed endpoint.
|
||||||
|
- default-public-interface-only: WebRTC should only use the default route
|
||||||
|
used by http. This doesn't expose any local addresses.
|
||||||
|
- disable-non-proxied-udp: WebRTC should only use TCP to contact peers or
|
||||||
|
servers unless the proxy server supports UDP. This doesn't expose
|
||||||
|
any local addresses either.
|
||||||
|
default: all-interfaces
|
||||||
backend:
|
backend:
|
||||||
QtWebKit: false
|
QtWebKit: false
|
||||||
QtWebEngine: Qt 5.9.2
|
QtWebEngine: Qt 5.9.2
|
||||||
|
restart: true
|
||||||
desc: >-
|
desc: >-
|
||||||
Only expose public interfaces via WebRTC.
|
Which interfaces to expose via WebRTC.
|
||||||
|
|
||||||
On Qt 5.9, this option requires a restart.
|
On Qt 5.10, this option doesn't work because of a Qt bug.
|
||||||
On Qt 5.10, this option doesn't work at all because of a Qt bug.
|
|
||||||
On Qt >= 5.11, no restart is required.
|
|
||||||
|
|
||||||
content.xss_auditing:
|
content.xss_auditing:
|
||||||
type: Bool
|
type: Bool
|
||||||
default: false
|
default: true
|
||||||
supports_pattern: true
|
supports_pattern: true
|
||||||
desc: >-
|
desc: >-
|
||||||
Monitor load requests for cross-site scripting attempts.
|
Monitor load requests for cross-site scripting attempts.
|
||||||
|
|
||||||
Suspicious scripts will be blocked and reported in the inspector's
|
Suspicious scripts will be blocked and reported in the inspector's
|
||||||
JavaScript console. Enabling this feature might have an impact on
|
JavaScript console.
|
||||||
performance.
|
|
||||||
|
content.mute:
|
||||||
|
default: false
|
||||||
|
type: Bool
|
||||||
|
supports_pattern: true
|
||||||
|
desc: >-
|
||||||
|
Automatically mute tabs.
|
||||||
|
|
||||||
|
Note that if the `:tab-mute` command is used, the mute status for the
|
||||||
|
affected tab is now controlled manually, and this setting doesn't have any
|
||||||
|
effect.
|
||||||
|
|
||||||
# emacs: '
|
# emacs: '
|
||||||
|
|
||||||
@ -788,7 +898,27 @@ completion.timestamp_format:
|
|||||||
default: '%Y-%m-%d'
|
default: '%Y-%m-%d'
|
||||||
desc: Format of timestamps (e.g. for the history completion).
|
desc: Format of timestamps (e.g. for the history completion).
|
||||||
|
|
||||||
|
completion.web_history.exclude:
|
||||||
|
type:
|
||||||
|
name: List
|
||||||
|
valtype: UrlPattern
|
||||||
|
none_ok: true
|
||||||
|
default: []
|
||||||
|
restart: true
|
||||||
|
desc: >-
|
||||||
|
A list of patterns which should not be shown in the history.
|
||||||
|
|
||||||
|
This only affects the completion. Matching URLs are still saved in the
|
||||||
|
history (and visible on the qute://history page), but hidden in the
|
||||||
|
completion.
|
||||||
|
|
||||||
|
Changing this setting will cause the completion history to be regenerated
|
||||||
|
on the next start, which will take a short while.
|
||||||
|
|
||||||
completion.web_history_max_items:
|
completion.web_history_max_items:
|
||||||
|
renamed: completion.web_history.max_items
|
||||||
|
|
||||||
|
completion.web_history.max_items:
|
||||||
default: -1
|
default: -1
|
||||||
type:
|
type:
|
||||||
name: Int
|
name: Int
|
||||||
@ -854,6 +984,18 @@ downloads.location.suggestion:
|
|||||||
- both: Show download path and filename.
|
- both: Show download path and filename.
|
||||||
desc: What to display in the download filename input.
|
desc: What to display in the download filename input.
|
||||||
|
|
||||||
|
completion.open_categories:
|
||||||
|
type:
|
||||||
|
name: FlagList
|
||||||
|
valid_values: [searchengines, quickmarks, bookmarks, history]
|
||||||
|
none_ok: true
|
||||||
|
default:
|
||||||
|
- searchengines
|
||||||
|
- quickmarks
|
||||||
|
- bookmarks
|
||||||
|
- history
|
||||||
|
desc: Which categories to show (in which order) in the :open completion.
|
||||||
|
|
||||||
downloads.open_dispatcher:
|
downloads.open_dispatcher:
|
||||||
type:
|
type:
|
||||||
name: String
|
name: String
|
||||||
@ -1021,6 +1163,71 @@ hints.scatter:
|
|||||||
|
|
||||||
Ignored for number hints.
|
Ignored for number hints.
|
||||||
|
|
||||||
|
hints.selectors:
|
||||||
|
no_autoconfig: true
|
||||||
|
default:
|
||||||
|
all:
|
||||||
|
- 'a'
|
||||||
|
- 'area'
|
||||||
|
- 'textarea'
|
||||||
|
- 'select'
|
||||||
|
- 'input:not([type="hidden"])'
|
||||||
|
- 'button'
|
||||||
|
- 'frame'
|
||||||
|
- 'iframe'
|
||||||
|
- 'img'
|
||||||
|
- 'link'
|
||||||
|
- 'summary'
|
||||||
|
- '[onclick]'
|
||||||
|
- '[onmousedown]'
|
||||||
|
- '[role="link"]'
|
||||||
|
- '[role="option"]'
|
||||||
|
- '[role="button"]'
|
||||||
|
- '[ng-click]'
|
||||||
|
- '[ngClick]'
|
||||||
|
- '[data-ng-click]'
|
||||||
|
- '[x-ng-click]'
|
||||||
|
- '[tabindex]'
|
||||||
|
links:
|
||||||
|
- 'a[href]'
|
||||||
|
- 'area[href]'
|
||||||
|
- 'link[href]'
|
||||||
|
- '[role="link"][href]'
|
||||||
|
images:
|
||||||
|
- 'img'
|
||||||
|
media:
|
||||||
|
- 'audio'
|
||||||
|
- 'img'
|
||||||
|
- 'video'
|
||||||
|
url:
|
||||||
|
- '[src]'
|
||||||
|
- '[href]'
|
||||||
|
inputs:
|
||||||
|
- 'input[type="text"]'
|
||||||
|
- 'input[type="date"]'
|
||||||
|
- 'input[type="datetime-local"]'
|
||||||
|
- 'input[type="email"]'
|
||||||
|
- 'input[type="month"]'
|
||||||
|
- 'input[type="number"]'
|
||||||
|
- 'input[type="password"]'
|
||||||
|
- 'input[type="search"]'
|
||||||
|
- 'input[type="tel"]'
|
||||||
|
- 'input[type="time"]'
|
||||||
|
- 'input[type="url"]'
|
||||||
|
- 'input[type="week"]'
|
||||||
|
- 'input:not([type])'
|
||||||
|
- 'textarea'
|
||||||
|
type:
|
||||||
|
name: Dict
|
||||||
|
keytype: String
|
||||||
|
valtype:
|
||||||
|
name: List
|
||||||
|
none_ok: true
|
||||||
|
valtype: String
|
||||||
|
supports_pattern: true
|
||||||
|
desc: CSS selectors used to determine which elements on a page should have
|
||||||
|
hints.
|
||||||
|
|
||||||
hints.uppercase:
|
hints.uppercase:
|
||||||
default: false
|
default: false
|
||||||
type: Bool
|
type: Bool
|
||||||
@ -1167,9 +1374,15 @@ prompt.radius:
|
|||||||
## scrolling
|
## scrolling
|
||||||
|
|
||||||
scrolling.bar:
|
scrolling.bar:
|
||||||
type: Bool
|
type:
|
||||||
default: false
|
name: String
|
||||||
desc: Show a scrollbar.
|
valid_values:
|
||||||
|
- always: Always show the scrollbar.
|
||||||
|
- never: Never show the scrollbar.
|
||||||
|
- when-searching: Show the scrollbar when searching for text in the
|
||||||
|
webpage. With the QtWebKit backend, this is equal to `never`.
|
||||||
|
default: when-searching
|
||||||
|
desc: When to show the scrollbar.
|
||||||
|
|
||||||
scrolling.smooth:
|
scrolling.smooth:
|
||||||
type: Bool
|
type: Bool
|
||||||
@ -1349,12 +1562,27 @@ tabs.mousewheel_switching:
|
|||||||
tabs.new_position.related:
|
tabs.new_position.related:
|
||||||
default: next
|
default: next
|
||||||
type: NewTabPosition
|
type: NewTabPosition
|
||||||
desc: Position of new tabs opened from another tab.
|
desc: >-
|
||||||
|
Position of new tabs opened from another tab.
|
||||||
|
|
||||||
|
See `tabs.new_position.stacking` for controlling stacking behavior.
|
||||||
|
|
||||||
tabs.new_position.unrelated:
|
tabs.new_position.unrelated:
|
||||||
default: last
|
default: last
|
||||||
type: NewTabPosition
|
type: NewTabPosition
|
||||||
desc: "Position of new tabs which aren't opened from another tab."
|
desc: >-
|
||||||
|
Position of new tabs which are not opened from another tab.
|
||||||
|
|
||||||
|
See `tabs.new_position.stacking` for controlling stacking behavior.
|
||||||
|
|
||||||
|
tabs.new_position.stacking:
|
||||||
|
default: true
|
||||||
|
type: Bool
|
||||||
|
desc: >-
|
||||||
|
Stack related tabs on top of each other when opened consecutively.
|
||||||
|
|
||||||
|
Only applies for `next` and `prev` values of `tabs.new_position.related`
|
||||||
|
and `tabs.new_position.unrelated`.
|
||||||
|
|
||||||
tabs.padding:
|
tabs.padding:
|
||||||
default:
|
default:
|
||||||
@ -1498,6 +1726,23 @@ tabs.min_width:
|
|||||||
|
|
||||||
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False.
|
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False.
|
||||||
|
|
||||||
|
tabs.max_width:
|
||||||
|
default: -1
|
||||||
|
type:
|
||||||
|
name: Int
|
||||||
|
minval: -1
|
||||||
|
maxval: maxint
|
||||||
|
desc: >-
|
||||||
|
Maximum width (in pixels) of tabs (-1 for no maximum).
|
||||||
|
|
||||||
|
This setting only applies when tabs are horizontal.
|
||||||
|
|
||||||
|
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is
|
||||||
|
False.
|
||||||
|
|
||||||
|
This setting may not apply properly if max_width is smaller than the
|
||||||
|
minimum size of tab contents, or smaller than tabs.min_width.
|
||||||
|
|
||||||
tabs.width.indicator:
|
tabs.width.indicator:
|
||||||
renamed: tabs.indicator.width
|
renamed: tabs.indicator.width
|
||||||
|
|
||||||
@ -1753,7 +1998,7 @@ colors.completion.item.selected.border.bottom:
|
|||||||
|
|
||||||
colors.completion.match.fg:
|
colors.completion.match.fg:
|
||||||
default: '#ff4444'
|
default: '#ff4444'
|
||||||
type: QssColor
|
type: QtColor
|
||||||
desc: Foreground color of the matched text in the completion.
|
desc: Foreground color of the matched text in the completion.
|
||||||
|
|
||||||
colors.completion.scrollbar.fg:
|
colors.completion.scrollbar.fg:
|
||||||
@ -2440,6 +2685,7 @@ bindings.default:
|
|||||||
.: repeat-command
|
.: repeat-command
|
||||||
<Ctrl-p>: tab-pin
|
<Ctrl-p>: tab-pin
|
||||||
<Alt-m>: tab-mute
|
<Alt-m>: tab-mute
|
||||||
|
gD: tab-give
|
||||||
q: record-macro
|
q: record-macro
|
||||||
"@": run-macro
|
"@": run-macro
|
||||||
tsh: config-cycle -p -t -u *://{url:host}/* content.javascript.enabled ;; reload
|
tsh: config-cycle -p -t -u *://{url:host}/* content.javascript.enabled ;; reload
|
||||||
@ -2454,6 +2700,12 @@ bindings.default:
|
|||||||
tPH: config-cycle -p -u *://*.{url:host}/* content.plugins ;; reload
|
tPH: config-cycle -p -u *://*.{url:host}/* content.plugins ;; reload
|
||||||
tpu: config-cycle -p -t -u {url} content.plugins ;; reload
|
tpu: config-cycle -p -t -u {url} content.plugins ;; reload
|
||||||
tPu: config-cycle -p -u {url} content.plugins ;; reload
|
tPu: config-cycle -p -u {url} content.plugins ;; reload
|
||||||
|
tih: config-cycle -p -t -u *://{url:host}/* content.images ;; reload
|
||||||
|
tIh: config-cycle -p -u *://{url:host}/* content.images ;; reload
|
||||||
|
tiH: config-cycle -p -t -u *://*.{url:host}/* content.images ;; reload
|
||||||
|
tIH: config-cycle -p -u *://*.{url:host}/* content.images ;; reload
|
||||||
|
tiu: config-cycle -p -t -u {url} content.images ;; reload
|
||||||
|
tIu: config-cycle -p -u {url} content.images ;; reload
|
||||||
insert:
|
insert:
|
||||||
<Ctrl-E>: open-editor
|
<Ctrl-E>: open-editor
|
||||||
<Shift-Ins>: insert-text {primary}
|
<Shift-Ins>: insert-text {primary}
|
||||||
@ -2465,7 +2717,7 @@ bindings.default:
|
|||||||
<Ctrl-B>: hint all tab-bg
|
<Ctrl-B>: hint all tab-bg
|
||||||
<Escape>: leave-mode
|
<Escape>: leave-mode
|
||||||
passthrough:
|
passthrough:
|
||||||
<Ctrl-V>: leave-mode
|
<Shift-Escape>: leave-mode
|
||||||
command:
|
command:
|
||||||
<Ctrl-P>: command-history-prev
|
<Ctrl-P>: command-history-prev
|
||||||
<Ctrl-N>: command-history-next
|
<Ctrl-N>: command-history-next
|
||||||
@ -2499,6 +2751,7 @@ bindings.default:
|
|||||||
prompt:
|
prompt:
|
||||||
<Return>: prompt-accept
|
<Return>: prompt-accept
|
||||||
<Ctrl-X>: prompt-open-download
|
<Ctrl-X>: prompt-open-download
|
||||||
|
<Ctrl-P>: prompt-open-download --pdfjs
|
||||||
<Shift-Tab>: prompt-item-focus prev
|
<Shift-Tab>: prompt-item-focus prev
|
||||||
<Up>: prompt-item-focus prev
|
<Up>: prompt-item-focus prev
|
||||||
<Tab>: prompt-item-focus next
|
<Tab>: prompt-item-focus next
|
||||||
|
@ -276,7 +276,24 @@ class YamlConfig(QObject):
|
|||||||
del settings['bindings.default']
|
del settings['bindings.default']
|
||||||
self._mark_changed()
|
self._mark_changed()
|
||||||
|
|
||||||
|
# content.webrtc_public_interfaces_only got merged into
|
||||||
|
# content.webrtc_ip_handling_policy.
|
||||||
|
old = 'content.webrtc_public_interfaces_only'
|
||||||
|
new = 'content.webrtc_ip_handling_policy'
|
||||||
|
if old in settings:
|
||||||
|
settings[new] = {}
|
||||||
|
for scope, val in settings[old].items():
|
||||||
|
if val:
|
||||||
|
settings[new][scope] = 'default-public-interface-only'
|
||||||
|
else:
|
||||||
|
settings[new][scope] = 'all-interfaces'
|
||||||
|
|
||||||
|
del settings[old]
|
||||||
|
self._mark_changed()
|
||||||
|
|
||||||
self._migrate_bool(settings, 'tabs.favicons.show', 'always', 'never')
|
self._migrate_bool(settings, 'tabs.favicons.show', 'always', 'never')
|
||||||
|
self._migrate_bool(settings, 'scrolling.bar',
|
||||||
|
'when-searching', 'never')
|
||||||
self._migrate_bool(settings, 'qt.force_software_rendering',
|
self._migrate_bool(settings, 'qt.force_software_rendering',
|
||||||
'software-opengl', 'none')
|
'software-opengl', 'none')
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ from qutebrowser.config import (config, configdata, configfiles, configtypes,
|
|||||||
configexc, configcommands)
|
configexc, configcommands)
|
||||||
from qutebrowser.utils import (objreg, usertypes, log, standarddir, message,
|
from qutebrowser.utils import (objreg, usertypes, log, standarddir, message,
|
||||||
qtutils)
|
qtutils)
|
||||||
|
from qutebrowser.config import configcache
|
||||||
from qutebrowser.misc import msgbox, objects
|
from qutebrowser.misc import msgbox, objects
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ def early_init(args):
|
|||||||
config.instance = config.Config(yaml_config=yaml_config)
|
config.instance = config.Config(yaml_config=yaml_config)
|
||||||
config.val = config.ConfigContainer(config.instance)
|
config.val = config.ConfigContainer(config.instance)
|
||||||
config.key_instance = config.KeyConfig(config.instance)
|
config.key_instance = config.KeyConfig(config.instance)
|
||||||
|
config.cache = configcache.ConfigCache()
|
||||||
yaml_config.setParent(config.instance)
|
yaml_config.setParent(config.instance)
|
||||||
|
|
||||||
for cf in config.change_filters:
|
for cf in config.change_filters:
|
||||||
@ -89,6 +91,8 @@ def _init_envvars():
|
|||||||
os.environ['QT_XCB_FORCE_SOFTWARE_OPENGL'] = '1'
|
os.environ['QT_XCB_FORCE_SOFTWARE_OPENGL'] = '1'
|
||||||
elif software_rendering == 'qt-quick':
|
elif software_rendering == 'qt-quick':
|
||||||
os.environ['QT_QUICK_BACKEND'] = 'software'
|
os.environ['QT_QUICK_BACKEND'] = 'software'
|
||||||
|
elif software_rendering == 'chromium':
|
||||||
|
os.environ['QT_WEBENGINE_DISABLE_NOUVEAU_WORKAROUND'] = '1'
|
||||||
|
|
||||||
if config.val.qt.force_platform is not None:
|
if config.val.qt.force_platform is not None:
|
||||||
os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform
|
os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform
|
||||||
@ -167,24 +171,67 @@ def qt_args(namespace):
|
|||||||
argv += ['--' + arg for arg in config.val.qt.args]
|
argv += ['--' + arg for arg in config.val.qt.args]
|
||||||
|
|
||||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||||
if not qtutils.version_check('5.11', compiled=False):
|
argv += list(_qtwebengine_args())
|
||||||
# WORKAROUND equivalent to
|
|
||||||
# https://codereview.qt-project.org/#/c/217932/
|
|
||||||
# Needed for Qt < 5.9.5 and < 5.10.1
|
|
||||||
argv.append('--disable-shared-workers')
|
|
||||||
|
|
||||||
if config.val.qt.force_software_rendering == 'chromium':
|
|
||||||
argv.append('--disable-gpu')
|
|
||||||
|
|
||||||
if not config.val.content.canvas_reading:
|
|
||||||
argv.append('--disable-reading-from-canvas')
|
|
||||||
|
|
||||||
if not qtutils.version_check('5.11'):
|
|
||||||
# On Qt 5.11, we can control this via QWebEngineSettings
|
|
||||||
if not config.val.content.autoplay:
|
|
||||||
argv.append('--autoplay-policy=user-gesture-required')
|
|
||||||
if config.val.content.webrtc_public_interfaces_only:
|
|
||||||
argv.append('--force-webrtc-ip-handling-policy='
|
|
||||||
'default_public_interface_only')
|
|
||||||
|
|
||||||
return argv
|
return argv
|
||||||
|
|
||||||
|
|
||||||
|
def _qtwebengine_args():
|
||||||
|
"""Get the QtWebEngine arguments to use based on the config."""
|
||||||
|
if not qtutils.version_check('5.11', compiled=False):
|
||||||
|
# WORKAROUND equivalent to
|
||||||
|
# https://codereview.qt-project.org/#/c/217932/
|
||||||
|
# Needed for Qt < 5.9.5 and < 5.10.1
|
||||||
|
yield '--disable-shared-workers'
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
'qt.force_software_rendering': {
|
||||||
|
'software-opengl': None,
|
||||||
|
'qt-quick': None,
|
||||||
|
'chromium': '--disable-gpu',
|
||||||
|
'none': None,
|
||||||
|
},
|
||||||
|
'content.canvas_reading': {
|
||||||
|
True: None,
|
||||||
|
False: '--disable-reading-from-canvas',
|
||||||
|
},
|
||||||
|
'content.webrtc_ip_handling_policy': {
|
||||||
|
'all-interfaces': None,
|
||||||
|
'default-public-and-private-interfaces':
|
||||||
|
'--force-webrtc-ip-handling-policy='
|
||||||
|
'default_public_and_private_interfaces',
|
||||||
|
'default-public-interface-only':
|
||||||
|
'--force-webrtc-ip-handling-policy='
|
||||||
|
'default_public_interface_only',
|
||||||
|
'disable-non-proxied-udp':
|
||||||
|
'--force-webrtc-ip-handling-policy='
|
||||||
|
'disable_non_proxied_udp',
|
||||||
|
},
|
||||||
|
'qt.process_model': {
|
||||||
|
'process-per-site-instance': None,
|
||||||
|
'process-per-site': '--process-per-site',
|
||||||
|
'single-process': '--single-process',
|
||||||
|
},
|
||||||
|
'qt.low_end_device_mode': {
|
||||||
|
'auto': None,
|
||||||
|
'always': '--enable-low-end-device-mode',
|
||||||
|
'never': '--disable-low-end-device-mode',
|
||||||
|
},
|
||||||
|
'content.headers.referer': {
|
||||||
|
'always': None,
|
||||||
|
'never': '--no-referrers',
|
||||||
|
'same-domain': '--reduced-referrer-granularity',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if not qtutils.version_check('5.11'):
|
||||||
|
# On Qt 5.11, we can control this via QWebEngineSettings
|
||||||
|
settings['content.autoplay'] = {
|
||||||
|
True: None,
|
||||||
|
False: '--autoplay-policy=user-gesture-required',
|
||||||
|
}
|
||||||
|
|
||||||
|
for setting, args in sorted(settings.items()):
|
||||||
|
arg = args[config.instance.get(setting)]
|
||||||
|
if arg is not None:
|
||||||
|
yield arg
|
||||||
|
@ -60,8 +60,8 @@ from PyQt5.QtGui import QColor, QFont
|
|||||||
from PyQt5.QtWidgets import QTabWidget, QTabBar
|
from PyQt5.QtWidgets import QTabWidget, QTabBar
|
||||||
|
|
||||||
from qutebrowser.commands import cmdutils
|
from qutebrowser.commands import cmdutils
|
||||||
from qutebrowser.config import configexc
|
from qutebrowser.config import configexc, configutils
|
||||||
from qutebrowser.utils import standarddir, utils, qtutils, urlutils
|
from qutebrowser.utils import standarddir, utils, qtutils, urlutils, urlmatch
|
||||||
from qutebrowser.keyinput import keyutils
|
from qutebrowser.keyinput import keyutils
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ class BaseType:
|
|||||||
"""A type used for a setting value.
|
"""A type used for a setting value.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
none_ok: Whether to convert to None for an empty string.
|
none_ok: Whether to allow None (or an empty string for :set) as value.
|
||||||
|
|
||||||
Class attributes:
|
Class attributes:
|
||||||
valid_values: Possible values if they can be expressed as a fixed
|
valid_values: Possible values if they can be expressed as a fixed
|
||||||
@ -149,6 +149,9 @@ class BaseType:
|
|||||||
value: The value to check.
|
value: The value to check.
|
||||||
pytype: A Python type to check the value against.
|
pytype: A Python type to check the value against.
|
||||||
"""
|
"""
|
||||||
|
if value is configutils.UNSET:
|
||||||
|
return
|
||||||
|
|
||||||
if (value is None or (pytype == list and value == []) or
|
if (value is None or (pytype == list and value == []) or
|
||||||
(pytype == dict and value == {})):
|
(pytype == dict and value == {})):
|
||||||
if not self.none_ok:
|
if not self.none_ok:
|
||||||
@ -309,7 +312,9 @@ class MappingType(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
self._validate_valid_values(value.lower())
|
self._validate_valid_values(value.lower())
|
||||||
return self.MAPPING[value.lower()]
|
return self.MAPPING[value.lower()]
|
||||||
@ -367,7 +372,9 @@ class String(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self._validate_encoding(value)
|
self._validate_encoding(value)
|
||||||
@ -399,7 +406,9 @@ class UniqueCharString(String):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
value = super().to_py(value)
|
value = super().to_py(value)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Check for duplicate values
|
# Check for duplicate values
|
||||||
@ -455,7 +464,9 @@ class List(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, list)
|
self._basic_py_validation(value, list)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
for val in value:
|
for val in value:
|
||||||
@ -534,6 +545,9 @@ class ListOrValue(BaseType):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return [self.valtype.to_py(value)]
|
return [self.valtype.to_py(value)]
|
||||||
except configexc.ValidationError:
|
except configexc.ValidationError:
|
||||||
@ -577,7 +591,8 @@ class FlagList(List):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
vals = super().to_py(value)
|
vals = super().to_py(value)
|
||||||
self._check_duplicates(vals)
|
if vals is not configutils.UNSET:
|
||||||
|
self._check_duplicates(vals)
|
||||||
return vals
|
return vals
|
||||||
|
|
||||||
def complete(self):
|
def complete(self):
|
||||||
@ -764,7 +779,9 @@ class Perc(_Numeric):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, (float, int, str))
|
self._basic_py_validation(value, (float, int, str))
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
@ -903,13 +920,49 @@ class QtColor(BaseType):
|
|||||||
* An SVG color name as specified in
|
* An SVG color name as specified in
|
||||||
http://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification].
|
http://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification].
|
||||||
* transparent (no color)
|
* transparent (no color)
|
||||||
|
* `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages)
|
||||||
|
* `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def _parse_value(self, val):
|
||||||
|
try:
|
||||||
|
return int(val)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
mult = 255.0
|
||||||
|
if val.endswith('%'):
|
||||||
|
val = val[:-1]
|
||||||
|
mult = 255.0 / 100
|
||||||
|
|
||||||
|
try:
|
||||||
|
return int(float(val) * mult)
|
||||||
|
except ValueError:
|
||||||
|
raise configexc.ValidationError(val, "must be a valid color value")
|
||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if '(' in value and value.endswith(')'):
|
||||||
|
openparen = value.index('(')
|
||||||
|
kind = value[:openparen]
|
||||||
|
vals = value[openparen+1:-1].split(',')
|
||||||
|
vals = [self._parse_value(v) for v in vals]
|
||||||
|
if kind == 'rgba' and len(vals) == 4:
|
||||||
|
return QColor.fromRgb(*vals)
|
||||||
|
elif kind == 'rgb' and len(vals) == 3:
|
||||||
|
return QColor.fromRgb(*vals)
|
||||||
|
elif kind == 'hsva' and len(vals) == 4:
|
||||||
|
return QColor.fromHsv(*vals)
|
||||||
|
elif kind == 'hsv' and len(vals) == 3:
|
||||||
|
return QColor.fromHsv(*vals)
|
||||||
|
else:
|
||||||
|
raise configexc.ValidationError(value, "must be a valid color")
|
||||||
|
|
||||||
color = QColor(value)
|
color = QColor(value)
|
||||||
if color.isValid():
|
if color.isValid():
|
||||||
return color
|
return color
|
||||||
@ -936,7 +989,9 @@ class QssColor(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
functions = ['rgb', 'rgba', 'hsv', 'hsva', 'qlineargradient',
|
functions = ['rgb', 'rgba', 'hsv', 'hsva', 'qlineargradient',
|
||||||
@ -981,7 +1036,9 @@ class Font(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not self.font_regex.fullmatch(value): # pragma: no cover
|
if not self.font_regex.fullmatch(value): # pragma: no cover
|
||||||
@ -1000,7 +1057,9 @@ class FontFamily(Font):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
match = self.font_regex.fullmatch(value)
|
match = self.font_regex.fullmatch(value)
|
||||||
@ -1024,7 +1083,9 @@ class QtFont(Font):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
style_map = {
|
style_map = {
|
||||||
@ -1136,7 +1197,9 @@ class Regex(BaseType):
|
|||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
"""Get a compiled regex from either a string or a regex object."""
|
"""Get a compiled regex from either a string or a regex object."""
|
||||||
self._basic_py_validation(value, (str, self._regex_type))
|
self._basic_py_validation(value, (str, self._regex_type))
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
elif isinstance(value, str):
|
elif isinstance(value, str):
|
||||||
return self._compile_regex(value)
|
return self._compile_regex(value)
|
||||||
@ -1214,7 +1277,9 @@ class Dict(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, dict)
|
self._basic_py_validation(value, dict)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return self._fill_fixed_keys({})
|
return self._fill_fixed_keys({})
|
||||||
|
|
||||||
self._validate_keys(value)
|
self._validate_keys(value)
|
||||||
@ -1230,7 +1295,7 @@ class Dict(BaseType):
|
|||||||
if not value:
|
if not value:
|
||||||
# An empty Dict is treated just like None -> empty string
|
# An empty Dict is treated just like None -> empty string
|
||||||
return ''
|
return ''
|
||||||
return json.dumps(value)
|
return json.dumps(value, sort_keys=True)
|
||||||
|
|
||||||
def to_doc(self, value, indent=0):
|
def to_doc(self, value, indent=0):
|
||||||
if not value:
|
if not value:
|
||||||
@ -1256,7 +1321,9 @@ class File(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
value = os.path.expanduser(value)
|
value = os.path.expanduser(value)
|
||||||
@ -1282,7 +1349,9 @@ class Directory(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
value = os.path.expandvars(value)
|
value = os.path.expandvars(value)
|
||||||
value = os.path.expanduser(value)
|
value = os.path.expanduser(value)
|
||||||
@ -1309,7 +1378,9 @@ class FormatString(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -1341,8 +1412,10 @@ class ShellCommand(List):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
value = super().to_py(value)
|
value = super().to_py(value)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
return value
|
return value
|
||||||
|
elif not value:
|
||||||
|
return []
|
||||||
|
|
||||||
if (self.placeholder and
|
if (self.placeholder and
|
||||||
'{}' not in ' '.join(value) and
|
'{}' not in ' '.join(value) and
|
||||||
@ -1365,7 +1438,9 @@ class Proxy(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -1401,7 +1476,9 @@ class SearchEngineUrl(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not ('{}' in value or '{0}' in value):
|
if not ('{}' in value or '{0}' in value):
|
||||||
@ -1429,7 +1506,9 @@ class FuzzyUrl(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -1463,6 +1542,9 @@ class Padding(Dict):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
d = super().to_py(value)
|
d = super().to_py(value)
|
||||||
|
if d is configutils.UNSET:
|
||||||
|
return d
|
||||||
|
|
||||||
return PaddingValues(**d)
|
return PaddingValues(**d)
|
||||||
|
|
||||||
|
|
||||||
@ -1472,7 +1554,9 @@ class Encoding(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
codecs.lookup(value)
|
codecs.lookup(value)
|
||||||
@ -1529,7 +1613,9 @@ class Url(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
qurl = QUrl.fromUserInput(value)
|
qurl = QUrl.fromUserInput(value)
|
||||||
@ -1545,7 +1631,9 @@ class SessionName(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
if value.startswith('_'):
|
if value.startswith('_'):
|
||||||
raise configexc.ValidationError(value, "may not start with '_'!")
|
raise configexc.ValidationError(value, "may not start with '_'!")
|
||||||
@ -1593,8 +1681,10 @@ class ConfirmQuit(FlagList):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
values = super().to_py(value)
|
values = super().to_py(value)
|
||||||
if not values:
|
if values is configutils.UNSET:
|
||||||
return values
|
return values
|
||||||
|
elif not values:
|
||||||
|
return []
|
||||||
|
|
||||||
# Never can't be set with other options
|
# Never can't be set with other options
|
||||||
if 'never' in values and len(values) > 1:
|
if 'never' in values and len(values) > 1:
|
||||||
@ -1630,7 +1720,9 @@ class TimestampTemplate(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -1654,10 +1746,33 @@ class Key(BaseType):
|
|||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return keyutils.KeySequence.parse(value)
|
return keyutils.KeySequence.parse(value)
|
||||||
except keyutils.KeyParseError as e:
|
except keyutils.KeyParseError as e:
|
||||||
raise configexc.ValidationError(value, str(e))
|
raise configexc.ValidationError(value, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
class UrlPattern(BaseType):
|
||||||
|
|
||||||
|
"""A match pattern for a URL.
|
||||||
|
|
||||||
|
See https://developer.chrome.com/apps/match_patterns for the allowed
|
||||||
|
syntax.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def to_py(self, value):
|
||||||
|
self._basic_py_validation(value, str)
|
||||||
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
|
elif not value:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return urlmatch.UrlPattern(value)
|
||||||
|
except urlmatch.ParseError as e:
|
||||||
|
raise configexc.ValidationError(value, str(e))
|
||||||
|
@ -111,7 +111,7 @@ li {
|
|||||||
<li>
|
<li>
|
||||||
You can manually download the pdf.js archive
|
You can manually download the pdf.js archive
|
||||||
<a href="https://mozilla.github.io/pdf.js/getting_started/#download">here</a>
|
<a href="https://mozilla.github.io/pdf.js/getting_started/#download">here</a>
|
||||||
and extract it to <code>~/.local/share/qutebrowser/pdfjs</code>
|
and extract it to <code>{{ pdfjs_dir }}</code>
|
||||||
<br>
|
<br>
|
||||||
<span class="warning">Warning:</span> Using this method you are
|
<span class="warning">Warning:</span> Using this method you are
|
||||||
responsible for yourself to keep the installation updated! If a
|
responsible for yourself to keep the installation updated! If a
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
{% block script %}
|
{% block script %}
|
||||||
var cset = function(option, value) {
|
var cset = function(option, value) {
|
||||||
// FIXME:conf we might want some error handling here?
|
// FIXME:conf we might want some error handling here?
|
||||||
var url = "qute://settings/set?option=" + encodeURIComponent(option);
|
var url = "qute://user:{{csrf_token}}@settings/set"
|
||||||
|
url += "?option=" + encodeURIComponent(option);
|
||||||
url += "&value=" + encodeURIComponent(value);
|
url += "&value=" + encodeURIComponent(value);
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open("GET", url);
|
xhr.open("GET", url);
|
||||||
@ -33,7 +34,7 @@ input { width: 98%; }
|
|||||||
<th>Setting</th>
|
<th>Setting</th>
|
||||||
<th>Value</th>
|
<th>Value</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for option in configdata.DATA.values() if not option.no_autoconfig %}
|
{% for option in configdata.DATA.values()|sort(attribute='name') if not option.no_autoconfig %}
|
||||||
<tr>
|
<tr>
|
||||||
<!-- FIXME: convert to string properly -->
|
<!-- FIXME: convert to string properly -->
|
||||||
<td class="setting">{{ option.name }} (Current: {{ confget(option.name) | string |truncate(100) }})
|
<td class="setting">{{ option.name }} (Current: {{ confget(option.name) | string |truncate(100) }})
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user