Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d166587f3d | ||
|
|
dd663df35f | ||
|
|
9d292128d2 | ||
|
|
f3eb5dbb66 | ||
|
|
3eaad092b8 | ||
|
|
38bccd4fdd | ||
|
|
9a5668f7a0 | ||
|
|
48023e7b1d | ||
|
|
74e1b1ec26 | ||
|
|
cfe7386f20 | ||
|
|
12b00dad44 | ||
|
|
ccf3cb6d7c | ||
|
|
fcd8be5b68 | ||
|
|
259d08ba29 | ||
|
|
9d65039b35 | ||
|
|
e50b7b65a4 | ||
|
|
1aefaaf7c7 | ||
|
|
4da89139d1 | ||
|
|
3f04c94047 | ||
|
|
00f456fd7f | ||
|
|
00d5aa6b22 | ||
|
|
91c5eff2c9 | ||
|
|
0d704043ec | ||
|
|
b11abb028b | ||
|
|
474e904409 | ||
|
|
cc05cb1c67 | ||
|
|
e10ce420ab | ||
|
|
5b5adc1a00 | ||
|
|
63fa412882 | ||
|
|
47ad8f212e | ||
|
|
7ac27fdd85 | ||
|
|
d4baeb2ada |
@@ -5,15 +5,15 @@ cache:
|
||||
build: off
|
||||
environment:
|
||||
PYTHONUNBUFFERED: 1
|
||||
PYTHON: C:\Python36-x64\python.exe
|
||||
PYTHON: C:\Python36\python.exe
|
||||
matrix:
|
||||
- TESTENV: py36-pyqt511
|
||||
- TESTENV: py36-pyqt58
|
||||
- TESTENV: pylint
|
||||
|
||||
install:
|
||||
- '%PYTHON% -m pip install -U pip'
|
||||
- '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
|
||||
- 'set PATH=C:\Python36-x64;%PATH'
|
||||
- 'set PATH=%PATH%;C:\Python36'
|
||||
|
||||
test_script:
|
||||
- '%PYTHON% -m tox -e %TESTENV%'
|
||||
|
||||
@@ -12,7 +12,6 @@ exclude_lines =
|
||||
def __repr__
|
||||
raise AssertionError
|
||||
raise NotImplementedError
|
||||
raise utils\.Unreachable
|
||||
if __name__ == ["']__main__["']:
|
||||
|
||||
[xml]
|
||||
|
||||
29
.flake8
@@ -1,8 +1,5 @@
|
||||
[flake8]
|
||||
exclude = .*,__pycache__,resources.py
|
||||
# B001: bare except
|
||||
# B008: Do not perform calls in argument defaults. (fine with some Qt stuff)
|
||||
# B305: .next() (false-positives)
|
||||
# E128: continuation line under-indented for visual indent
|
||||
# E226: missing whitespace around arithmetic operator
|
||||
# E265: Block comment should start with '#'
|
||||
@@ -14,7 +11,6 @@ exclude = .*,__pycache__,resources.py
|
||||
# (for pytest's __tracebackhide__)
|
||||
# F401: Unused import
|
||||
# N802: function name should be lowercase
|
||||
# N806: variable in function should be lowercase
|
||||
# P101: format string does contain unindexed parameters
|
||||
# P102: docstring does contain unindexed parameters
|
||||
# P103: other string does contain unindexed parameters
|
||||
@@ -22,33 +18,30 @@ exclude = .*,__pycache__,resources.py
|
||||
# D103: Missing docstring in public function (will be handled by others)
|
||||
# D104: Missing docstring in public package (will be handled by others)
|
||||
# D105: Missing docstring in magic method (will be handled by others)
|
||||
# D106: Missing docstring in public nested class (will be handled by others)
|
||||
# D107: Missing docstring in __init__ (will be handled by others)
|
||||
# D209: Blank line before closing """ (removed from PEP257)
|
||||
# D211: No blank lines allowed before class docstring
|
||||
# (PEP257 got changed, but let's stick to the old standard)
|
||||
# D401: First line should be in imperative mood (okay sometimes)
|
||||
# D402: First line should not be function's signature (false-positives)
|
||||
# D403: First word of the first line should be properly capitalized
|
||||
# (false-positives)
|
||||
# D413: Missing blank line after last section (not in pep257?)
|
||||
# A003: Builtin name for class attribute (needed for overridden methods)
|
||||
ignore =
|
||||
B001,B008,B305,
|
||||
E128,E226,E265,E501,E402,E266,E722,E731,
|
||||
F401,
|
||||
N802,
|
||||
P101,P102,P103,
|
||||
D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D413,
|
||||
A003
|
||||
D102,D103,D104,D105,D209,D211,D402,D403
|
||||
min-version = 3.4.0
|
||||
max-complexity = 12
|
||||
per-file-ignores =
|
||||
/tests/**/*.py : D100,D101,D401
|
||||
/tests/unit/browser/test_history.py : N806
|
||||
/tests/helpers/fixtures.py : N806
|
||||
/tests/unit/browser/webkit/http/test_content_disposition.py : D400
|
||||
/scripts/dev/ci/appveyor_install.py : FI53
|
||||
putty-auto-ignore = True
|
||||
putty-ignore =
|
||||
/# pylint: disable=invalid-name/ : +N801,N806
|
||||
/# pragma: no mccabe/ : +C901
|
||||
tests/*/test_*.py : +D100,D101,D401
|
||||
tests/conftest.py : +F403
|
||||
tests/unit/browser/webkit/test_history.py : +N806
|
||||
tests/helpers/fixtures.py : +N806
|
||||
tests/unit/browser/webkit/http/test_content_disposition.py : +D400
|
||||
scripts/dev/ci/appveyor_install.py : +FI53
|
||||
copyright-check = True
|
||||
copyright-regexp = # Copyright [\d-]+ .*
|
||||
copyright-min-file-size = 110
|
||||
|
||||
12
.github/CODEOWNERS
vendored
@@ -1,12 +0,0 @@
|
||||
qutebrowser/browser/history.py @rcorre
|
||||
qutebrowser/completion/* @rcorre
|
||||
qutebrowser/misc/sql.py @rcorre
|
||||
tests/end2end/features/completion.feature @rcorre
|
||||
tests/end2end/features/test_completion_bdd.py @rcorre
|
||||
tests/unit/browser/test_history.py @rcorre
|
||||
tests/unit/completion/* @rcorre
|
||||
tests/unit/misc/test_sql.py @rcorre
|
||||
|
||||
qutebrowser/config/configdata.yml @mschilli87
|
||||
|
||||
qutebrowser/javascript/caret.js @artur-shaik
|
||||
46
.github/CODE_OF_CONDUCT.md
vendored
@@ -1,46 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mail@qutebrowser.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
17
.github/CONTRIBUTING.asciidoc
vendored
@@ -1,17 +0,0 @@
|
||||
IMPORTANT: I'm currently (July 2018) more busy than usual until September,
|
||||
because of exams coming up. Review of non-trivial pull requests will thus be
|
||||
delayed until then. If you're reading this note after mid-September, please
|
||||
open an issue.
|
||||
|
||||
- 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.
|
||||
|
||||
- Either run the testsuite locally, or keep an eye on Travis CI / AppVeyor
|
||||
after pushing changes.
|
||||
|
||||
- If you are stuck somewhere or have questions,
|
||||
https://github.com/qutebrowser/qutebrowser#getting-help[please ask]!
|
||||
|
||||
See the full contribution documentation for details and other useful hints:
|
||||
|
||||
include::../doc/contributing.asciidoc[]
|
||||
24
.gitignore
vendored
@@ -15,8 +15,11 @@ __pycache__
|
||||
/qutebrowser/3rdparty
|
||||
/doc/*.html
|
||||
/README.html
|
||||
/CHANGELOG.html
|
||||
/CONTRIBUTING.html
|
||||
/FAQ.html
|
||||
/INSTALL.html
|
||||
/qutebrowser/html/doc/
|
||||
/qutebrowser/html/*.html
|
||||
/.venv*
|
||||
/.coverage
|
||||
/htmlcov
|
||||
@@ -25,20 +28,17 @@ __pycache__
|
||||
/.tox
|
||||
/testresults.html
|
||||
/.cache
|
||||
/.pytest_cache
|
||||
/.testmondata
|
||||
/.hypothesis
|
||||
/.mypy_cache
|
||||
/prof
|
||||
/venv
|
||||
TODO
|
||||
/scripts/testbrowser/cpp/webkit/Makefile
|
||||
/scripts/testbrowser/cpp/webkit/main.o
|
||||
/scripts/testbrowser/cpp/webkit/testbrowser
|
||||
/scripts/testbrowser/cpp/webkit/.qmake.stash
|
||||
/scripts/testbrowser/cpp/webengine/Makefile
|
||||
/scripts/testbrowser/cpp/webengine/main.o
|
||||
/scripts/testbrowser/cpp/webengine/testbrowser
|
||||
/scripts/testbrowser/cpp/webengine/.qmake.stash
|
||||
/scripts/testbrowser_cpp/webkit/Makefile
|
||||
/scripts/testbrowser_cpp/webkit/main.o
|
||||
/scripts/testbrowser_cpp/webkit/testbrowser
|
||||
/scripts/testbrowser_cpp/webkit/.qmake.stash
|
||||
/scripts/testbrowser_cpp/webengine/Makefile
|
||||
/scripts/testbrowser_cpp/webengine/main.o
|
||||
/scripts/testbrowser_cpp/webengine/testbrowser
|
||||
/scripts/testbrowser_cpp/webengine/.qmake.stash
|
||||
/scripts/dev/pylint_checkers/qute_pylint.egg-info
|
||||
/misc/file_version_info.txt
|
||||
|
||||
36
.pylintrc
@@ -13,40 +13,35 @@ persistent=n
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
enable=all
|
||||
disable=locally-disabled,
|
||||
locally-enabled,
|
||||
suppressed-message,
|
||||
disable=no-self-use,
|
||||
fixme,
|
||||
no-self-use,
|
||||
global-statement,
|
||||
locally-disabled,
|
||||
locally-enabled,
|
||||
too-many-ancestors,
|
||||
too-few-public-methods,
|
||||
too-many-public-methods,
|
||||
cyclic-import,
|
||||
bad-continuation,
|
||||
too-many-instance-attributes,
|
||||
blacklisted-name,
|
||||
too-many-lines,
|
||||
logging-format-interpolation,
|
||||
logging-not-lazy,
|
||||
broad-except,
|
||||
bare-except,
|
||||
eval-used,
|
||||
exec-used,
|
||||
global-statement,
|
||||
wrong-import-position,
|
||||
duplicate-code,
|
||||
no-else-return,
|
||||
too-many-ancestors,
|
||||
too-many-public-methods,
|
||||
too-many-instance-attributes,
|
||||
too-many-lines,
|
||||
ungrouped-imports,
|
||||
suppressed-message,
|
||||
too-many-return-statements,
|
||||
too-many-boolean-expressions,
|
||||
too-many-locals,
|
||||
too-many-branches,
|
||||
too-many-statements,
|
||||
too-few-public-methods
|
||||
duplicate-code,
|
||||
wrong-import-position,
|
||||
no-else-return
|
||||
|
||||
[BASIC]
|
||||
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
||||
const-rgx=[A-Za-z_][A-Za-z0-9_]{0,30}$
|
||||
method-rgx=[a-z_][A-Za-z0-9_]{1,50}$
|
||||
attr-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$
|
||||
argument-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
variable-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
docstring-min-length=3
|
||||
@@ -74,4 +69,3 @@ ignored-modules=PyQt5,PyQt5.QtWebKit
|
||||
# For some reason, pylint doesn't know about some Python 3 modules on
|
||||
# AppVeyor...
|
||||
known-standard-library=faulthandler,http,enum,tokenize,posixpath,importlib,types
|
||||
known-third-party=sip
|
||||
|
||||
64
.travis.yml
@@ -1,11 +1,15 @@
|
||||
sudo: false
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: python
|
||||
language: generic
|
||||
group: edge
|
||||
python: 3.6
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
env: TESTENV=py34-cov
|
||||
- os: linux
|
||||
env: DOCKER=debian-jessie
|
||||
services: docker
|
||||
- os: linux
|
||||
env: DOCKER=archlinux
|
||||
services: docker
|
||||
@@ -13,40 +17,39 @@ matrix:
|
||||
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
|
||||
services: docker
|
||||
- os: linux
|
||||
env: DOCKER=ubuntu-xenial
|
||||
services: docker
|
||||
- os: linux
|
||||
language: python
|
||||
python: 3.6
|
||||
env: TESTENV=py36-pyqt571
|
||||
- os: linux
|
||||
language: python
|
||||
python: 3.6
|
||||
env: TESTENV=py36-pyqt58
|
||||
- os: linux
|
||||
language: python
|
||||
python: 3.5
|
||||
env: TESTENV=py35-pyqt571
|
||||
env: TESTENV=py35-pyqt59
|
||||
- os: linux
|
||||
language: python
|
||||
python: 3.6
|
||||
env: TESTENV=py36-pyqt59
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt510
|
||||
- os: linux
|
||||
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
|
||||
env: TESTENV=py37 OSX=sierra
|
||||
osx_image: xcode9.2
|
||||
language: generic
|
||||
env: TESTENV=py36 OSX=elcapitan
|
||||
osx_image: xcode7.3
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
||||
# - os: osx
|
||||
# env: TESTENV=py35 OSX=yosemite
|
||||
# osx_image: xcode6.4
|
||||
- os: linux
|
||||
env: TESTENV=pylint PYTHON=python3.6
|
||||
language: python
|
||||
python: 3.6
|
||||
- os: linux
|
||||
env: TESTENV=flake8
|
||||
- os: linux
|
||||
env: TESTENV=docs
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- asciidoc
|
||||
- os: linux
|
||||
env: TESTENV=vulture
|
||||
- os: linux
|
||||
@@ -57,24 +60,21 @@ matrix:
|
||||
env: TESTENV=check-manifest
|
||||
- os: linux
|
||||
env: TESTENV=eslint
|
||||
language: node_js
|
||||
python: null
|
||||
node_js: "lts/*"
|
||||
- os: linux
|
||||
language: generic
|
||||
env: TESTENV=shellcheck
|
||||
services: docker
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/4055
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt510
|
||||
- os: osx
|
||||
env: TESTENV=py36 OSX=elcapitan
|
||||
osx_image: xcode7.3
|
||||
fast_finish: true
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/build/qutebrowser/qutebrowser/.cache
|
||||
|
||||
before_install:
|
||||
# We need to do this so we pick up the system-wide python properly
|
||||
- 'export PATH="/usr/bin:$PATH"'
|
||||
|
||||
install:
|
||||
- bash scripts/dev/ci/travis_install.sh
|
||||
- ulimit -c unlimited
|
||||
|
||||
@@ -4,8 +4,7 @@ Change Log
|
||||
// http://keepachangelog.com/
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to http://semver.org/[Semantic Versioning], though minor
|
||||
breaking changes (such as renamed commands) can happen in minor releases.
|
||||
This project adheres to http://semver.org/[Semantic Versioning].
|
||||
|
||||
// tags:
|
||||
// `Added` for new features.
|
||||
@@ -15,752 +14,6 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.5.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- The qute-pass userscript now has optional OTP support.
|
||||
|
||||
v1.4.1 (unreleased)
|
||||
-------------------
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
debugging.
|
||||
- New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically
|
||||
selects the first hint.
|
||||
- New `input.escape_quits_reporter` setting which can be used to avoid
|
||||
accidentally quitting the crash reporter when pressing escape.
|
||||
- New `qute-lastpass` userscript which uses the LastPass CLI to fill passwords.
|
||||
- The Makefile now installs a `/usr/share/metainfo/qutebrowser.appdata.xml` file.
|
||||
- QtWebEngine: Support for printing from webpages via `window.print`.
|
||||
- QtWebEngine: Support for muting tabs:
|
||||
* New `{audio}` field for `window.title_format` and `tabs.title.format` which
|
||||
displays `[M]`/`[A]` for muted/recently audible tabs.
|
||||
* New `:tab-mute` command (bound to `<Alt-m>`) to mute/unmute a tab.
|
||||
- QtWebEngine: Support for `content.cookies.accept` with third-party cookies
|
||||
blocked by default (requires Qt 5.11).
|
||||
- QtWebEngine: New settings:
|
||||
* Support for requesting persistent storage via
|
||||
`navigator.webkitPersistentStorage.requestQuota` with a new
|
||||
`content.persistent_storage` setting (requires Qt 5.11).
|
||||
This setting also supports URL patterns.
|
||||
* Support for registering custom protocol handlers via
|
||||
`navigator.registerProtocolHandler` with a new
|
||||
`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`
|
||||
setting (requires Qt 5.10).
|
||||
This setting also supports URL patterns.
|
||||
* New `content.autoplay` setting to enable/disable automatic video playback
|
||||
(requires Qt 5.10).
|
||||
* New `content.webrtc_public_interfaces_only` setting to only expose public
|
||||
interfaces over WebRTC (requires Qt 5.9.2 or 5.11).
|
||||
* New `content.canvas_reading` setting to disable reading from canvas
|
||||
elements.
|
||||
|
||||
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
|
||||
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
|
||||
`--temp-basedir`; `-d` and `-D` for `--debug` and `--debug-flag`.
|
||||
- Deleting history items via `:history-clear` or `:completion-item-del` now
|
||||
also removes that URL from QtWebEngine's visited links.
|
||||
- There's now completion for commands taking a variable count of arguments
|
||||
(like `:config-cycle`).
|
||||
- QtWebEngine: On Qt 5.11.1, no reloads are needed anymore when switching
|
||||
between pages with changed settings (e.g. `content.javascript.enabled`).
|
||||
- The `qt.force_software_rendering` setting changed from a boolean to taking
|
||||
different values (`software-opengl`, `qt-quick` and `chromium`) for different
|
||||
kinds of software rendering workarounds.
|
||||
- On Qt 5.11, using wayland with QtWebEngine is now possible when using
|
||||
software rendering.
|
||||
- GreaseMonkey scripts now get their own global scope (based on the page's
|
||||
one), which allows scripts like OneeChan to work.
|
||||
- Rapid hinting is now supported with the `yank` and `yank-primary` targets,
|
||||
copying newline-separated links.
|
||||
- QtWebEngine: On Qt 5.11, the developer tools (inspector) can now be used
|
||||
securely and without requiring the `--enable-webengine-inspector` option.
|
||||
- The `<Enter>` key (`:follow-selected`) now follows the currently focused
|
||||
element if there's no selection.
|
||||
- The `--logfilter` argument now can be prepended with an exclamation mark
|
||||
(e.g. `--logfilter '!init,destroy'`) to invert the filter.
|
||||
- `:view-source` now has a `--pygments` flag which uses the "old" way of
|
||||
rendering sources even with QtWebEngine.
|
||||
- Improved error messages when a setting needs a newer Qt version.
|
||||
- QtWebEngine: Various improvements to make the cursor more visible in caret
|
||||
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
|
||||
~~~~~~~
|
||||
|
||||
- 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.
|
||||
- The `content.developer_extras` setting got removed. On QtWebKit, developer
|
||||
extras are now automatically enabled when opening the inspector.
|
||||
|
||||
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
|
||||
~~~~~
|
||||
|
||||
- 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
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- QtWebEngine: Improved workaround for a bug in Qt 5.11 where only the
|
||||
top/bottom half of the window is used.
|
||||
- QtWebEngine: Work around a bug in Qt 5.11 where an endless loading-loop is
|
||||
triggered when clicking a link with an unknown scheme.
|
||||
- QtWebEngine: When switching between pages with changed settings, less
|
||||
unnecessary reloads are done now.
|
||||
- QtWebEngine: It's now possible to open external links such as `magnet://` or
|
||||
`mailto:` via hints.
|
||||
|
||||
v1.3.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Work around a bug in Qt 5.11 where only the top/bottom half of the window is used.
|
||||
This workaround is incomplete, but fixes the majority of the cases where this happens.
|
||||
- Work around keyboard focus issues with Qt 5.11.
|
||||
- Work around an issue in Qt 5.11 where e.g. activating JavaScript per-domain
|
||||
needed a manual reload in some cases.
|
||||
- Don't crash when a ² key is pressed (e.g. on AZERTY keyboards).
|
||||
- Don't crash when a tab is opened and quickly closed again.
|
||||
|
||||
|
||||
v1.3.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New `:scroll-to-anchor` command to scroll to an anchor in the document.
|
||||
- New `url.open_base_url` option to open the base URL of a searchengine when no
|
||||
search term is given.
|
||||
- New `tabs.min_width` setting to configure the minimal width for tabs.
|
||||
- New userscripts:
|
||||
* `getbib` to download bibtex information for DOIs on a page.
|
||||
* `qute-keepass` to get passwords from KeePassX.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- QtWebEngine: Support for JavaScript Shared Web Workers have been disabled on
|
||||
Qt versions older than 5.11 because of security issues in in Chromium.
|
||||
You can get the same effect in earlier versions via
|
||||
`:set qt.args ['disable-shared-workers']`. An equivalent workaround is also
|
||||
contained in Qt 5.9.5 and 5.10.1.
|
||||
- The file dialog for downloads now has basic tab completion based on the
|
||||
entered text.
|
||||
- `:version` now shows OS information for POSIX OS other than Linux/macOS.
|
||||
- When there's an error inserting the text from an external editor, a backup
|
||||
file is now saved.
|
||||
- The `window.hide_wayland_decoration` setting got renamed to
|
||||
`window.hide_decoration` and now also works outside of wayland.
|
||||
- The `tabs.favicons.show` setting now can take three values: `'always'` (was
|
||||
`True`), `'never'` (was `False`) and `'pinned'` (to only show favicons for
|
||||
pinned tabs).
|
||||
- Hover tooltips on tabs now always show the webpage's title.
|
||||
- The default value for `content.host_blocking.lists` was changed to only
|
||||
include https://github.com/StevenBlack/hosts[Steven Black's hosts-list] which
|
||||
combines various sources.
|
||||
- Error messages when trying to wrap when `tabs.wrap` is `False` are now logged
|
||||
to debug instead of messages.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Using hints before a page is fully loaded is now possible again.
|
||||
- Selecting hints with the number keypad now works again.
|
||||
- Tab titles for tabs loaded from sessions should now really be correct instead
|
||||
of showing the URL.
|
||||
- Loading URLs with customized settings from a session now avoids an additional
|
||||
reload.
|
||||
- The window icon and title now get set correctly again.
|
||||
- The `tabs.switching_delay` setting now has a correct maximum value limit set.
|
||||
- The `taskadd` script now works properly when there's multi-line output.
|
||||
- QtWebEngine: Worked around issues with GreaseMonkey/stylesheets not being
|
||||
loaded correctly in some situations.
|
||||
- The statusbar now more closely reflects the caret mode state.
|
||||
- The icon on Windows should now be displayed in a higher resolution.
|
||||
- The QtWebEngine development tools (inspector) now also work when JavaScript is
|
||||
disabled globally.
|
||||
- Building `.exe` files now works when `upx` is installed on the system.
|
||||
- The keyhint widget now shows the correct text for chained modifiers.
|
||||
- Loading GreaseMonkey scripts now also works with Jinja2 2.8 (e.g. on Debian
|
||||
Stable).
|
||||
- Adding styles with GreaseMonkey on fast sites now works properly.
|
||||
- Window ID 0 is now excluded properly from `:tab-take` completion.
|
||||
- A rare crash when cancelling a download has been fixed.
|
||||
- The Makefile (intended for packagers) now supports `PREFIX` properly.
|
||||
- The workaround for a black window with Nvidia graphics is now enabled on
|
||||
non-Linux systems (like FreeBSD) as well.
|
||||
- Initial support for Qt 5.11.
|
||||
- Checking for a new version after sending a crash report now works properly
|
||||
again.
|
||||
- `@match` in Greasemonkey scripts now more closely matches the proper pattern
|
||||
syntax.
|
||||
- Searching via `/` or `?` now doesn't handle any characters in a special way.
|
||||
- Fixed crash when trying to retry some failed downloads on QtWebEngine.
|
||||
- An invalid spellcheck dictionary filename now doesn't crash anymore.
|
||||
- When no spellcheck dictionaries are configured, it's now disabled internally.
|
||||
This works around an issue with entering special characters on Facebook
|
||||
messenger.
|
||||
- The macOS release now should work again on macOS 10.11 and newer.
|
||||
|
||||
v1.2.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- qutebrowser now starts properly when the PyQt5 QOpenGLFunctions package wasn't
|
||||
found.
|
||||
- The keybinding cheatsheet on the quickstart page is now loaded from a local
|
||||
`qute://` URL again.
|
||||
- With "tox -e mkvenv-pypi", PyQt 5.10.0 is used again instead of Qt 5.10.1,
|
||||
because of an issue with Qt 5.10.1 which causes qutebrowser to fail to start
|
||||
("Could not find QtWebEngineProcess").
|
||||
- Unbinding keys which were bound in older qutebrowser versions now doesn't
|
||||
crash anymore.
|
||||
- Fixed a crash when reloading a page which wasn't fully loaded with v1.2.0
|
||||
- Keys on the numeric keypad now fall back to the same bindings without `Num+`
|
||||
if no `Num+` binding was found.
|
||||
- Fixed hinting on some pages with Qt < 5.10.
|
||||
- Titles are now displayed correctly again for tabs which are cloned or loaded
|
||||
from sessions.
|
||||
- Shortcuts now correctly use `Ctrl` instead of `Command` on macOS again.
|
||||
|
||||
v1.2.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Initial implementation of per-domain settings:
|
||||
* `:set` and `:config-cycle` now have a `-u`/`--pattern` argument taking a
|
||||
https://developer.chrome.com/extensions/match_patterns[URL match pattern]
|
||||
for supported settings.
|
||||
* `config.set` in `config.py` now takes a third argument which is the pattern.
|
||||
* New `with config.pattern('...') as p:` context manager for `config.py` to
|
||||
use the shorthand syntax with a pattern.
|
||||
* New `tsh` keybinding to toggle scripts for the current host. With a capital
|
||||
`S`, the toggle is saved. With a capital `H`, subdomains are included. With
|
||||
`u` instead of `h`, the exact current URL is used.
|
||||
* New `tph` keybinding to toggle plugins, with the same additional binding
|
||||
described above.
|
||||
- New QtWebEngine features:
|
||||
* Caret/visual mode
|
||||
* Authentication via ~/.netrc
|
||||
* Retrying downloads with Qt 5.10 or newer
|
||||
* Hinting and other features inside same-origin frames
|
||||
- New flags for existing commands:
|
||||
* `:session-load` has a new `--delete` flag which deletes the
|
||||
session after loading it.
|
||||
* New `--no-last` flag for `:tab-focus` to not focus the last tab when focusing
|
||||
the currently focused one.
|
||||
* New `--edit` flag for `:view-source` to open the source in an external editor.
|
||||
* New `--select` flag for `:follow-hint` which acts like the given string was entered but doesn't necessary follow the hint.
|
||||
- New special pages:
|
||||
* `qute://bindings` (opened via `:bind`) which shows all keybindings.
|
||||
* `qute://tabs` (opened via `:buffer`) which lists all tabs.
|
||||
- New settings:
|
||||
* `statusbar.widgets` to configure which widgets should be shown in which
|
||||
order in the statusbar.
|
||||
* `tabs.mode_on_change` which replaces `tabs.persist_mode_on_change`. It can
|
||||
now be set to `restore` which remembers input modes (input/passthrough)
|
||||
per tab.
|
||||
* `input.insert_mode.auto_enter` which makes it possible to disable entering
|
||||
insert mode automatically when an editable element was clicked. Together
|
||||
with `input.forward_unbound_keys`, this should allow for emacs-like
|
||||
"modeless" keybindings.
|
||||
- New `:prompt-yank` command (bound to `Alt-y` by default) to yank URLs
|
||||
referenced in prompts.
|
||||
- The `hostblock_blame` script which was removed in v1.0 was updated for the new
|
||||
config and re-added.
|
||||
- New `cycle-inputs.js` script in `scripts/` which can be used with `:jseval -f`
|
||||
to cycle through inputs.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Complete refactoring of key input handling, with various effects:
|
||||
* emacs-like keychains such as `<Ctrl-X><Ctrl-C>` can now be bound.
|
||||
* Key chains can now be bound in any mode (this allows binding unused keys in
|
||||
hint mode).
|
||||
* Yes/no prompts don't use keybindings from the `prompt` section anymore, they
|
||||
have their own `yesno` section instead.
|
||||
* Trying to bind invalid keys now shows an error.
|
||||
* The `bindings.default` setting can now only be set in a `config.py`, and
|
||||
existing values in `autoconfig.yml` are ignored.
|
||||
- Improvements for GreaseMonkey support:
|
||||
* `@include` and `@exclude` now support regex matches. With QtWebEngine and Qt
|
||||
5.8 and newer, Qt handles the matching, but similar functionality will be
|
||||
added in Qt 5.11.
|
||||
* Support for `@requires`
|
||||
* Support for the GreaseMonkey 4.0 API
|
||||
- The sqlite history now uses write-ahead logging which should be
|
||||
a performance and stability improvement.
|
||||
- When an editor is spawned with `:open-editor` and `:config-edit`, the changes
|
||||
are now applied as soon as the file is saved in the editor.
|
||||
- The `hist_importer.py` script now only imports URL schemes qutebrowser can
|
||||
handle.
|
||||
- Deleting a prefix (`:`, `/` or `?`) via backspace now leaves command mode.
|
||||
- Angular 1 elements and `<summary>`/`<details>` now get hints assigned.
|
||||
- `:tab-only` with pinned tabs now still closes unpinned tabs.
|
||||
- The `url.incdec_segments` option now also can take `port` as possible segment.
|
||||
- QtWebEngine: `:view-source` now uses Chromium's `view-source:` scheme.
|
||||
- Tabs now show their full title as tooltip.
|
||||
- When there are multiple unknown keys in a autoconfig.yml, they now all get
|
||||
reported in one error.
|
||||
- More performance improvements when opening/closing many tabs.
|
||||
- The `:version` page now has a button to pastebin the information.
|
||||
- Replacements like `{url}` can now be escaped as `{{url}}`.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- QtWebEngine bugfixes:
|
||||
* Improved fullscreen handling with Qt 5.10.
|
||||
* Hinting and scrolling now works properly on special `view-source:` pages.
|
||||
* Scroll positions are now restored correctly from sessions.
|
||||
* `:follow-selected` should now work in more cases with Qt > 5.10.
|
||||
* Incremental search now flickers less and doesn't move to the second result
|
||||
when pressing Enter.
|
||||
* Keys like `Ctrl-V` or `Shift-Insert` are now correctly handled/filtered with
|
||||
Qt 5.10.
|
||||
* Fixed hangs/segfaults on exit with Qt 5.10.1.
|
||||
* Fixed favicons sometimes getting cleared with Qt 5.10.
|
||||
* Qt download objects are now cleaned up properly when a download is removed.
|
||||
* JavaScript messages are now not double-HTML escaped anymore on Qt < 5.11
|
||||
- QtWebKit bugfixes:
|
||||
* Fixed GreaseMonkey-related crashes.
|
||||
* `:view-source` now displays a valid URL.
|
||||
- URLs containing ampersands and other special chars are now shown correctly
|
||||
when filtering them in the completion.
|
||||
- `:bookmark-add "" foo` can now be used to save the current URL with a custom
|
||||
title.
|
||||
- `:spawn -o` now waits until the process has finished before trying to show the
|
||||
output. Previously, it incorrectly showed the previous output immediately.
|
||||
- Suspended pages now should always load the correct page when being un-suspended.
|
||||
- Exception types are now shown properly with `:config-source` and `:config-edit`.
|
||||
- When using `:bookmark-add --toggle`, bookmarks are now saved properly.
|
||||
- Crash when opening an invalid URL from an application on macOS.
|
||||
- Crash with an empty `completion.timestamp_format`.
|
||||
- Crash when `completion.min_chars` is set in some cases.
|
||||
- HTML/JS resource files are now read into RAM on start to avoid crashes when
|
||||
changing qutebrowser versions while it's open.
|
||||
- Setting `bindings.key_mappings` to an empty value is now allowed.
|
||||
- Bindings to an empty commands are now ignored rather than crashing.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- `QUTE_SELECTED_HTML` is now not set for userscripts anymore except when called
|
||||
via hints.
|
||||
- The `qutebrowser_viewsource` userscript has been removed as
|
||||
`:view-source --edit` can now be used.
|
||||
- The `tabs.persist_mode_on_change` setting has been removed and replaced by
|
||||
`tabs.mode_on_change`.
|
||||
|
||||
v1.1.2
|
||||
------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Windows/macOS releases now bundle Qt 5.10.1 which includes security fixes from
|
||||
Chromium up to version 64.0.3282.140.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- QtWebEngine: Crash with Qt 5.10.1 when using :undo on some tabs.
|
||||
- Compatibility with Python 3.7
|
||||
|
||||
v1.1.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The Makefile now actually works.
|
||||
- Fixed crashes with Qt 5.10 when closing a tab before it finished loading.
|
||||
|
||||
v1.1.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Initial support for Greasemonkey scripts. There are still some rough edges,
|
||||
but many scripts should already work.
|
||||
- There's now a `misc/Makefile` file in releases, which should help
|
||||
distributions which package qutebrowser, as they can run something like
|
||||
`make -f misc/Makefile DESTDIR="$pkgdir" install` now.
|
||||
- New fields for `window.title_format` and `tabs.title.format`:
|
||||
* `{current_url}`
|
||||
* `{protocol}`
|
||||
- New settings:
|
||||
* `colors.statusbar.passthrough.fg`/`.bg`
|
||||
* `completion.delay` and `completion.min_chars` to update the completion less
|
||||
often.
|
||||
* `completion.use_best_match` to automatically use the best-matching
|
||||
command in the completion.
|
||||
* `keyhint.radius` to configure the edge rounding for the key hint widget.
|
||||
* `qt.highdpi` to turn on Qt's High-DPI scaling.
|
||||
* `tabs.pinned.shrink` (`true` by default) to make it possible
|
||||
for pinned tabs and normal tabs to have the same size.
|
||||
* `content.windowed_fullscreen` to show e.g. a fullscreened video in the
|
||||
window without fullscreening that window.
|
||||
* `tabs.persist_mode_on_change` to keep the current mode when
|
||||
switching tabs.
|
||||
* `session.lazy_restore` which allows to not load pages immediately
|
||||
when restoring a session.
|
||||
- New commands:
|
||||
* `:tab-give` and `:tab-take`, to give tabs to another window, or take them
|
||||
from another window.
|
||||
* `:completion-item-yank` (bound to `<Ctrl-C>`) to yank the current
|
||||
completion item text.
|
||||
* `:edit-command` to edit the commandline in an editor.
|
||||
* `search.incremental` for incremental text search.
|
||||
- New flags for existing commands:
|
||||
* `-o` flag for `:spawn` to show stdout/stderr in a new tab.
|
||||
* `--rapid` flag for `:command-accept` (bound to `Ctrl-Enter` by default),
|
||||
which allows executing a command in the completion without closing it.
|
||||
* `--private` and `--related` flags for `:edit-url`, which have the
|
||||
same effect they have with `:open`.
|
||||
* `--history` for `:completion-item-focus` which causes it to go
|
||||
through the command history when no text was entered. The default bindings for
|
||||
cursor keys in the completion changed to use that, so that they can be used
|
||||
again to navigate through completion items when a text was entered.
|
||||
* `--file` for `:debug-pyeval` which makes it take a filename instead of a
|
||||
line of code.
|
||||
- New `config.source(...)` method for `config.py` to source another file.
|
||||
- New `{line}` and `{column}` replacements for `editor.command` to position the
|
||||
cursor correctly.
|
||||
- New `qute-pass` userscript as alternative to `password_fill` which allows
|
||||
selecting accounts via rofi or any other dmenu-compatile application.
|
||||
- New `hist_importer.py` script to import history from Firefox/Chromium.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Some settings got renamed:
|
||||
* `tabs.width.bar` -> `tabs.width`
|
||||
* `tabs.width.indicator` -> `tabs.indicator.width`
|
||||
* `tabs.indicator_padding` -> `tabs.indicator.padding`
|
||||
* `session_default_name` -> `session.default_name`
|
||||
* `ignore_case` -> `search.ignore_case`
|
||||
- Much improved user stylesheet handling for QtWebEngine which reduces
|
||||
flickering and updates immediately after setting a stylesheet.
|
||||
- High-DPI favicons are now used when available.
|
||||
- The `asciidoc2html.py` script now uses Pygments (which is already a dependency
|
||||
of qutebrowser) instead of `source-highlight` for syntax highlighting.
|
||||
- The `:buffer` command now doesn't require quoting anymore, similar to `:open`.
|
||||
- The `importer.py` script was largely rewritten and now also supports importing
|
||||
from Firefox' `places.sqlite` file and Chrome/Chromium profiles.
|
||||
- Various internal refactorings to use Python 3.5 and ECMAscript 6 features.
|
||||
- If the `window.hide_wayland_decoration` setting is False, but
|
||||
`QT_WAYLAND_DISABLE_WINDOWDECORATION` is set in the environment,
|
||||
the decorations are still hidden.
|
||||
- The `install_dict.py` script for QtWebEngine was renamed to `dictcli.py` and
|
||||
can now also upgrade dictionaries correctly.
|
||||
- `:undo` now can re-open multiple tabs after `:tab-only` was used.
|
||||
- `:config-write-py` with a relative path now puts the file into the config
|
||||
directory.
|
||||
- The `qute://version` page now also shows the uptime of qutebrowser.
|
||||
- qutebrowser now prompts to create a non-existing directory when starting a
|
||||
download.
|
||||
- `:jseval --file` now searches relative paths in a `js/` subdir in
|
||||
qutebrowser's data dir, e.g. `~/.local/share/qutebrowser/js`.
|
||||
- The current/default bindings are now shown in the ``:bind` completion.
|
||||
- Empty categories are now hidden in the `:open` completion.
|
||||
- Search terms for URLs and titles can now be mixed when filtering the
|
||||
completion.
|
||||
- The default font size for the UI got bumped up from 8pt to 10pt.
|
||||
- Improved matching in the completion: The words entered are now matched in any
|
||||
order, and mixed matches on URL/tite are possible.
|
||||
- The system's default encoding (rather than UTF-8) is now used to decode
|
||||
subprocess output.
|
||||
- qutebrowser now ensures it's focused again after an external editor is closed.
|
||||
- The `colors.completion.fg` setting can now be a list, allowing to specify
|
||||
different colors for the three completion columns.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- More consistent sizing for favicons with vertical tabs.
|
||||
- Using `:home` on pinned tabs is now prevented.
|
||||
- Fix crash with unknown file types loaded via `qute://help`.
|
||||
- Scrolling performance improvements.
|
||||
- Sites like `qute://help` now redirect to `qute://help/` to make sure links
|
||||
work properly.
|
||||
- Fixes for the size calculation of pinned tabs in the tab bar.
|
||||
- Worked around a crash with PyQt 5.9.1 compiled against Qt < 5.9.1 when using
|
||||
`:yank` or `qute://` URLs.
|
||||
- Fixed crash when opening `qute://help/img`.
|
||||
- Fixed `gU` (`:navigate up`) on `qute://help` and webservers not handling `..`
|
||||
in a URL.
|
||||
- Using e.g. `-s backend webkit` to set the backend now works correctly.
|
||||
- Fixed crash when closing the tab an external editor was opened in.
|
||||
- When using `:search-next` before a search is finished, no warning about no
|
||||
results being found is shown anymore.
|
||||
- Fix `:click-element` with an ID containing non-alphanumeric characters.
|
||||
- Fix crash when a subprocess outputs data which is not decodable as UTF-8.
|
||||
- Fix crash when closing a tab immediately after hinting.
|
||||
- Worked around issues in Qt 5.10 with loading progress never being finished.
|
||||
- Fixed a crash when writing a flag before a command (e.g. `:-w open `).
|
||||
- Fixed a crash when clicking certain form elements with QtWebEngine.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- `:tab-detach` has been deprecated, as `:tab-give` without argument can be used
|
||||
instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The long-deprecated `:prompt-yes`, `:prompt-no`, `:paste-primary` and `:paste`
|
||||
commands have been removed.
|
||||
- The invocation `:download <url> <dest>` which was deprecated in v0.5.0 was
|
||||
removed, use `:download --dest <dest> <url>` instead.
|
||||
- The `messages.unfocused` option which wasn't used anymore was removed.
|
||||
- The `x[xtb]` default bindings got removed again as many users accidentally
|
||||
triggered them.
|
||||
|
||||
v1.0.4
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The `qute://gpl` page now works correctly again.
|
||||
- Trying to bind an empty command now doesn't crash anymore.
|
||||
- Fixed crash when `:config-write-py` fails to write to the given path.
|
||||
- Fixed crash for some users when selecting a file with Qt 5.9.3
|
||||
- Improved handling for various SQL errors
|
||||
- Fix crash when setting content.cache.size to a big value (> 2 GB)
|
||||
|
||||
v1.0.3
|
||||
------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- macOS and Windows builds are now built with PyQt 5.9.1 and Qt 5.9.2, including
|
||||
various bugfixes, as well as security fixes from Chromium up to version
|
||||
61.0.3163.79.
|
||||
- Performance improvements for tab rendering.
|
||||
- The :open-editor command is now not hidden anymore as it's also usable in
|
||||
normal mode.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Handle accessing a locked sqlite database gracefully
|
||||
- Abort pinned tab dialogs properly when a tab is closed e.g. by closing a
|
||||
window
|
||||
- Unbinding a default keybinding twice now doesn't bind it again
|
||||
- Completions are now sorted correctly again when filtered
|
||||
|
||||
v1.0.2
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fix workaround for black screens or crashes with Nvidia cards
|
||||
- Handle a filesystem going read-only gracefully
|
||||
- Fix crash when setting `fonts.monospace`
|
||||
- Fix list options not being modifyable via `.append()` in `config.py`
|
||||
- Mark the content.notifications setting as QtWebKit only correctly
|
||||
- Fix wrong rendering of keys like `<back>` in the completion
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Nicer error messages and other minor improvements
|
||||
|
||||
v1.0.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed starting after customizing `fonts.tabs` or `fonts.debug_console`.
|
||||
- Fixed starting with old PyQt versions compiled against newer Qt versions.
|
||||
- Fixed check for PyQt version to correctly enforce 5.7 (not 5.2).
|
||||
|
||||
v1.0.0
|
||||
------
|
||||
|
||||
Major changes
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
- Dependency changes:
|
||||
* Support for legacy QtWebKit (before 5.212 which is
|
||||
https://github.com/annulen/webkit/wiki[distributed independently from Qt])
|
||||
is dropped.
|
||||
* Support for Python 3.4 is dropped.
|
||||
* Support for Qt before 5.7.1 and PyQt before 5.7 is dropped.
|
||||
* New dependency on the QtSql module and Qt sqlite support.
|
||||
* New dependency on the http://www.attrs.org/[attrs] project (packaged as
|
||||
`python-attr` in some distributions).
|
||||
* The depedency on PyOpenGL (when using QtWebEngine) got removed. Note
|
||||
that PyQt5.QtOpenGL is still a dependency.
|
||||
* PyQt5.QtOpenGL is now always required, even with QtWebKit.
|
||||
- The QtWebEngine backend is now used by default. Note this means that
|
||||
QtWebEngine now should be a required dependency, and QtWebKit (if new enough)
|
||||
should be changed to an optional dependency.
|
||||
- Completely rewritten configuration system which ignores the old config file.
|
||||
See link:qute://help/configuring.html[] for details.
|
||||
- Various documentation files got moved to the doc/ subfolder;
|
||||
`qutebrowser.desktop` got moved to misc/.
|
||||
- `:set` now doesn't support toggling/cycling values anymore, that functionality
|
||||
got moved to `:config-cycle`.
|
||||
- New completion engine based on sqlite, which allows to complete
|
||||
the entire browsing history. The default for
|
||||
`completion.web_history_max_items` got changed to `-1` (unlimited). If the
|
||||
completion is too slow on your machine, try setting it to a few 1000 items.
|
||||
- Up/Down now navigates through the command history instead of selecting
|
||||
completion items. Either use Tab to cycle through the completion, or
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc#migrating-older-configurations[restore the old behavior].
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- QtWebEngine: Spell checking support, see the `spellcheck.languages` setting.
|
||||
- New `qt.args` setting to pass additional arguments to Qt/Chromium.
|
||||
- New `backend` setting to select the backend to use.
|
||||
Together with the previous setting, this should make most wrapper scripts
|
||||
unnecessary.
|
||||
- qutebrowser can now be set as the default browser on macOS.
|
||||
- New config commands:
|
||||
* `:config-cycle` to cycle an option between multiple values.
|
||||
* `:config-unset` to remove a configured option.
|
||||
* `:config-clear` to remove all configured options.
|
||||
* `:config-source` to (re-)read a `config.py` file.
|
||||
* `:config-edit` to open the `config.py` file in an editor.
|
||||
* `:config-write-py` to write a `config.py` template file.
|
||||
- New `:version` command which opens `qute://version`.
|
||||
- New back/forward indicator in the statusbar.
|
||||
- New `bindings.key_mappings` setting to map keys to other keys.
|
||||
- QtWebEngine: Support for proxy authentication.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Using `:download` now uses the page's title as filename.
|
||||
- Using `:back` or `:forward` with a count now skips intermediate pages.
|
||||
- When there are multiple messages shown, the timeout is increased.
|
||||
- `:search` now only clears the search if one was displayed before, so pressing
|
||||
`<Escape>` doesn't un-focus inputs anymore.
|
||||
- Pinned tabs now adjust to their text's width, so the `tabs.width.pinned`
|
||||
setting got removed.
|
||||
- `:set-cmd-text` now has a `--run-on-count` argument to run the underlying
|
||||
command directly if a count was given.
|
||||
- `:scroll-perc` got renamed to `:scroll-to-perc`.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- Migrating QtWebEngine data written by versions before 2016-11-15 (before
|
||||
v0.9.0) is now not supported anymore.
|
||||
- Upgrading qutebrowser with a version older than v0.4.0 still running now won't
|
||||
work properly anymore.
|
||||
- The `--harfbuzz` and `--relaxed-config` commandline arguments got dropped.
|
||||
|
||||
Fixes
|
||||
~~~~~
|
||||
|
||||
- Exiting fullscreen via `:fullscreen` or buttons on a page now
|
||||
restores the correct previous window state (maximized/fullscreen).
|
||||
- When `input.insert_mode.auto_load` is set, background tabs now don't enter
|
||||
insert mode anymore.
|
||||
- The keybinding help widget now works correctly when using keybindings with a
|
||||
count.
|
||||
- The `window.hide_wayland_decoration` setting now works correctly again.
|
||||
|
||||
v0.11.1
|
||||
-------
|
||||
|
||||
@@ -821,11 +74,11 @@ Changed
|
||||
- Starting with legacy QtWebKit now shows a warning message.
|
||||
*With the next release, support for it will be removed.*
|
||||
- The Windows releases are redone from scratch, which means:
|
||||
* They now use the new QtWebEngine backend
|
||||
* The bundled Qt is updated from 5.5 to 5.9
|
||||
* The bundled Python is updated from 3.4 to 3.6
|
||||
* They are now generated with PyInstaller instead of cx_Freeze
|
||||
* The installer is now generated using NSIS instead of being a MSI
|
||||
- They now use the new QtWebEngine backend
|
||||
- The bundled Qt is updated from 5.5 to 5.9
|
||||
- The bundled Python is updated from 3.4 to 3.6
|
||||
- They are now generated with PyInstaller instead of cx_Freeze
|
||||
- The installer is now generated using NSIS instead of being a MSI
|
||||
- Improved `qute://history` page (with lazy loading)
|
||||
- Crash reports are not public anymore.
|
||||
- Paths like `C:` are now treated as absolute paths on Windows for downloads,
|
||||
@@ -964,13 +217,13 @@ Removed
|
||||
~~~~~~~
|
||||
|
||||
- (QtWebKit) Various rarely customized settings were removed:
|
||||
* `ui -> css-media-type` (defaults to desktop)
|
||||
* `general -> site-specific-quirks` (now always turned on)
|
||||
* `storage -> offline-storage-default-quota` (defaults to 5MB)
|
||||
* `storage -> offline-web-application-cache-quota` (defaults to no quota)
|
||||
* `storage -> object-cache-capacities` (default depends on disk space)
|
||||
* `content -> css-regions` (now always turned off)
|
||||
* `storage -> offline-storage-database` (merged into `storage -> local-storage`)
|
||||
- `ui -> css-media-type` (defaults to desktop)
|
||||
- `general -> site-specific-quirks` (now always turned on)
|
||||
- `storage -> offline-storage-default-quota` (defaults to 5MB)
|
||||
- `storage -> offline-web-application-cache-quota` (defaults to no quota)
|
||||
- `storage -> object-cache-capacities` (default depends on disk space)
|
||||
- `content -> css-regions` (now always turned off)
|
||||
- `storage -> offline-storage-database` (merged into `storage -> local-storage`)
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
@@ -1544,7 +797,7 @@ Added
|
||||
- New `:fake-key` command to send a fake keypress to a website or to
|
||||
qutebrowser.
|
||||
- New `--mhtml` argument for `:download` to download a page including all
|
||||
resources as MHTML file.
|
||||
ressources as MHTML file.
|
||||
- New option `tabs -> title-alignment` to change the alignment of tab titles.
|
||||
|
||||
Changed
|
||||
@@ -1744,7 +997,7 @@ Added
|
||||
- New argument `--no-err-windows` to suppress all error windows.
|
||||
- New arguments `--top-navigate` and `--bottom-navigate` (`-t`/`-b`) for `:scroll-page` to specify a navigation action (e.g. automatically go to the next page when arriving at the bottom).
|
||||
- New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is.
|
||||
- New flag `-v`/`--verbose` for `:spawn` to print information when the process started/exited successfully.
|
||||
- New flag `-v`/`--verbose` for `:spawn` to print informations when the process started/exited successfully.
|
||||
- Many new color settings (foreground setting for every background setting).
|
||||
- New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar.
|
||||
- New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one.
|
||||
@@ -5,11 +5,6 @@ The Compiler <mail@qutebrowser.org>
|
||||
:data-uri:
|
||||
:toc:
|
||||
|
||||
IMPORTANT: I'm currently (July 2018) more busy than usual until September,
|
||||
because of exams coming up. Review of non-trivial pull requests will thus be
|
||||
delayed until then. If you're reading this note after mid-September, please
|
||||
open an issue.
|
||||
|
||||
I `<3` footnote:[Of course, that says `<3` in HTML.] contributors!
|
||||
|
||||
This document contains guidelines for contributing to qutebrowser, as well as
|
||||
@@ -44,13 +39,14 @@ pointers:
|
||||
|
||||
* https://github.com/qutebrowser/qutebrowser/labels/easy[Issues which should
|
||||
be easy to solve]
|
||||
* https://github.com/qutebrowser/qutebrowser/labels/component%3A%20docs[Documentation issues which require little/no coding]
|
||||
* https://github.com/qutebrowser/qutebrowser/labels/not%20code[Issues which
|
||||
require little/no coding]
|
||||
|
||||
If you prefer C++ or Javascript to Python, see the relevant issues which involve
|
||||
work in those languages:
|
||||
|
||||
* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3A%22language%3A+c%2B%2B%22[C++] (mostly work on Qt, the library behind qutebrowser)
|
||||
* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3A%22language%3A+javascript%22[JavaScript]
|
||||
* https://github.com/qutebrowser/qutebrowser/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3Ac%2B%2B[C++] (mostly work on Qt, the library behind qutebrowser)
|
||||
* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Ajavascript[JavaScript]
|
||||
|
||||
There are also some things to do if you don't want to write code:
|
||||
|
||||
@@ -90,16 +86,6 @@ git format-patch origin/master <1>
|
||||
<1> Replace `master` by the branch your work was based on, e.g.,
|
||||
`origin/develop`.
|
||||
|
||||
Running qutebrowser
|
||||
-------------------
|
||||
|
||||
After link:install.asciidoc#tox[installing qutebrowser via tox], you can run
|
||||
`.venv/bin/qutebrowser --debug --temp-basedir` to test your changes with debug
|
||||
logging enabled and without affecting existing running instances.
|
||||
|
||||
Alternatively, you can install qutebrowser's dependencies system-wide and run
|
||||
`python3 -m qutebrowser --debug --temp-basedir`.
|
||||
|
||||
Useful utilities
|
||||
----------------
|
||||
|
||||
@@ -112,18 +98,25 @@ unittests and several linters/checkers.
|
||||
Currently, the following tox environments are available:
|
||||
|
||||
* Tests using https://www.pytest.org[pytest]:
|
||||
- `py35`, `py36`: Run pytest for python 3.5/3.6 with the system-wide PyQt.
|
||||
- `py36-pyqt57`, ..., `py36-pyqt59`: Run pytest with the given PyQt version (`py35-*` also works).
|
||||
- `py36-pyqt59-cov`: Run with coverage support (other Python/PyQt versions work too).
|
||||
* `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8].
|
||||
- `py34`: Run pytest for python-3.4.
|
||||
- `py35`: Run pytest for python-3.5.
|
||||
- `py34-cov`: Run pytest for python-3.4 with code coverage report.
|
||||
- `py35-cov`: Run pytest for python-3.5 with code coverage report.
|
||||
* `flake8`: Run https://pypi.python.org/pypi/flake8[flake8] checks:
|
||||
https://pypi.python.org/pypi/pyflakes[pyflakes],
|
||||
https://pypi.python.org/pypi/pep8[pep8],
|
||||
https://pypi.python.org/pypi/mccabe[mccabe]
|
||||
* `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find
|
||||
unused code portions.
|
||||
* `pylint`: Run http://pylint.org/[pylint] static code analysis.
|
||||
* `pydocstyle`: Check
|
||||
https://www.python.org/dev/peps/pep-0257/[PEP257] compliance with
|
||||
https://github.com/PyCQA/pydocstyle[pydocstyle]
|
||||
* `pyroma`: Check packaging practices with
|
||||
https://pypi.python.org/pypi/pyroma/[pyroma].
|
||||
https://pypi.python.org/pypi/pyroma/[pyroma]
|
||||
* `eslint`: Run http://eslint.org/[ESLint] javascript checker.
|
||||
* `check-manifest`: Check MANIFEST.in completeness with
|
||||
https://github.com/mgedmin/check-manifest[check-manifest].
|
||||
https://github.com/mgedmin/check-manifest[check-manifest]
|
||||
* `mkvenv`: Bootstrap a virtualenv for testing.
|
||||
* `misc`: Run `scripts/misc_checks.py` to check for:
|
||||
- untracked git files
|
||||
@@ -140,7 +133,7 @@ techniques are useful to handle these:
|
||||
|
||||
* Use `_foo` for unused parameters, with `foo` being a descriptive name. Using
|
||||
`_` is discouraged.
|
||||
* If you think you have a good reason to suppress a message, then add the
|
||||
* If you think you have a good reason to suppress a message, then add the
|
||||
following comment:
|
||||
+
|
||||
----
|
||||
@@ -188,7 +181,7 @@ In the _scripts/_ subfolder there's a `run_profile.py` which profiles the code
|
||||
and shows a graphical representation of what takes how much time.
|
||||
|
||||
It uses the built-in Python
|
||||
https://docs.python.org/3.6/library/profile.html[cProfile] module and can show
|
||||
https://docs.python.org/3.4/library/profile.html[cProfile] module and can show
|
||||
the output in four different ways:
|
||||
|
||||
* Raw profile file (`--profile-tool=none`)
|
||||
@@ -203,8 +196,8 @@ There are some useful functions for debugging in the `qutebrowser.utils.debug`
|
||||
module.
|
||||
|
||||
When starting qutebrowser with the `--debug` flag, you also get useful debug
|
||||
logs. You can add +--logfilter _[!]category[,category,...]_+ to restrict
|
||||
logging to the given categories.
|
||||
logs. You can add +--logfilter _category[,category,...]_+ to restrict logging
|
||||
to the given categories.
|
||||
|
||||
With `--debug` there are also some additional +debug-_*_+ commands available,
|
||||
for example `:debug-all-objects` and `:debug-all-widgets` which print a list of
|
||||
@@ -299,7 +292,7 @@ There are some exceptions to that:
|
||||
|
||||
* `QThread` is used instead of Python threads because it provides signals and
|
||||
slots.
|
||||
* `QProcess` is used instead of Python's `subprocess`.
|
||||
* `QProcess` is used instead of Python's `subprocess`
|
||||
* `QUrl` is used instead of storing URLs as string, see the
|
||||
<<handling-urls,handling URLs>> section for details.
|
||||
|
||||
@@ -320,7 +313,7 @@ carefully be checked.
|
||||
* Methods of Qt objects have certain maximum values based on their
|
||||
underlying C++ types.
|
||||
+
|
||||
To avoid passing too large of a numeric parameter to a Qt function, all
|
||||
To avoid passing too large of a numeric parameter to a Qt function, all
|
||||
numbers should be range-checked using `qutebrowser.qtutils.check_overflow`,
|
||||
or by other means (e.g. by setting a maximum value for a config object).
|
||||
|
||||
@@ -334,7 +327,7 @@ dictionaries which map object names to the actual long-living objects.
|
||||
There are currently these object registries, also called 'scopes':
|
||||
|
||||
* The `global` scope, with objects which are used globally (`config`,
|
||||
`cookie-jar`, etc.).
|
||||
`cookie-jar`, etc.)
|
||||
* The `tab` scope with objects which are per-tab (`hintmanager`, `webview`,
|
||||
etc.). Passing this scope to `objreg.get()` selects the object in the currently
|
||||
focused tab by default. A tab can be explicitly selected by passing
|
||||
@@ -390,7 +383,7 @@ The following logging levels are available for every logger:
|
||||
|error |There was an issue and some kind of operation was abandoned.
|
||||
|warning |There was an issue but the operation can continue running.
|
||||
|info |General informational messages.
|
||||
|debug |Verbose debugging information.
|
||||
|debug |Verbose debugging informations.
|
||||
|=======================================================================
|
||||
|
||||
[[commands]]
|
||||
@@ -443,14 +436,14 @@ def foo(bar: int, baz=True):
|
||||
----
|
||||
|
||||
Possible values:
|
||||
|
||||
- A callable (`int`, `float`, etc.): Gets called to validate/convert the value.
|
||||
- A python enum type: All members of the enum are possible values.
|
||||
- A `typing.Union` of multiple types above: Any of these types are valid
|
||||
values, e.g., `typing.Union[str, int]`.
|
||||
- A callable (`int`, `float`, etc.): Gets called to validate/convert the
|
||||
value.
|
||||
- A python enum type: All members of the enum are possible values.
|
||||
- A `typing.Union` of multiple types above: Any of these types are valid
|
||||
values, e.g., `typing.Union[str, int]`
|
||||
|
||||
You can customize how an argument is handled using the `@cmdutils.argument`
|
||||
decorator *after* `@cmdutils.register`. This can, for example, be used to
|
||||
decorator *after* `@cmdutils.register`. This can, for example, be used to
|
||||
customize the flag an argument should get:
|
||||
|
||||
[source,python]
|
||||
@@ -477,10 +470,10 @@ For `typing.Union` types, the given `choices` are only checked if other types
|
||||
The following arguments are supported for `@cmdutils.argument`:
|
||||
|
||||
- `flag`: Customize the short flag (`-x`) the argument will get.
|
||||
- `win_id=True`: Mark the argument as special window ID argument.
|
||||
- `count=True`: Mark the argument as special count argument.
|
||||
- `completion`: A completion function (see `qutebrowser.completions.models.*`)
|
||||
to use when completing arguments for the given command.
|
||||
- `win_id=True`: Mark the argument as special window ID argument
|
||||
- `count=True`: Mark the argument as special count argument
|
||||
- `hide=True`: Hide the argument from the documentation
|
||||
- `completion`: A `usertypes.Completion` member to use as completion.
|
||||
- `choices`: The allowed string choices for the argument.
|
||||
|
||||
The name of an argument will always be the parameter name, with any trailing
|
||||
@@ -542,12 +535,12 @@ ____
|
||||
Setting up a Windows Development Environment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Install https://www.python.org/downloads/release/python-362/[Python 3.6].
|
||||
* Install PyQt via `pip install PyQt5`.
|
||||
* Create a file at `C:\Windows\system32\python3.bat` with the following content (adjust the path as necessary):
|
||||
`@C:\Python36\python %*`.
|
||||
This will make the Python 3.6 interpreter available as `python3`, which is used by various development scripts.
|
||||
* Install git from the https://git-scm.com/download/win[git-scm downloads page].
|
||||
* Install https://www.python.org/downloads/release/python-344/[Python 3.4]
|
||||
* Install https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5.1/[PyQt 5.5]
|
||||
* Create a file at `C:\Windows\system32\python3.bat` with the following content:
|
||||
`@C:\Python34\python %*`
|
||||
This will make the Python 3.4 interpreter available as `python3`, which is used by various development scripts.
|
||||
* Install git from the https://git-scm.com/download/win[git-scm downloads page]
|
||||
Try not to enable `core.autocrlf`, since that will cause `flake8` to complain a lot. Use an editor that can deal with plain line feeds instead.
|
||||
* Clone your favourite qutebrowser repository.
|
||||
* To install tox, open an elevated cmd, enter your working directory and run `pip install -rmisc/requirements/requirements-tox.txt`.
|
||||
@@ -581,23 +574,6 @@ can be useful for debugging:
|
||||
- chrome://gpuclean/ (crashes the current renderer process!)
|
||||
- chrome://ppapiflashcrash/
|
||||
- chrome://ppapiflashhang/
|
||||
- chrome://quota-internals/ (Qt 5.11)
|
||||
- chrome://taskscheduler-internals/ (Qt 5.11)
|
||||
- chrome://sandbox/ (Qt 5.11, Linux only)
|
||||
|
||||
QtWebEngine internals
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is mostly useful for qutebrowser maintainers to work around issues in Qt - if you don't understand it, don't worry, just ignore it.
|
||||
|
||||
The hierarchy of widgets when QtWebEngine is involved looks like this:
|
||||
|
||||
- qutebrowser has a `WebEngineTab` object, which is its abstraction over QtWebKit/QtWebEngine.
|
||||
- The `WebEngineTab` has a `_widget` attribute, which is the https://doc.qt.io/qt-5/qwebengineview.html[QWebEngineView]
|
||||
- That view has a https://doc.qt.io/qt-5/qwebenginepage.html[QWebEnginePage] for everything which doesn't require rendering.
|
||||
- The view also has a layout with exactly one element (which also is its `focusProxy()`)
|
||||
- That element is the http://code.qt.io/cgit/qt/qtwebengine.git/tree/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp[RenderWidgetHostViewQtDelegateWidget] (it inherits https://doc.qt.io/qt-5/qquickwidget.html[QQuickWidget]) - also often referred to as RWHV or RWHVQDW. It can be obtained via `sip.cast(tab._widget.focusProxy(), QQuickWidget)`.
|
||||
- Calling `rootObject()` on that gives us the https://doc.qt.io/qt-5/qquickitem.html[QQuickItem] where Chromium renders into (?). With it, we can do things like `.setRotation(20)`.
|
||||
|
||||
Style conventions
|
||||
-----------------
|
||||
@@ -683,7 +659,7 @@ New Qt release
|
||||
https://bugreports.qt.io/issues/?jql=reporter%20%3D%20%22The%20Compiler%22%20ORDER%20BY%20fixVersion%20ASC[Qt bugtracker]
|
||||
and make sure all bugs marked as resolved are actually fixed.
|
||||
* Update own PKGBUILDs based on upstream Archlinux updates and rebuild.
|
||||
* Update recommended Qt version in `README`.
|
||||
* Update recommended Qt version in `README`
|
||||
* Grep for `WORKAROUND` in the code and test if fixed stuff works without the
|
||||
workaround.
|
||||
* Check relevant
|
||||
@@ -693,32 +669,38 @@ bugs] and check if they're fixed.
|
||||
New PyQt release
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* See above.
|
||||
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions.
|
||||
* 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
|
||||
|
||||
qutebrowser release
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Make sure there are no unstaged changes and the tests are green.
|
||||
* Make sure all issues with the related milestone are closed.
|
||||
* Run `x=... y=...` to set the respective shell variables.
|
||||
* Run `x=... y=...` to set the respective shell variables
|
||||
|
||||
* Update changelog (remove *(unreleased)*).
|
||||
* Add newest config to `tests/unit/config/old_configs` and update `test_upgrade_version`
|
||||
- `python -m qutebrowser --basedir conf :quit`
|
||||
- `sed '/^#/d' conf/config/qutebrowser.conf > tests/unit/config/old_configs/qutebrowser-v0.$x.$y.conf`
|
||||
- `rm -r conf`
|
||||
- git add
|
||||
- commit
|
||||
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
|
||||
* Commit.
|
||||
* Update changelog (remove *(unreleased)*)
|
||||
* Run tests again
|
||||
* Commit
|
||||
|
||||
* Create annotated git tag (`git tag -s "v1.$x.$y" -m "Release v1.$x.$y"`).
|
||||
* `git push origin`; `git push origin v1.$x.$y`.
|
||||
* Create annotated git tag (`git tag -s "v0.$x.$y" -m "Release v0.$x.$y"`)
|
||||
* `git push origin`; `git push origin v0.$x.$y`
|
||||
* If committing on minor branch, cherry-pick release commit to master.
|
||||
* Create release on github.
|
||||
* Create release on github
|
||||
* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones
|
||||
as closed.
|
||||
|
||||
* 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; py -3.6 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).
|
||||
* macOS: Run `pyenv shell 3.6.6 && git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* On server:
|
||||
- Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
|
||||
- Run `git pull github master && sudo python3 scripts/asciidoc2html.py --website /srv/http/qutebrowser`
|
||||
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed.
|
||||
* Announce to qutebrowser and qutebrowser-announce mailinglist.
|
||||
* Linux: Run `python3 scripts/dev/build_release.py --upload v0.$x.$y`
|
||||
* Windows: Run `C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v0.X.Y` (replace X/Y by hand)
|
||||
* OS X: Run `python3 scripts/dev/build_release.py --upload v0.X.Y` (replace X/Y by hand)
|
||||
* On server: Run `python3 scripts/dev/download_release.py v0.X.Y` (replace X/Y by hand)
|
||||
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed
|
||||
* Announce to qutebrowser and qutebrowser-announce mailinglist
|
||||
@@ -28,20 +28,16 @@ What's wrong with link:http://portix.bitbucket.org/dwb/[dwb]/link:http://sourcef
|
||||
https://lists.webkit.org/pipermail/webkit-gtk/2014-March/001821.html[deprecated],
|
||||
these bugs are never going to be fixed.
|
||||
+
|
||||
When qutebrowser was created, the newer
|
||||
http://webkitgtk.org/reference/webkit2gtk/stable/index.html[WebKit2 API] lacked
|
||||
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
|
||||
still only a few projects which have some kind of WebKit2 support (see the
|
||||
https://github.com/qutebrowser/qutebrowser#similar-projects[list of
|
||||
alternatives]).
|
||||
The newer http://webkitgtk.org/reference/webkit2gtk/stable/index.html[WebKit2
|
||||
API] seems to lack basic features like proxy support, and almost no projects
|
||||
seem to have started porting to WebKit2 (I only know of
|
||||
http://www.uzbl.org/[uzbl]).
|
||||
+
|
||||
qutebrowser uses http://qt.io/[Qt] and
|
||||
http://wiki.qt.io/QtWebEngine[QtWebEngine] by default (and supports
|
||||
http://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
|
||||
much more man-power behind it than WebKitGTK+ has, and thus supports more modern
|
||||
web features - it's also arguably more secure.
|
||||
qutebrowser uses http://qt.io/[Qt] and http://wiki.qt.io/QtWebKit[QtWebKit]
|
||||
instead, which suffers from far less such crashes. It might switch to
|
||||
http://wiki.qt.io/QtWebEngine[QtWebEngine] in the future, which is based on
|
||||
Google's https://en.wikipedia.org/wiki/Blink_(layout_engine)[Blink] rendering
|
||||
engine.
|
||||
|
||||
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]?::
|
||||
Firefox likes to break compatibility with addons on each upgrade, gets
|
||||
@@ -70,31 +66,6 @@ But isn't Python too slow for a browser?::
|
||||
and WebKit in C++, with the
|
||||
https://wiki.python.org/moin/GlobalInterpreterLock[GIL] released.
|
||||
|
||||
Is qutebrowser secure?::
|
||||
Most security issues are in the backend (which handles networking,
|
||||
rendering, JavaScript, etc.) and not qutebrowser itself.
|
||||
+
|
||||
qutebrowser uses http://wiki.qt.io/QtWebEngine[QtWebEngine] by default.
|
||||
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
|
||||
months), every patch release backports security fixes from newer Chromium
|
||||
versions. In other words: As long as you're using an up-to-date Qt, you should
|
||||
be recieving security updates on a regular basis, without qutebrowser having to
|
||||
do anything. Chromium's process isolation and
|
||||
https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md[sandboxing]
|
||||
features are also enabled as a second line of defense.
|
||||
+
|
||||
http://wiki.qt.io/QtWebKit[QtWebKit] is also supported as an alternative
|
||||
backend, but hasn't seen new releases
|
||||
https://github.com/annulen/webkit/releases[in a while]. It also doesn't have any
|
||||
process isolation or sandboxing.
|
||||
+
|
||||
Security issues in qutebrowser's code happen very rarely (as per March 2018,
|
||||
there has been one security issue caused by qutebrowser in over four years) and
|
||||
are fixed timely. To report security bugs, please contact me directly at
|
||||
mail@qutebrowser.org, GPG ID
|
||||
https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072].
|
||||
|
||||
Is there an adblocker?::
|
||||
There is a host-based adblocker which takes /etc/hosts-like lists. A "real"
|
||||
adblocker has a
|
||||
@@ -154,9 +125,9 @@ When using quickmark, you can give them all names, like
|
||||
without having to remember the exact website title or address.
|
||||
|
||||
How do I use spell checking?::
|
||||
Configuring spell checking in qutebrowser depends on the backend in use
|
||||
(see https://github.com/qutebrowser/qutebrowser/issues/700[#700] for
|
||||
a more detailed discussion).
|
||||
Qutebrowser's support for spell checking is somewhat limited at the moment
|
||||
(see https://github.com/qutebrowser/qutebrowser/issues/700[#700]), but it
|
||||
can be done.
|
||||
+
|
||||
For QtWebKit:
|
||||
|
||||
@@ -174,14 +145,15 @@ For QtWebKit:
|
||||
+
|
||||
For QtWebEngine:
|
||||
|
||||
. Make sure your versions of PyQt and Qt are 5.8 or higher.
|
||||
. Use `dictcli.py` script to install dictionaries.
|
||||
Run the script with `-h` for the parameter description.
|
||||
. Set `spellcheck.languages` to the desired list of languages, e.g.:
|
||||
`:set spellcheck.languages "['en-US', 'pl-PL']"`
|
||||
. Not yet supported unfortunately :-( +
|
||||
Adding it shouldn't be too hard though, since QtWebEngine 5.8 added an API for
|
||||
this (see
|
||||
https://github.com/qutebrowser/qutebrowser/issues/700#issuecomment-290780706[this
|
||||
comment for a basic example]), so what are you waiting for and why aren't you
|
||||
hacking qutebrowser yet?
|
||||
|
||||
How do I use Tor with qutebrowser?::
|
||||
Start tor on your machine, and do `:set content.proxy socks://localhost:9050/`
|
||||
Start tor on your machine, and do `:set network proxy socks://localhost:9050/`
|
||||
in qutebrowser. Note this won't give you the same amount of fingerprinting
|
||||
protection that the Tor Browser does, but it's useful to be able to access
|
||||
`.onion` sites.
|
||||
@@ -191,7 +163,7 @@ Why does J move to the next (right) tab, and K to the previous (left) one?::
|
||||
and qutebrowser's keybindings are designed to be compatible with dwb's.
|
||||
The rationale behind it is that J is "down" in vim, and K is "up", which
|
||||
corresponds nicely to "next"/"previous". It also makes much more sense with
|
||||
vertical tabs (e.g. `:set tabs.position left`).
|
||||
vertical tabs (e.g. `:set tabs position left`).
|
||||
|
||||
What's the difference between insert and passthrough mode?::
|
||||
They are quite similar, but insert mode has some bindings (like `Ctrl-e` to
|
||||
@@ -199,56 +171,15 @@ What's the difference between insert and passthrough mode?::
|
||||
be useful to rebind escape to something else in passthrough mode only, to be
|
||||
able to send an escape keypress to the website.
|
||||
|
||||
Why takes it longer to open an URL in qutebrowser than in chromium?::
|
||||
When opening an URL in an existing instance the normal qutebrowser
|
||||
Python script is started and a few PyQt libraries need to be
|
||||
loaded until it is detected that there is an instance running
|
||||
where the URL is then passed to. This takes some time.
|
||||
One workaround is to use this
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/scripts/open_url_in_instance.sh[script]
|
||||
and place it in your $PATH with the name "qutebrowser". This
|
||||
script passes the URL via an unix socket to qutebrowser (if its
|
||||
running already) using socat which is much faster and starts a new
|
||||
qutebrowser if it is not running already. Also check if you want
|
||||
to use webengine as backend in line 17 and change it to your
|
||||
needs.
|
||||
|
||||
How do I make qutebrowser use greasemonkey scripts?::
|
||||
There is currently no UI elements to handle managing greasemonkey scripts.
|
||||
All management of what scripts are installed or disabled is done in the
|
||||
filesystem by you. qutebrowser reads all files that have an extension of
|
||||
`.js` from the `<data>/greasemonkey/` folder and attempts to load them.
|
||||
Where `<data>` is the qutebrowser data directory shown in the `Paths`
|
||||
section of the page displayed by `:version`. If you want to disable a
|
||||
script just rename it, for example, to have `.disabled` on the end, after
|
||||
the `.js` extension. To reload scripts from that directory run the command
|
||||
`:greasemonkey-reload`.
|
||||
+
|
||||
Troubleshooting: to check that your script is being loaded when
|
||||
`:greasemonkey-reload` runs you can start qutebrowser with the arguments
|
||||
`--debug --logfilter greasemonkey,js` and check the messages on the
|
||||
program's standard output for errors parsing or loading your script.
|
||||
You may also see javascript errors if your script is expecting an environment
|
||||
that we fail to provide.
|
||||
+
|
||||
Note that there are some missing features which you may run into:
|
||||
|
||||
. Some scripts expect `GM_xmlhttpRequest` to ignore Cross Origin Resource
|
||||
Sharing restrictions, this is currently not supported, so scripts making
|
||||
requests to third party sites will often fail to function correctly.
|
||||
. If your backend is a QtWebEngine version 5.8, 5.9 or 5.10 then regular
|
||||
expressions are not supported in `@include` or `@exclude` rules. If your
|
||||
script uses them you can re-write them to use glob expressions or convert
|
||||
them to `@match` rules.
|
||||
See https://wiki.greasespot.net/Metadata_Block[the wiki] for more info.
|
||||
. Any greasemonkey API function to do with adding UI elements is not currently
|
||||
supported. That means context menu extentensions and background pages.
|
||||
|
||||
== Troubleshooting
|
||||
|
||||
Configuration not saved after modifying config.::
|
||||
When editing your config file manually, qutebrowser must be exited completely.
|
||||
This can be done by issuing the command `:quit` or by pressing `Ctrl+q`.
|
||||
|
||||
Unable to view flash content.::
|
||||
If you have flash installed for on your system, it's necessary to enable plugins
|
||||
to use the flash plugin. Using the command `:set content.plugins true`
|
||||
to use the flash plugin. Using the command `:set content allow-plugins true`
|
||||
in qutebrowser will enable plugins. Packages for flash should
|
||||
be provided for your platform or it can be obtained from
|
||||
http://get.adobe.com/flashplayer/[Adobe].
|
||||
@@ -260,45 +191,30 @@ Experiencing freezing on sites like duckduckgo and youtube.::
|
||||
See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357]
|
||||
for more details.
|
||||
|
||||
When using QtWebEngine, qutebrowser reports "Render Process Crashed" and the console prints a traceback on Gentoo Linux or another Source-Based Distro::
|
||||
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. +
|
||||
On gentoo, you just need to add it into your make.conf, like this: +
|
||||
|
||||
CFLAGS="... -fno-delete-null-pointer-checks"
|
||||
CXXFLAGS="... -fno-delete-null-pointer-checks"
|
||||
+
|
||||
And then re-emerging qtwebengine with: +
|
||||
|
||||
emerge -1 qtwebengine
|
||||
Experiencing segfaults (crashes) on Debian systems.::
|
||||
For Debian it's highly recommended to install the `gstreamer0.10-plugins-base` package.
|
||||
This is a workaround for a bug in Qt, it has been fixed upstream in Qt 5.4
|
||||
More details can be found
|
||||
https://bugs.webkit.org/show_bug.cgi?id=119951[here].
|
||||
|
||||
Unable to view DRM content (Netflix, Spotify, etc.).::
|
||||
You will need to install `widevine` and set `qt.args` to point to it.
|
||||
Qt 5.9 currently only supports widevine up to Chrome version 61.
|
||||
Segfaults on Facebook, Medium, Amazon, ...::
|
||||
If you are on a Debian or Ubuntu based system, you might experience some crashes
|
||||
visiting these sites. This is caused by various bugs in Qt which have been
|
||||
fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade
|
||||
some packages. On Debian Jessie, it's recommended to use the experimental
|
||||
repos as described in https://github.com/qutebrowser/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL].
|
||||
+
|
||||
On Arch, simply install `qt5-webengine-widevine` from the AUR and run:
|
||||
+
|
||||
----
|
||||
:set qt.args '["ppapi-widevine-path=/usr/lib/qt/plugins/ppapi/libwidevinecdmadapter.so"]'
|
||||
:restart
|
||||
----
|
||||
+
|
||||
For other distributions, download the chromium tarball and widevine-cdm zip from
|
||||
https://aur.archlinux.org/packages/qt5-webengine-widevine/[the AUR page],
|
||||
extract `libwidevinecdmadapter.so` and `libwidevinecdm.so` files, respectively,
|
||||
and move them to the `ppapi` plugin directory in your Qt library directory (create it if it does not exist).
|
||||
+
|
||||
Lastly, set your `qt.args` to point to that directory and restart qutebrowser:
|
||||
+
|
||||
----
|
||||
:set qt.args '["ppapi-widevine-path=/usr/lib64/qt5/plugins/ppapi/libwidevinecdmadapter.so"]'
|
||||
:restart
|
||||
----
|
||||
Since Ubuntu Trusty (using Qt 5.2.1),
|
||||
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.3.0%2C%20%225.3.0%20Alpha%22%2C%20%225.3.0%20Beta1%22%2C%20%225.3.0%20RC1%22%2C%205.3.1%2C%205.3.2%2C%205.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[over
|
||||
70 important bugs] have been fixed in QtWebKit. For Debian Jessie (using Qt 5.3.2)
|
||||
it's still
|
||||
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[nearly
|
||||
20 important bugs].
|
||||
|
||||
My issue is not listed.::
|
||||
If you experience any segfaults or crashes, you can report the issue in
|
||||
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
|
||||
using the `:report` command.
|
||||
If you are reporting a segfault, make sure you read the
|
||||
link:stacktrace.asciidoc[guide] on how to report them with all needed
|
||||
link:doc/stacktrace.asciidoc[guide] on how to report them with all needed
|
||||
information.
|
||||
385
INSTALL.asciidoc
Normal file
@@ -0,0 +1,385 @@
|
||||
Installing qutebrowser
|
||||
======================
|
||||
|
||||
toc::[]
|
||||
|
||||
On Debian / Ubuntu
|
||||
------------------
|
||||
|
||||
qutebrowser should run on these systems:
|
||||
|
||||
* Debian jessie or newer
|
||||
* Ubuntu Trusty (14.04 LTS) or newer
|
||||
* Any other distribution based on these (e.g. Linux Mint 17+)
|
||||
|
||||
Unfortunately there is no Debian package in the official repos yet, but installing qutebrowser is
|
||||
still relatively easy!
|
||||
|
||||
You can use packages that are built for every release or build it yourself from git.
|
||||
|
||||
On Ubuntu 16.04 and 16.10 it's recommended to <<tox,install qutebrowser via tox>>
|
||||
instead in order to be able to use the new QtWebEngine backend. Newer versions
|
||||
have a QtWebEngine package in the repositories.
|
||||
|
||||
Using the packages
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Install the dependencies via apt-get:
|
||||
|
||||
----
|
||||
# apt-get install python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-jinja2 python3-pygments python3-yaml
|
||||
----
|
||||
|
||||
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to use the
|
||||
newer QtWebEngine backend.
|
||||
|
||||
To do so, install `python3-pyqt5.qtwebengine` and `python3-pyqt5.qtopengl`, then
|
||||
start qutebrowser with `--backend webengine`.
|
||||
|
||||
Get the qutebrowser package from the
|
||||
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
|
||||
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
|
||||
|
||||
Install the packages:
|
||||
|
||||
----
|
||||
# dpkg -i python3-pypeg2_*_all.deb
|
||||
# dpkg -i qutebrowser_*_all.deb
|
||||
----
|
||||
|
||||
Build it from git
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Install the dependencies via apt-get:
|
||||
|
||||
----
|
||||
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python-tox python3-sip python3-dev
|
||||
----
|
||||
|
||||
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to install
|
||||
`python3-pyqt5.qtwebengine` and start qutebrowser with `--backend webengine` in
|
||||
order to use the new backend.
|
||||
|
||||
To generate the documentation for the `:help` command, when using the git
|
||||
repository (rather than a release):
|
||||
|
||||
----
|
||||
# apt-get install asciidoc source-highlight
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
If video or sound don't seem to work, try installing the gstreamer plugins:
|
||||
|
||||
----
|
||||
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||
----
|
||||
|
||||
Then <<tox,install qutebrowser via tox>>.
|
||||
|
||||
On Fedora
|
||||
---------
|
||||
|
||||
qutebrowser is available in the official repositories for Fedora 22 and newer.
|
||||
|
||||
----
|
||||
# dnf install qutebrowser
|
||||
----
|
||||
|
||||
It's also recommended to install `qt5-qtwebengine` and start with `--backend
|
||||
webengine` to use the new backend.
|
||||
|
||||
On Archlinux
|
||||
------------
|
||||
|
||||
qutebrowser is available in the official [community] repository.
|
||||
|
||||
----
|
||||
# pacman -S qutebrowser
|
||||
----
|
||||
|
||||
Archlinux packages an updated `qt5-webkit` package by default. If you want to
|
||||
use the QtWebEngine backend instead, install `qt5-webengine` and start with
|
||||
`--backend webengine`.
|
||||
|
||||
There is also a -git version available in the AUR:
|
||||
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
|
||||
|
||||
You can install it using `makepkg` like this:
|
||||
|
||||
----
|
||||
$ git clone https://aur.archlinux.org/qutebrowser-git.git
|
||||
$ cd qutebrowser-git
|
||||
$ makepkg -si
|
||||
$ cd ..
|
||||
$ rm -r qutebrowser-git
|
||||
----
|
||||
|
||||
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
|
||||
|
||||
If video or sound don't seem to work, try installing the gstreamer plugins:
|
||||
|
||||
----
|
||||
# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav
|
||||
----
|
||||
|
||||
On Gentoo
|
||||
---------
|
||||
|
||||
A version of qutebrowser is available in the main repository and can be installed with:
|
||||
|
||||
----
|
||||
# emerge -av qutebrowser
|
||||
----
|
||||
|
||||
However it is suggested to install the Live version (-9999) to take advantage
|
||||
of the newest features introduced.
|
||||
|
||||
First of all you need to edit your package.accept_keywords file to accept the live
|
||||
version:
|
||||
|
||||
----
|
||||
# nano /etc/portage/package.accept_keywords
|
||||
----
|
||||
|
||||
And add the following line to it:
|
||||
|
||||
=www-client/qutebrowser-9999 **
|
||||
|
||||
Save the file and then install qutebrowser via
|
||||
|
||||
----
|
||||
# emerge -av qutebrowser
|
||||
----
|
||||
|
||||
Or rebuild your system if you already installed it.
|
||||
|
||||
To update to the last Live version, remember to do
|
||||
|
||||
----
|
||||
# emerge -uDNav @live-rebuild @world
|
||||
----
|
||||
|
||||
To include qutebrowser among the updates.
|
||||
|
||||
Make sure you have `python3_4` in your `PYTHON_TARGETS`
|
||||
(`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
|
||||
necessary.
|
||||
|
||||
It's also recommended to install QtWebKit-NG via
|
||||
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild],
|
||||
or install Qt >= 5.7.1 with QtWebEngine in order to use an up-to-date backend.
|
||||
|
||||
If video or sound don't seem to work, try installing the gstreamer plugins:
|
||||
|
||||
----
|
||||
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
|
||||
----
|
||||
|
||||
|
||||
On Void Linux
|
||||
-------------
|
||||
|
||||
qutebrowser is available in the official repositories and can be installed
|
||||
with:
|
||||
|
||||
----
|
||||
# xbps-install qutebrowser
|
||||
----
|
||||
|
||||
It's currently recommended to install `python3-PyQt5-webengine` and
|
||||
`python3-PyQt5-opengl`, then start with `--backend webengine` to use the new
|
||||
backend.
|
||||
|
||||
On NixOS
|
||||
--------
|
||||
|
||||
Nixpkgs collection contains `pkgs.qutebrowser` since June 2015. You can install
|
||||
it with:
|
||||
|
||||
----
|
||||
$ nix-env -i qutebrowser
|
||||
----
|
||||
|
||||
It's recommended to install `qt5.qtwebengine` and start with
|
||||
`--backend webengine` to use the new backend.
|
||||
|
||||
On openSUSE
|
||||
-----------
|
||||
|
||||
There are prebuilt RPMs available for Tumbleweed and Leap 42.1:
|
||||
|
||||
http://software.opensuse.org/download.html?project=home%3Aarpraher&package=qutebrowser[One Click Install]
|
||||
|
||||
Or add the repo manually:
|
||||
|
||||
----
|
||||
# zypper addrepo http://download.opensuse.org/repositories/home:arpraher/openSUSE_Tumbleweed/home:arpraher.repo
|
||||
# zypper refresh
|
||||
# zypper install qutebrowser
|
||||
----
|
||||
|
||||
On OpenBSD
|
||||
----------
|
||||
|
||||
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports].
|
||||
|
||||
Install the package:
|
||||
|
||||
----
|
||||
# pkg_add qutebrowser
|
||||
----
|
||||
|
||||
Or alternatively, use the ports system :
|
||||
|
||||
----
|
||||
# cd /usr/ports/www/qutebrowser
|
||||
# make install
|
||||
----
|
||||
|
||||
On Windows
|
||||
----------
|
||||
|
||||
There are different ways to install qutebrowser on Windows:
|
||||
|
||||
Prebuilt binaries
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Prebuilt standalone packages and installers
|
||||
https://github.com/qutebrowser/qutebrowser/releases[are built] for every
|
||||
release.
|
||||
|
||||
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* PackageManagement PowerShell module
|
||||
----
|
||||
PS C:\> Install-Package qutebrowser
|
||||
----
|
||||
* Chocolatey's client
|
||||
----
|
||||
C:\> choco install qutebrowser
|
||||
----
|
||||
|
||||
Manual install
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* Use the installer from http://www.python.org/downloads[python.org] to get
|
||||
Python 3 (be sure to install pip).
|
||||
* Use the installer from
|
||||
http://www.riverbankcomputing.com/software/pyqt/download5[Riverbank computing]
|
||||
to get Qt and PyQt5.
|
||||
* Install https://testrun.org/tox/latest/index.html[tox] via
|
||||
https://pip.pypa.io/en/latest/[pip]:
|
||||
|
||||
----
|
||||
$ pip install tox
|
||||
----
|
||||
|
||||
Then <<tox,install qutebrowser via tox>>.
|
||||
|
||||
On OS X
|
||||
-------
|
||||
|
||||
Prebuilt binary
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The easiest way to install qutebrowser on OS X is to use the prebuilt `.app`
|
||||
files from the
|
||||
https://github.com/qutebrowser/qutebrowser/releases[release page].
|
||||
|
||||
This binary is also available through the
|
||||
https://caskroom.github.io/[Homebrew Cask] package manager:
|
||||
|
||||
----
|
||||
$ brew cask install qutebrowser
|
||||
----
|
||||
|
||||
Manual Install
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Alternatively, you can install the dependencies via a package manager (like
|
||||
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]) and run
|
||||
qutebrowser from source.
|
||||
|
||||
==== Homebrew
|
||||
|
||||
----
|
||||
$ brew install qt5
|
||||
$ pip3 install qutebrowser
|
||||
----
|
||||
|
||||
Homebrew's builds of Qt and PyQt no longer include QtWebKit - if you need
|
||||
QtWebKit support, it is necessary to build from source. The build takes several
|
||||
hours on an average laptop.
|
||||
|
||||
----
|
||||
$ brew install qt5 --with-qtwebkit
|
||||
$ brew install -s pyqt5
|
||||
$ pip3 install qutebrowser
|
||||
----
|
||||
|
||||
Packagers
|
||||
---------
|
||||
|
||||
There are example .desktop and icon files provided. They would go in the
|
||||
standard location for your distro (`/usr/share/applications` and
|
||||
`/usr/share/pixmaps` for example).
|
||||
|
||||
The normal `setup.py install` doesn't install these files, so you'll have to do
|
||||
it as part of the packaging process.
|
||||
|
||||
[[tox]]
|
||||
Installing qutebrowser with tox
|
||||
-------------------------------
|
||||
|
||||
First of all, clone the repository using http://git-scm.org/[git] and switch
|
||||
into the repository folder:
|
||||
|
||||
----
|
||||
$ git clone https://github.com/qutebrowser/qutebrowser.git
|
||||
$ cd qutebrowser
|
||||
----
|
||||
|
||||
|
||||
Then run tox inside the qutebrowser repository to set up a
|
||||
https://docs.python.org/3/library/venv.html[virtual environment]:
|
||||
|
||||
----
|
||||
$ tox -e mkvenv-pypi
|
||||
----
|
||||
|
||||
If your distribution uses OpenSSL 1.1 (like Debian Stretch or Archlinux), you'll
|
||||
need to set `LD_LIBRARY_PATH` to the OpenSSL 1.0 directory
|
||||
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
|
||||
qutebrowser.
|
||||
|
||||
Alternatively, you can use `tox -e mkvenv` (without `-pypi`) to symlink your
|
||||
local Qt install instead of installing PyQt in the virtualenv. However, unless
|
||||
you have QtWebKit-NG or QtWebEngine available, qutebrowser will use the legacy
|
||||
QtWebKit backend.
|
||||
|
||||
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
|
||||
Python3 is in your PATH before running tox.
|
||||
|
||||
This installs all needed Python dependencies in a `.venv` subfolder.
|
||||
|
||||
You can then create a simple wrapper script to start qutebrowser somewhere in
|
||||
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
|
||||
|
||||
----
|
||||
#!/bin/bash
|
||||
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser "$@"
|
||||
----
|
||||
|
||||
Updating
|
||||
~~~~~~~~
|
||||
|
||||
When you updated your local copy of the code (e.g. by pulling the git repo, or
|
||||
extracting a new version), the virtualenv should automatically use the updated
|
||||
code. However, if dependencies got added, this won't be reflected in the
|
||||
virtualenv. Thus it's recommended to run the following command to recreate the
|
||||
virtualenv:
|
||||
|
||||
----
|
||||
$ tox -r -e mkvenv-pypi
|
||||
----
|
||||
21
MANIFEST.in
@@ -1,5 +1,6 @@
|
||||
recursive-include qutebrowser *.py
|
||||
recursive-include qutebrowser/img *.svg *.png
|
||||
recursive-include qutebrowser/test *.py
|
||||
recursive-include qutebrowser/javascript *.js
|
||||
graft qutebrowser/html
|
||||
graft qutebrowser/3rdparty
|
||||
@@ -7,37 +8,37 @@ graft icons
|
||||
graft doc/img
|
||||
graft misc/apparmor
|
||||
graft misc/userscripts
|
||||
graft misc/requirements
|
||||
recursive-include scripts *.py *.sh *.js
|
||||
recursive-include scripts *.py
|
||||
include qutebrowser/utils/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include LICENSE doc/* README.asciidoc
|
||||
include misc/qutebrowser.desktop
|
||||
include misc/qutebrowser.appdata.xml
|
||||
include misc/Makefile
|
||||
include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc
|
||||
include qutebrowser.desktop
|
||||
include requirements.txt
|
||||
include tox.ini
|
||||
include qutebrowser.py
|
||||
include misc/cheatsheet.svg
|
||||
include qutebrowser/config/configdata.yml
|
||||
|
||||
prune www
|
||||
prune scripts/dev
|
||||
prune scripts/testbrowser/cpp
|
||||
prune scripts/testbrowser_cpp
|
||||
prune .github
|
||||
exclude scripts/asciidoc2html.py
|
||||
exclude doc/notes
|
||||
recursive-exclude doc *.asciidoc
|
||||
include doc/qutebrowser.1.asciidoc
|
||||
include doc/changelog.asciidoc
|
||||
prune tests
|
||||
prune qutebrowser/3rdparty
|
||||
prune misc/requirements
|
||||
prune misc/docker
|
||||
exclude pytest.ini
|
||||
exclude qutebrowser.rcc
|
||||
exclude qutebrowser/javascript/.eslintrc.yaml
|
||||
exclude qutebrowser/javascript/.eslintignore
|
||||
exclude doc/help
|
||||
exclude .*
|
||||
exclude codecov.yml
|
||||
exclude misc/appveyor_install.py
|
||||
exclude misc/qutebrowser.spec
|
||||
exclude misc/qutebrowser.nsi
|
||||
exclude misc/qutebrowser.rcc
|
||||
|
||||
global-exclude __pycache__ *.pyc *.pyo
|
||||
|
||||
321
README.asciidoc
@@ -9,11 +9,14 @@ qutebrowser
|
||||
// QUTE_WEB_HIDE
|
||||
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.*
|
||||
|
||||
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/COPYING"]
|
||||
image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"]
|
||||
image:https://requires.io/github/qutebrowser/qutebrowser/requirements.svg?branch=master["requirements badge",link="https://requires.io/github/qutebrowser/qutebrowser/requirements/?branch=master"]
|
||||
image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"]
|
||||
image:https://ci.appveyor.com/api/projects/status/5pyauww2k68bbow2/branch/master?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/qutebrowser/qutebrowser"]
|
||||
image:https://codecov.io/github/qutebrowser/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/qutebrowser/qutebrowser?branch=master"]
|
||||
|
||||
link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/faq.asciidoc[FAQ] | https://www.qutebrowser.org/doc/contributing.html[contributing] | link:https://github.com/qutebrowser/qutebrowser/releases[releases] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc[installing]
|
||||
link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | link:https://github.com/qutebrowser/qutebrowser/releases[releases]
|
||||
// QUTE_WEB_HIDE_END
|
||||
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
|
||||
@@ -33,8 +36,11 @@ Downloads
|
||||
---------
|
||||
|
||||
See the https://github.com/qutebrowser/qutebrowser/releases[github releases
|
||||
page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for
|
||||
detailed instructions on how to get qutebrowser running on various platforms.
|
||||
page] for available downloads (currently a source archive, and standalone
|
||||
packages as well as MSI installers for Windows).
|
||||
|
||||
See link:INSTALL.asciidoc[INSTALL] for detailed instructions on how to get
|
||||
qutebrowser running for various platforms.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
@@ -42,15 +48,14 @@ Documentation
|
||||
In addition to the topics mentioned in this README, the following documents are
|
||||
available:
|
||||
|
||||
* https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[Key binding cheatsheet]: +
|
||||
image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"]
|
||||
* A https://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]: +
|
||||
image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* link:doc/quickstart.asciidoc[Quick start guide]
|
||||
* https://www.shortcutfoo.com/app/dojos/qutebrowser[Free training course] to remember those key bindings
|
||||
* link:doc/faq.asciidoc[Frequently asked questions]
|
||||
* link:doc/help/configuring.asciidoc[Configuring qutebrowser]
|
||||
* link:doc/contributing.asciidoc[Contributing to qutebrowser]
|
||||
* link:doc/install.asciidoc[Installing qutebrowser]
|
||||
* link:doc/changelog.asciidoc[Change Log]
|
||||
* A https://www.shortcutfoo.com/app/dojos/qutebrowser[free training course] to remember those key bindings.
|
||||
* link:FAQ.asciidoc[Frequently asked questions]
|
||||
* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser]
|
||||
* link:INSTALL.asciidoc[INSTALL]
|
||||
* link:CHANGELOG.asciidoc[Change Log]
|
||||
* link:doc/stacktrace.asciidoc[Reporting segfaults]
|
||||
* link:doc/userscripts.asciidoc[How to write userscripts]
|
||||
|
||||
@@ -65,18 +70,15 @@ message to the
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||
mailto:qutebrowser@lists.qutebrowser.org[].
|
||||
|
||||
There's also an https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist]
|
||||
There's also a https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist]
|
||||
at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also
|
||||
get sent to the general qutebrowser@ list).
|
||||
|
||||
If you're a reddit user, there's a
|
||||
https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] subreddit there.
|
||||
|
||||
Contributions / Bugs
|
||||
--------------------
|
||||
|
||||
You want to contribute to qutebrowser? Awesome! Please read
|
||||
link:doc/contributing.asciidoc[the contribution guidelines] for details and
|
||||
link:CONTRIBUTING.asciidoc[the contribution guidelines] for details and
|
||||
useful hints.
|
||||
|
||||
If you found a bug or have a feature request, you can report it in several
|
||||
@@ -89,43 +91,36 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||
mailto:qutebrowser@lists.qutebrowser.org[].
|
||||
|
||||
For 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[0xFD55A072].
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
The following software and libraries are required to run qutebrowser:
|
||||
|
||||
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
|
||||
* http://qt.io/[Qt] 5.7.1 or newer (5.11.1 recommended) with the following modules:
|
||||
- QtCore / qtbase
|
||||
- QtQuick (part of qtbase in some distributions)
|
||||
- QtSQL (part of qtbase in some distributions)
|
||||
- QtOpenGL
|
||||
- QtWebEngine, or
|
||||
- QtWebKit - only the
|
||||
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
|
||||
supported
|
||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
||||
(5.11.2 recommended) for Python 3
|
||||
* http://www.python.org/[Python] 3.4 or newer (3.5 recommended)
|
||||
* http://qt.io/[Qt] 5.2.0 or newer (5.9 recommended)
|
||||
* QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG) or QtWebEngine
|
||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
|
||||
(5.9 recommended) for Python 3
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||
* http://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
* http://pygments.org/[pygments]
|
||||
* https://github.com/yaml/pyyaml[PyYAML]
|
||||
* http://www.attrs.org/[attrs]
|
||||
* http://pyyaml.org/wiki/PyYAML[PyYAML]
|
||||
|
||||
The following libraries are optional:
|
||||
The following libraries are optional and provide a better user experience:
|
||||
|
||||
* http://cthedot.de/cssutils/[cssutils] (for an improved `:download --mhtml`
|
||||
with QtWebKit).
|
||||
* On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log
|
||||
output.
|
||||
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
|
||||
command, when using the git repository (rather than a release).
|
||||
* http://cthedot.de/cssutils/[cssutils]
|
||||
|
||||
See link:doc/install.asciidoc[the documentation] for directions on how to
|
||||
install qutebrowser and its dependencies.
|
||||
To generate the documentation for the `:help` command, when using the git
|
||||
repository (rather than a release), http://asciidoc.org/[asciidoc] is needed.
|
||||
|
||||
On Windows, https://pypi.python.org/pypi/colorama/[colorama] is needed to
|
||||
display colored log output.
|
||||
|
||||
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
|
||||
and its dependencies.
|
||||
|
||||
Donating
|
||||
--------
|
||||
@@ -146,63 +141,219 @@ get in touch!
|
||||
Authors
|
||||
-------
|
||||
|
||||
qutebrowser's primary author is Florian Bruhin (The Compiler), but qutebrowser
|
||||
wouldn't be what it is without the help of
|
||||
https://github.com/qutebrowser/qutebrowser/graphs/contributors[hundreds of contributors]!
|
||||
Contributors, sorted by the number of commits in descending order:
|
||||
|
||||
Additionally, the following people have contributed graphics:
|
||||
// QUTE_AUTHORS_START
|
||||
* Florian Bruhin
|
||||
* Daniel Schadt
|
||||
* Ryan Roden-Corrent
|
||||
* Jan Verbeek
|
||||
* Jakub Klinkovský
|
||||
* Antoni Boucher
|
||||
* Lamar Pavel
|
||||
* Marshall Lochbaum
|
||||
* Bruno Oliveira
|
||||
* thuck
|
||||
* Martin Tournoij
|
||||
* Imran Sobir
|
||||
* Alexander Cogneau
|
||||
* Felix Van der Jeugt
|
||||
* Daniel Karbach
|
||||
* Kevin Velghe
|
||||
* Raphael Pierzina
|
||||
* Joel Torstensson
|
||||
* Patric Schmitz
|
||||
* Tarcisio Fedrizzi
|
||||
* Jay Kamat
|
||||
* Claude
|
||||
* Philipp Hansch
|
||||
* Fritz Reichwald
|
||||
* Corentin Julé
|
||||
* meles5
|
||||
* Panagiotis Ktistakis
|
||||
* Artur Shaik
|
||||
* Nathan Isom
|
||||
* Thorsten Wißmann
|
||||
* Austin Anderson
|
||||
* Jimmy
|
||||
* Niklas Haas
|
||||
* Maciej Wołczyk
|
||||
* Clayton Craft
|
||||
* sandrosc
|
||||
* Alexey "Averrin" Nabrodov
|
||||
* pkill9
|
||||
* nanjekyejoannah
|
||||
* avk
|
||||
* ZDarian
|
||||
* Milan Svoboda
|
||||
* John ShaggyTwoDope Jenkins
|
||||
* Peter Vilim
|
||||
* Jacob Sword
|
||||
* knaggita
|
||||
* Oliver Caldwell
|
||||
* Nikolay Amiantov
|
||||
* Marius
|
||||
* Julian Weigt
|
||||
* Tomasz Kramkowski
|
||||
* Sebastian Frysztak
|
||||
* Julie Engel
|
||||
* Jonas Schürmann
|
||||
* error800
|
||||
* Michael Hoang
|
||||
* Liam BEGUIN
|
||||
* Daniel Fiser
|
||||
* skinnay
|
||||
* Zach-Button
|
||||
* Samuel Walladge
|
||||
* Peter Rice
|
||||
* Ismail S
|
||||
* Halfwit
|
||||
* David Vogt
|
||||
* Claire Cavanaugh
|
||||
* rikn00
|
||||
* kanikaa1234
|
||||
* haitaka
|
||||
* Nick Ginther
|
||||
* Michał Góral
|
||||
* Michael Ilsaas
|
||||
* Martin Zimmermann
|
||||
* Link
|
||||
* Jussi Timperi
|
||||
* Cosmin Popescu
|
||||
* Brian Jackson
|
||||
* sbinix
|
||||
* rsteube
|
||||
* neeasade
|
||||
* jnphilipp
|
||||
* Yannis Rohloff
|
||||
* Tobias Patzl
|
||||
* Stefan Tatschner
|
||||
* Samuel Loury
|
||||
* Peter Michely
|
||||
* Panashe M. Fundira
|
||||
* Lucas Hoffmann
|
||||
* Larry Hynes
|
||||
* Kirill A. Shutemov
|
||||
* Johannes Altmanninger
|
||||
* Jeremy Kaplan
|
||||
* Ismail
|
||||
* Iordanis Grigoriou
|
||||
* Edgar Hipp
|
||||
* Daryl Finlay
|
||||
* arza
|
||||
* adam
|
||||
* Samir Benmendil
|
||||
* Regina Hug
|
||||
* Penaz
|
||||
* Matthias Lisin
|
||||
* Mathias Fussenegger
|
||||
* Marcelo Santos
|
||||
* Marcel Schilling
|
||||
* Joel Bradshaw
|
||||
* Jean-Louis Fuchs
|
||||
* Franz Fellner
|
||||
* Eric Drechsel
|
||||
* zwarag
|
||||
* xd1le
|
||||
* rmortens
|
||||
* oniondreams
|
||||
* issue
|
||||
* haxwithaxe
|
||||
* evan
|
||||
* dylan araps
|
||||
* caveman
|
||||
* addictedtoflames
|
||||
* Xitian9
|
||||
* Vasilij Schneidermann
|
||||
* Tomas Orsava
|
||||
* Tom Janson
|
||||
* Tobias Werth
|
||||
* Tim Harder
|
||||
* Thiago Barroso Perrotta
|
||||
* Steve Peak
|
||||
* Sorokin Alexei
|
||||
* Simon Désaulniers
|
||||
* Rok Mandeljc
|
||||
* Noah Huesser
|
||||
* Moez Bouhlel
|
||||
* MikeinRealLife
|
||||
* Lazlow Carmichael
|
||||
* Kevin Wang
|
||||
* Ján Kobezda
|
||||
* Justin Partain
|
||||
* Johannes Martinsson
|
||||
* Jean-Christophe Petkovich
|
||||
* Helen Sherwood-Taylor
|
||||
* HalosGhost
|
||||
* Gregor Pohl
|
||||
* Eivind Uggedal
|
||||
* Dietrich Daroch
|
||||
* Derek Sivers
|
||||
* Daniel Lu
|
||||
* Daniel Jakots
|
||||
* Arseniy Seroka
|
||||
* Anton Grensjö
|
||||
* Andy Balaam
|
||||
* Andreas Fischer
|
||||
* Amos Bird
|
||||
* Akselmo
|
||||
// QUTE_AUTHORS_END
|
||||
|
||||
The following people have contributed graphics:
|
||||
|
||||
* Jad/link:http://yelostudio.com[yelo] (new icon)
|
||||
* WOFall (original icon)
|
||||
* regines (key binding cheatsheet)
|
||||
|
||||
Also, thanks to everyone who contributed to one of qutebrowser's
|
||||
link:doc/backers.asciidoc[crowdfunding campaigns]!
|
||||
Thanks / Similar projects
|
||||
-------------------------
|
||||
|
||||
Similar projects
|
||||
----------------
|
||||
Many projects with a similar goal as qutebrowser exist:
|
||||
|
||||
* http://portix.bitbucket.org/dwb/[dwb] (C, GTK+ with WebKit1, currently
|
||||
http://www.reddit.com/r/linux/comments/2huqbc/dwb_abandoned/[unmaintained] -
|
||||
main inspiration for qutebrowser)
|
||||
* https://github.com/fanglingsu/vimb[vimb] (C, GTK+ with WebKit1, active)
|
||||
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
|
||||
WebKit1, dead)
|
||||
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1, active)
|
||||
* https://mason-larobina.github.io/luakit/[luakit] (C/Lua, GTK+ with
|
||||
WebKit1, not very active)
|
||||
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1, not very
|
||||
active)
|
||||
* http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2, active)
|
||||
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko,
|
||||
active)
|
||||
* https://github.com/AeroNotix/lispkit[lispkit] (quite new, lisp, GTK+ with
|
||||
WebKit, active)
|
||||
* http://www.vimperator.org/[Vimperator] (Firefox addon)
|
||||
* http://5digits.org/pentadactyl/[Pentadactyl] (Firefox addon)
|
||||
* https://github.com/akhodakivskiy/VimFx[VimFx] (Firefox addon)
|
||||
* https://github.com/1995eaton/chromium-vim[cVim] (Chrome/Chromium addon)
|
||||
* http://vimium.github.io/[vimium] (Chrome/Chromium addon)
|
||||
* https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome] (Chrome/Chromium addon)
|
||||
* https://github.com/jinzhu/vrome[Vrome] (Chrome/Chromium addon)
|
||||
|
||||
Many projects with a similar goal as qutebrowser exist.
|
||||
Most of them were inspirations for qutebrowser in some way, thanks for that!
|
||||
|
||||
Active
|
||||
~~~~~~
|
||||
Thanks as well to the following projects and people for helping me with
|
||||
problems and helpful hints:
|
||||
|
||||
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
|
||||
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
|
||||
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
|
||||
* http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
|
||||
* Chrome/Chromium addons:
|
||||
https://github.com/1995eaton/chromium-vim[cVim],
|
||||
http://vimium.github.io/[Vimium],
|
||||
https://github.com/brookhong/Surfingkeys[Surfingkeys],
|
||||
https://key.saka.io/[Saka Key]
|
||||
* Firefox addons (based on WebExtensions):
|
||||
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/shinglyu/QuantumVim[QuantumVim],
|
||||
https://github.com/cmcaine/tridactyl[Tridactyl] (working
|
||||
on a https://bugzilla.mozilla.org/show_bug.cgi?id=1215061[better API] for
|
||||
keyboard integration in Firefox).
|
||||
* http://eric-ide.python-projects.org/[eric5] / Detlev Offenbach
|
||||
* https://code.google.com/p/devicenzo/[devicenzo]
|
||||
* portix
|
||||
* seir
|
||||
* nitroxleecher
|
||||
|
||||
Inactive
|
||||
~~~~~~~~
|
||||
Also, thanks to:
|
||||
|
||||
* 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] -
|
||||
main inspiration for qutebrowser)
|
||||
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
|
||||
WebKit1)
|
||||
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1)
|
||||
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko)
|
||||
* Firefox addons (not based on WebExtensions or no recent activity):
|
||||
http://www.vimperator.org/[Vimperator],
|
||||
http://5digits.org/pentadactyl/[Pentadactyl],
|
||||
https://github.com/akhodakivskiy/VimFx[VimFx],
|
||||
* Chrome/Chromium addons:
|
||||
https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome],
|
||||
https://github.com/jinzhu/vrome[Vrome]
|
||||
* Everyone contributing to the link:doc/backers.asciidoc[crowdfunding].
|
||||
* Everyone who had the patience to test qutebrowser before v0.1.
|
||||
* Everyone triaging/fixing my bugs in the
|
||||
https://bugreports.qt.io/secure/Dashboard.jspa[Qt bugtracker]
|
||||
* Everyone answering my questions on http://stackoverflow.com/[Stack Overflow]
|
||||
and in IRC.
|
||||
* All the projects which were a great help while developing qutebrowser.
|
||||
|
||||
License
|
||||
-------
|
||||
@@ -218,7 +369,7 @@ 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 this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
pdf.js
|
||||
------
|
||||
|
||||
@@ -13,75 +13,47 @@ Thanks a lot to the following people who contributed to it:
|
||||
Gold sponsors
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
- Iggy
|
||||
- zwitschi
|
||||
- 2x Anonymous
|
||||
TODO
|
||||
|
||||
Silver sponsors
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
- https://benary.org[benaryorg]
|
||||
- https://scratchbook.ch[Claude]
|
||||
- Martin Tournoij
|
||||
- http://supported.elsensohn.ch[Thomas Elsensohn]
|
||||
- Christian Helbling
|
||||
- Gavin Troy
|
||||
- Chris King-Parra
|
||||
- Tim Das Mool Wegener
|
||||
TODO
|
||||
|
||||
Other sponsors
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
TODO: people with t-shirts or higher pledge levels
|
||||
|
||||
- 7scan
|
||||
- AMD1212
|
||||
- Alex
|
||||
- Alex Suykov
|
||||
- Alexey Zhikhartsev
|
||||
- Allan Nordhøy
|
||||
- Anirudh Sanjeev
|
||||
- Anssi Puustinen
|
||||
- Anton Grensjö
|
||||
- Aristaeus
|
||||
- Armin Fisslthaler
|
||||
- Ashley Hauck
|
||||
- Benedikt Steindorf
|
||||
- Bernardo Kuri
|
||||
- Blaise Duszynski
|
||||
- Bostan
|
||||
- Bruno Oliveira
|
||||
- BunnyApocalypse
|
||||
- Christian Kellermann
|
||||
- Colin Jacobs
|
||||
- Daniel Andersson
|
||||
- Daniel Nelson
|
||||
- Daniel P. Schmidt
|
||||
- Daniel Salby
|
||||
- Danilo
|
||||
- David Beley
|
||||
- David Hollings
|
||||
- David Keijser
|
||||
- David Parrish
|
||||
- Derin Yarsuvat
|
||||
- Dmytro Kostiuchenko
|
||||
- Eero Kari
|
||||
- Epictek
|
||||
- Eric
|
||||
- Faure Hu
|
||||
- Ferus
|
||||
- Frederik Thorøe
|
||||
- G4v4g4i
|
||||
- Granitosaurus
|
||||
- Gyula Teleki
|
||||
- H
|
||||
- Heinz Bruhin
|
||||
- Hosaka
|
||||
- Ihor Radchenko
|
||||
- Iordanis Grigoriou
|
||||
- Isaac Sandaljian
|
||||
- Jakub Podeszwik
|
||||
- Jamie Anderson
|
||||
- Jasper Woudenberg
|
||||
- Jay Kamat
|
||||
- Jens Højgaard
|
||||
- Johannes
|
||||
- John Baber-Lucero
|
||||
@@ -89,11 +61,9 @@ Other sponsors
|
||||
- Kenichiro Ito
|
||||
- Kenny Low
|
||||
- Lars Ivar Igesund
|
||||
- Leulas
|
||||
- Lucas Aride Moulin
|
||||
- Ludovic Chabant
|
||||
- Lukas Gierth
|
||||
- Magnus Lindström
|
||||
- Marulkan
|
||||
- Matthew Chun-Lum
|
||||
- Matthew Cronen
|
||||
@@ -110,10 +80,7 @@ Other sponsors
|
||||
- Peter Rice
|
||||
- Philipp Middendorf
|
||||
- Pkill9
|
||||
- PluMGMK
|
||||
- Prescott
|
||||
- ProXicT
|
||||
- Ram-Z
|
||||
- Robotichead
|
||||
- Roshless
|
||||
- Ryan Ellis
|
||||
@@ -123,53 +90,35 @@ Other sponsors
|
||||
- Sean Herman
|
||||
- Sebastian Frysztak
|
||||
- Shelby Cruver
|
||||
- Simon Désaulniers
|
||||
- SirCmpwn
|
||||
- Soham Pal
|
||||
- Stephan Jauernick
|
||||
- Stewart Webb
|
||||
- Sven Reinecke
|
||||
- Timothée Floure
|
||||
- Tom Bass
|
||||
- Tom Kirchner
|
||||
- Tomas Slusny
|
||||
- Tomasz Kramkowski
|
||||
- Tommy Thomas
|
||||
- Tuscan
|
||||
- Ulrich Pötter
|
||||
- Vasilij Schneidermann
|
||||
- Vlaaaaaaad
|
||||
- XTaran
|
||||
- Z2h-A6n
|
||||
- ayekat
|
||||
- beanieuptop
|
||||
- cee
|
||||
- craftyguy
|
||||
- demure
|
||||
- dlangevi
|
||||
- epon
|
||||
- evenorbert
|
||||
- fishss
|
||||
- gsnewmark
|
||||
- guillermohs9
|
||||
- hernani
|
||||
- hubcaps
|
||||
- jnphilipp
|
||||
- lobachevsky
|
||||
- neodarz
|
||||
- nihlaeth
|
||||
- notbenh
|
||||
- nyctea
|
||||
- ongy
|
||||
- patrick suwanvithaya
|
||||
- pyratebeard
|
||||
- p≡p foundation
|
||||
- randm_dave
|
||||
- sabreman
|
||||
- toml
|
||||
- vimja
|
||||
- wiz
|
||||
- 48 Anonymous
|
||||
- 43 Anonymous
|
||||
|
||||
2016
|
||||
----
|
||||
@@ -263,7 +212,6 @@ Other sponsors
|
||||
- Julie Engel
|
||||
- Jörg Behrmann
|
||||
- Jørgen Skancke
|
||||
- Kevin Kainan Li
|
||||
- Kevin Velghe
|
||||
- Konstantin Shmelkov
|
||||
- Kyle Frazer
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
Configuring qutebrowser
|
||||
=======================
|
||||
|
||||
IMPORTANT: qutebrowser's configuration system was completely rewritten in
|
||||
September 2017. This information is not applicable to older releases, and older
|
||||
information elsewhere might be outdated.
|
||||
|
||||
qutebrowser's config files
|
||||
--------------------------
|
||||
|
||||
qutebrowser releases before v1.0.0 had a `qutebrowser.conf` and `keys.conf`
|
||||
file. Those are not used anymore since that release - see
|
||||
<<migrating,"Migrating older configurations">> for information on how to
|
||||
migrate to the new config.
|
||||
|
||||
When using `:set` and `:bind`, changes are saved to an `autoconfig.yml` file
|
||||
automatically. If you don't want to have a config file which is curated by
|
||||
hand, you can simply use those - see
|
||||
<<autoconfig,"Configuring qutebrowser via the user interface">> for details.
|
||||
|
||||
For more advanced configuration, you can write a `config.py` file - see
|
||||
<<configpy,"Configuring qutebrowser via config.py">>. As soon as a `config.py`
|
||||
exists, the `autoconfig.yml` file **is not read anymore** by default. You need
|
||||
to <<configpy-autoconfig,load it by hand>> if you want settings done via
|
||||
`:set`/`:bind` to still persist.
|
||||
|
||||
[[autoconfig]]
|
||||
Configuring qutebrowser via the user interface
|
||||
----------------------------------------------
|
||||
|
||||
The easy (but less flexible) way to configure qutebrowser is using its user
|
||||
interface or command line. Changes you make this way are immediately active
|
||||
(with the exception of a few settings, where this is pointed out in the
|
||||
documentation) and are persisted in an `autoconfig.yml` file.
|
||||
|
||||
The `autoconfig.yml` file is located in the "config" folder listed on the
|
||||
link:qute://version[] page. On macOS, the "auto config" folder is used, which is
|
||||
different from where hand-written config files are kept.
|
||||
|
||||
However, **do not** edit `autoconfig.yml` by hand. Instead, see the next
|
||||
section.
|
||||
|
||||
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
|
||||
customizable.
|
||||
|
||||
Using the link:commands.html#set[`:set`] command and command completion, you
|
||||
can quickly set settings interactively, for example `:set tabs.position left`.
|
||||
|
||||
Some settings are also customizable for a given
|
||||
https://developer.chrome.com/apps/match_patterns[URL pattern] by doing e.g.
|
||||
`:set --pattern=*://example.com/ content.images false`.
|
||||
|
||||
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
|
||||
link:commands.html#unbind[`:unbind`] commands:
|
||||
|
||||
- Binding the key chain `,v` to the `:spawn mpv {url}` command:
|
||||
`:bind ,v spawn mpv {url}`
|
||||
- Unbinding the same key chain: `:unbind ,v`
|
||||
|
||||
Key chains starting with a comma are ideal for custom bindings, as the comma key
|
||||
will never be used in a default keybinding.
|
||||
|
||||
See the help pages linked above (or `:help :bind`, `:help :unbind`) for more
|
||||
information.
|
||||
|
||||
Other useful commands for config manipulation are
|
||||
link:commands.html#config-unset[`:config-unset`] to reset a value to its default,
|
||||
link:commands.html#config-clear[`:config-clear`] to reset the entire configuration,
|
||||
and link:commands.html#config-cycle[`:config-cycle`] to cycle a setting between
|
||||
different values.
|
||||
|
||||
[[configpy]]
|
||||
Configuring qutebrowser via config.py
|
||||
-------------------------------------
|
||||
|
||||
For more powerful configuration possibilities, you can create a `config.py`
|
||||
file. Since it's a Python file, you have much more flexibility for
|
||||
configuration. Note that qutebrowser will never touch this file - this means
|
||||
you'll be responsible for updating it when upgrading to a newer qutebrowser
|
||||
version.
|
||||
|
||||
You can run `:config-edit` inside qutebrowser to open the file in your editor,
|
||||
`:config-source` to reload the file (`:config-edit` does this automatically), or
|
||||
`:config-write-py --defaults` to write a template file to work with.
|
||||
|
||||
The file should be located in the "config" location listed on
|
||||
link:qute://version[], which is typically `~/.config/qutebrowser/config.py` on
|
||||
Linux, `~/.qutebrowser/config.py` on macOS, and
|
||||
`%APPDATA%/qutebrowser/config.py` on Windows.
|
||||
|
||||
Two global objects are pre-defined when running `config.py`: `c` and `config`.
|
||||
|
||||
Changing settings
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
While you can set settings using the `config.set()` method (which is explained
|
||||
in the next section), it's easier to use the `c` shorthand object to easily set
|
||||
settings like this:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
c.tabs.position = "left"
|
||||
c.completion.shrink = True
|
||||
----
|
||||
|
||||
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"`,
|
||||
you'll get an error when starting.
|
||||
|
||||
See the link:settings.html[settings help page] for all available settings. The
|
||||
accepted values depend on the type of the option. Commonly used are:
|
||||
|
||||
- Strings: `c.tabs.position = "left"`
|
||||
- Booleans: `c.completion.shrink = True`
|
||||
- Integers: `c.messages.timeout = 5000`
|
||||
- Dictionaries:
|
||||
* `c.headers.custom = {'X-Hello': 'World', 'X-Awesome': 'yes'}` to override
|
||||
any other values in the dictionary.
|
||||
* `c.aliases['foo'] = 'message-info foo'` to add a single value.
|
||||
- Lists:
|
||||
* `c.url.start_pages = ["https://www.qutebrowser.org/"]` to override any
|
||||
previous elements.
|
||||
* `c.url.start_pages.append("https://www.python.org/")` to add a new value.
|
||||
|
||||
Any other config types (e.g. a color) are specified as a string. The only
|
||||
exception is the `Regex` type, which can take either a string (with an `r`
|
||||
prefix to preserve backslashes) or a Python regex object:
|
||||
|
||||
- `c.hints.next_regexes.append(r'\bvor\b')`
|
||||
- `c.hints.prev_regexes.append(re.compile(r'\bzurück\b'))`
|
||||
|
||||
If you want to read a setting, you can use the `c` object to do so as well:
|
||||
`c.colors.tabs.even.bg = c.colors.tabs.odd.bg`.
|
||||
|
||||
Using strings for setting names
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to set settings based on their name as a string, use the
|
||||
`config.set` method:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
# Equivalent to:
|
||||
# c.content.javascript.enabled = False
|
||||
config.set('content.javascript.enabled', False)
|
||||
----
|
||||
|
||||
To read a setting, use the `config.get` method:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
# Equivalent to:
|
||||
# color = c.colors.completion.fg
|
||||
color = config.get('colors.completion.fg')
|
||||
----
|
||||
|
||||
Per-domain settings
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using `config.set`, some settings are also customizable for a given
|
||||
https://developer.chrome.com/apps/match_patterns[URL pattern]:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.set('content.images', False, '*://example.com/')
|
||||
----
|
||||
|
||||
Alternatively, you can use `with config.pattern(...) as p:` to get a shortcut
|
||||
similar to `c.` which is scoped to the given domain:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
with config.pattern('*://example.com/') as p:
|
||||
p.content.images = False
|
||||
----
|
||||
|
||||
Binding keys
|
||||
~~~~~~~~~~~~
|
||||
|
||||
While it's possible to change the `bindings.commands` setting to bind keys, it's
|
||||
preferred to use the `config.bind` command. Doing so ensures the commands are
|
||||
valid and normalizes different expressions which map to the same key.
|
||||
|
||||
For details on how to specify keys and the available modes, see the
|
||||
link:settings.html#bindings.commands[documentation] for the `bindings.commands`
|
||||
setting.
|
||||
|
||||
To bind a key:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
config.bind('<Ctrl-v>', 'spawn mpv {url}')
|
||||
----
|
||||
|
||||
To bind a key in a mode other than `'normal'`, add a `mode` argument:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.bind('<Ctrl-y>', 'prompt-yes', mode='prompt')
|
||||
----
|
||||
|
||||
To unbind a key (either a key which has been bound before, or a default binding):
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.unbind('<Ctrl-v>', mode='normal')
|
||||
----
|
||||
|
||||
To bind keys without modifiers, specify a key chain to bind as a string. Key
|
||||
chains starting with a comma are ideal for custom bindings, as the comma key
|
||||
will never be used in a default keybinding.
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.bind(',v', 'spawn mpv {url}')
|
||||
----
|
||||
|
||||
To suppress loading of any default keybindings, you can set
|
||||
`c.bindings.default = {}`.
|
||||
|
||||
[[configpy-autoconfig]]
|
||||
Loading `autoconfig.yml`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All customization done via the UI (`:set`, `:bind` and `:unbind`) is
|
||||
stored in the `autoconfig.yml` file, which is not loaded automatically as soon
|
||||
as a `config.py` exists. If you want those settings to be loaded, you'll need to
|
||||
explicitly load the `autoconfig.yml` file in your `config.py` by doing:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
config.load_autoconfig()
|
||||
----
|
||||
|
||||
If you do so at the top of your file, your `config.py` settings will take
|
||||
precedence as they overwrite the settings done in `autoconfig.yml`.
|
||||
|
||||
Importing other modules
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can import any module from the
|
||||
https://docs.python.org/3/library/index.html[Python standard library] (e.g.
|
||||
`import os.path`), as well as any module installed in the environment
|
||||
qutebrowser is run with.
|
||||
|
||||
If you have an `utils.py` file in your qutebrowser config folder, you can import
|
||||
that via `import utils` as well.
|
||||
|
||||
While it's in some cases possible to import code from the qutebrowser
|
||||
installation, doing so is unsupported and discouraged.
|
||||
|
||||
To read config data from a different file with `c` and `config` available, you
|
||||
can use `config.source('otherfile.py')` in your `config.py`.
|
||||
|
||||
Getting the config directory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you need to get the qutebrowser config directory, you can do so by reading
|
||||
`config.configdir`. Similarly, you can get the qutebrowser data directory via
|
||||
`config.datadir`.
|
||||
|
||||
This gives you a https://docs.python.org/3/library/pathlib.html[`pathlib.Path`
|
||||
object], on which you can use `/` to add more directory parts, or `str(...)` to
|
||||
get a string:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
print(str(config.configdir / 'config.py'))
|
||||
----
|
||||
|
||||
Handling errors
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
If there are errors in your `config.py`, qutebrowser will try to apply as much
|
||||
of it as possible, and show an error dialog before starting.
|
||||
|
||||
qutebrowser tries to display errors which are easy to understand even for people
|
||||
who are not used to writing Python. If you see a config error which you find
|
||||
confusing or you think qutebrowser could handle better, please
|
||||
https://github.com/qutebrowser/qutebrowser/issues[open an issue]!
|
||||
|
||||
Recipes
|
||||
~~~~~~~
|
||||
|
||||
Reading a YAML file
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To read a YAML config like this:
|
||||
|
||||
.config.yml:
|
||||
----
|
||||
tabs.position: left
|
||||
tabs.show: switching
|
||||
----
|
||||
|
||||
You can use:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
import yaml
|
||||
|
||||
with (config.configdir / 'config.yml').open() as f:
|
||||
yaml_data = yaml.load(f)
|
||||
|
||||
for k, v in yaml_data.items():
|
||||
config.set(k, v)
|
||||
----
|
||||
|
||||
Reading a nested YAML file
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To read a YAML file with nested values like this:
|
||||
|
||||
.colors.yml:
|
||||
----
|
||||
colors:
|
||||
statusbar:
|
||||
normal:
|
||||
bg: lime
|
||||
fg: black
|
||||
url:
|
||||
fg: red
|
||||
----
|
||||
|
||||
You can use:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
import yaml
|
||||
|
||||
with (config.configdir / 'colors.yml').open() as f:
|
||||
yaml_data = yaml.load(f)
|
||||
|
||||
def dict_attrs(obj, path=''):
|
||||
if isinstance(obj, dict):
|
||||
for k, v in obj.items():
|
||||
yield from dict_attrs(v, '{}.{}'.format(path, k) if path else k)
|
||||
else:
|
||||
yield path, obj
|
||||
|
||||
for k, v in dict_attrs(yaml_data):
|
||||
config.set(k, v)
|
||||
----
|
||||
|
||||
Note that this won't work for values which are dictionaries.
|
||||
|
||||
Binding chained commands
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you have a lot of chained commands you want to bind, you can write a helper
|
||||
to do so:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
def bind_chained(key, *commands):
|
||||
config.bind(key, ' ;; '.join(commands))
|
||||
|
||||
bind_chained('<Escape>', 'clear-keychain', 'search')
|
||||
----
|
||||
|
||||
Reading colors from Xresources
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can use something like this to read colors from an `~/.Xresources` file:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
import subprocess
|
||||
|
||||
def read_xresources(prefix):
|
||||
props = {}
|
||||
x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE)
|
||||
lines = x.stdout.decode().split('\n')
|
||||
for line in filter(lambda l : l.startswith(prefix), lines):
|
||||
prop, _, value = line.partition(':\t')
|
||||
props[prop] = value
|
||||
return props
|
||||
|
||||
xresources = read_xresources('*')
|
||||
c.colors.statusbar.normal.bg = xresources['*.background']
|
||||
----
|
||||
|
||||
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].
|
||||
- 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]
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you use an editor with flake8 and pylint integration, it may have some
|
||||
complaints about invalid names, undefined variables, or missing docstrings.
|
||||
You can silence those with:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
# pylint: disable=C0111
|
||||
c = c # noqa: F821 pylint: disable=E0602,C0103
|
||||
config = config # noqa: F821 pylint: disable=E0602,C0103
|
||||
----
|
||||
|
||||
For type annotation support (note that those imports aren't guaranteed to be
|
||||
stable across qutebrowser versions):
|
||||
|
||||
[source,python]
|
||||
----
|
||||
# pylint: disable=C0111
|
||||
from qutebrowser.config.configfiles import ConfigAPI # noqa: F401
|
||||
from qutebrowser.config.config import ConfigContainer # noqa: F401
|
||||
config = config # type: ConfigAPI # noqa: F821 pylint: disable=E0602,C0103
|
||||
c = c # type: ConfigContainer # noqa: F821 pylint: disable=E0602,C0103
|
||||
----
|
||||
|
||||
[[migrating]]
|
||||
Migrating older configurations
|
||||
------------------------------
|
||||
|
||||
qutebrowser does no automatic migration for the new configuration. However,
|
||||
there's a special link:qute://configdiff/old[configdiff] page
|
||||
(`qute://configdiff/old`) in qutebrowser, which will show you the changes you
|
||||
did in your old configuration, compared to the old defaults.
|
||||
|
||||
Other changes in default settings:
|
||||
|
||||
- In v1.1.x and newer, `<Up>` and `<Down>` navigate through command history
|
||||
if no text was entered yet.
|
||||
With v1.0.x, they always navigate through command history instead of selecting
|
||||
completion items. Use `<Tab>`/`<Shift-Tab>` to cycle through the completion
|
||||
instead.
|
||||
You can get back the old behavior by doing:
|
||||
+
|
||||
----
|
||||
:bind -m command <Up> completion-item-focus prev
|
||||
:bind -m command <Down> completion-item-focus next
|
||||
----
|
||||
+
|
||||
or always navigate through command history with
|
||||
+
|
||||
----
|
||||
:bind -m command <Up> command-history-prev
|
||||
:bind -m command <Down> command-history-next
|
||||
----
|
||||
|
||||
- The default for `completion.web_history_max_items` is now set to `-1`, showing
|
||||
an unlimited number of items in the completion for `:open` as the new
|
||||
sqlite-based completion is much faster. If the `:open` completion is too slow
|
||||
on your machine, set an appropriate limit again.
|
||||
@@ -7,13 +7,12 @@ Documentation
|
||||
The following help pages are currently available:
|
||||
|
||||
* link:../quickstart.html[Quick start guide]
|
||||
* link:../faq.html[Frequently asked questions]
|
||||
* link:../changelog.html[Change Log]
|
||||
* link:../../FAQ.html[Frequently asked questions]
|
||||
* link:../../CHANGELOG.html[Change Log]
|
||||
* link:commands.html[Documentation of commands]
|
||||
* link:configuring.html[Configuring qutebrowser]
|
||||
* link:settings.html[Documentation of settings]
|
||||
* link:../userscripts.html[How to write userscripts]
|
||||
* link:../contributing.html[Contributing to qutebrowser]
|
||||
* link:../../CONTRIBUTING.html[Contributing to qutebrowser]
|
||||
|
||||
Getting help
|
||||
------------
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 989 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 43 KiB |
@@ -1,471 +0,0 @@
|
||||
Installing qutebrowser
|
||||
======================
|
||||
|
||||
toc::[]
|
||||
|
||||
NOTE: qutebrowser recently had some bigger dependency changes for v1.0.0, which
|
||||
means those instructions might be out of date in some places.
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[Please help]
|
||||
updating them if you notice something being broken!
|
||||
|
||||
On Debian / Ubuntu
|
||||
------------------
|
||||
|
||||
How to install qutebrowser depends a lot on the version of Debian/Ubuntu you're
|
||||
running.
|
||||
|
||||
Debian Jessie / Ubuntu 14.04 LTS / Linux Mint < 18
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Those distributions only have Python 3.4 and a too old Qt version available,
|
||||
while qutebrowser requires Python 3.5 and Qt 5.7.1 or newer.
|
||||
|
||||
It should be possible to install Python 3.5 e.g. from the
|
||||
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or via
|
||||
https://github.com/pyenv/pyenv[pyenv], but nobody tried that yet.
|
||||
|
||||
If you get qutebrowser running on those distributions, please
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[contribute]
|
||||
to update this documentation!
|
||||
|
||||
Ubuntu 16.04 LTS / Linux Mint 18
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
|
||||
QtWebEngine). However, it comes with Python 3.5, so you can
|
||||
<<tox,install qutebrowser via tox>>.
|
||||
|
||||
You'll need some basic libraries to use the tox-installed PyQt:
|
||||
|
||||
----
|
||||
# apt install libglib2.0-0 libgl1 libfontconfig1 libx11-xcb1 libxi6 libxrender1 libdbus-1-3
|
||||
----
|
||||
|
||||
Debian Stretch / Ubuntu 17.04 and 17.10
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Those versions come with QtWebEngine in the repositories. This makes it possible
|
||||
to install qutebrowser via the Debian package.
|
||||
|
||||
You'll need to download three packages:
|
||||
|
||||
- https://packages.debian.org/sid/all/python3-pypeg2/download[PyPEG2] (a library
|
||||
used by qutebrowser which is not in the earlier repositories)
|
||||
- https://packages.debian.org/sid/all/qutebrowser/download[qutebrowser] itself
|
||||
- Either https://packages.debian.org/sid/all/qutebrowser-qtwebengine/download[qutebrowser-qtwebengine]
|
||||
or https://packages.debian.org/sid/all/qutebrowser-qtwebkit/download[qutebrowser-qtwebkit]
|
||||
(or both) depending on the backend you want to use. QtWebEngine is the
|
||||
default/recommended choice.
|
||||
|
||||
After downloading, install the packages (make sure to install all the
|
||||
downloaded qutebrowser deb files in one apt command):
|
||||
|
||||
----
|
||||
# apt install ./python3-pypeg2_*_all.deb
|
||||
# apt install ./qutebrowser*.deb
|
||||
----
|
||||
|
||||
For an update after the initial install, you only need to download/install the
|
||||
qutebrowser package.
|
||||
|
||||
Debian Testing / Ubuntu 18.04
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
On Debian Testing, qutebrowser is in the official repositories, and you can
|
||||
install it with apt:
|
||||
|
||||
----
|
||||
# apt install qutebrowser
|
||||
----
|
||||
|
||||
Additional hints
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
- Alternatively, you can <<tox,install qutebrowser via tox>> to get a newer
|
||||
QtWebEngine version.
|
||||
- If running from git, run the following to generate the documentation for the
|
||||
`:help` command:
|
||||
+
|
||||
----
|
||||
# apt install --no-install-recommends asciidoc source-highlight
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
- If you prefer using QtWebKit, there's an up-to-date version available in
|
||||
https://packages.debian.org/buster/libqt5webkit5[Debian Testing].
|
||||
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||
+
|
||||
----
|
||||
# apt install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||
----
|
||||
|
||||
On Fedora
|
||||
---------
|
||||
|
||||
NOTE: Fedora's packages used to be outdated for a long time, but are
|
||||
now (November 2017) maintained and up-to-date again.
|
||||
|
||||
qutebrowser is available in the official repositories:
|
||||
|
||||
-----
|
||||
# dnf install qutebrowser
|
||||
-----
|
||||
|
||||
However, note that Fedora 25/26 won't be updated to qutebrowser v1.0, so you
|
||||
might want to <<tox,install qutebrowser via tox>> instead there.
|
||||
|
||||
Additional hints
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Fedora only ships free software in the repositories.
|
||||
To be able to play videos with proprietary codecs with QtWebEngine, you will
|
||||
need to install an additional package from the RPM Fusion Free repository.
|
||||
For more information see https://rpmfusion.org/Configuration.
|
||||
|
||||
-----
|
||||
# dnf install qt5-qtwebengine-freeworld
|
||||
-----
|
||||
|
||||
On Archlinux
|
||||
------------
|
||||
|
||||
qutebrowser is available in the official [community] repository.
|
||||
|
||||
----
|
||||
# pacman -S qutebrowser
|
||||
----
|
||||
|
||||
There is also a -git version available in the AUR:
|
||||
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
|
||||
|
||||
You can install it using `makepkg` like this:
|
||||
|
||||
----
|
||||
$ git clone https://aur.archlinux.org/qutebrowser-git.git
|
||||
$ cd qutebrowser-git
|
||||
$ makepkg -si
|
||||
$ cd ..
|
||||
$ rm -r qutebrowser-git
|
||||
----
|
||||
|
||||
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
|
||||
|
||||
If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||
|
||||
----
|
||||
# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav
|
||||
----
|
||||
|
||||
On Gentoo
|
||||
---------
|
||||
|
||||
NOTE: Gentoo's packages used to be severely outdated for a long time, but are
|
||||
now (October 2017) maintained and up-to-date again.
|
||||
|
||||
qutebrowser is available in the main repository and can be installed with:
|
||||
|
||||
----
|
||||
# emerge -av qutebrowser
|
||||
----
|
||||
|
||||
To use QtWebKit instead of QtWebEngine, you'll need a newer QtWebKit using
|
||||
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild].
|
||||
|
||||
If video or sound don't work with QtWebKit, try installing the gstreamer
|
||||
plugins:
|
||||
|
||||
----
|
||||
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
|
||||
----
|
||||
|
||||
To be able to play videos with proprietary codecs with QtWebEngine, you will
|
||||
need to turn off the `bindist` flag for `dev-qt/qtwebengine`.
|
||||
|
||||
See the https://wiki.gentoo.org/wiki/Qutebrowser#USE_flags[Gentoo Wiki] for
|
||||
more information.
|
||||
|
||||
On Void Linux
|
||||
-------------
|
||||
|
||||
qutebrowser is available in the official repositories and can be installed
|
||||
with:
|
||||
|
||||
----
|
||||
# xbps-install qutebrowser
|
||||
----
|
||||
|
||||
It's currently recommended to install `python3-PyQt5-webengine` and
|
||||
`python3-PyQt5-opengl`, then start with `--backend webengine` to use the new
|
||||
backend.
|
||||
|
||||
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
|
||||
|
||||
On NixOS
|
||||
--------
|
||||
|
||||
Nixpkgs collection contains `pkgs.qutebrowser` since June 2015. You can install
|
||||
it with:
|
||||
|
||||
----
|
||||
$ nix-env -i qutebrowser
|
||||
----
|
||||
|
||||
It's recommended to install `qt5.qtwebengine` and start with
|
||||
`--backend webengine` to use the new backend.
|
||||
|
||||
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
|
||||
|
||||
On openSUSE
|
||||
-----------
|
||||
|
||||
There are prebuilt RPMs available at https://software.opensuse.org/download.html?project=network&package=qutebrowser[OBS].
|
||||
|
||||
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
|
||||
|
||||
On OpenBSD
|
||||
----------
|
||||
|
||||
WARNING: OpenBSD only packages a legacy unmaintained version of QtWebKit (for
|
||||
which support was dropped in qutebrowser v1.0). It's advised to not use
|
||||
qutebrowser from OpenBSD ports for untrusted websites.
|
||||
|
||||
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports].
|
||||
|
||||
Install the package:
|
||||
|
||||
----
|
||||
# pkg_add qutebrowser
|
||||
----
|
||||
|
||||
Or alternatively, use the ports system :
|
||||
|
||||
----
|
||||
# cd /usr/ports/www/qutebrowser
|
||||
# make install
|
||||
----
|
||||
|
||||
On FreeBSD
|
||||
----------
|
||||
|
||||
qutebrowser is in https://www.freshports.org/www/qutebrowser/[FreeBSD ports].
|
||||
|
||||
It can be installed with:
|
||||
|
||||
----
|
||||
# cd /usr/ports/www/qutebrowser
|
||||
# make install clean
|
||||
----
|
||||
|
||||
At present, precompiled packages are not available for this port,
|
||||
and QtWebEngine backend is also not available.
|
||||
|
||||
On Windows
|
||||
----------
|
||||
|
||||
There are different ways to install qutebrowser on Windows:
|
||||
|
||||
Prebuilt binaries
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Prebuilt standalone packages and installers
|
||||
https://github.com/qutebrowser/qutebrowser/releases[are built] for every
|
||||
release.
|
||||
|
||||
Note that you'll need to upgrade to new versions manually (subscribe to the
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce
|
||||
mailinglist] to get notified on new releases). You can install a newer version
|
||||
without uninstalling the older one.
|
||||
|
||||
The binary release ships with a QtWebEngine built without proprietary codec
|
||||
support. To get support for e.g. h264/h265 videos, you'll need to build
|
||||
QtWebEngine from source yourself with support for that enabled.
|
||||
|
||||
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* PackageManagement PowerShell module
|
||||
----
|
||||
PS C:\> Install-Package qutebrowser
|
||||
----
|
||||
* Chocolatey's client
|
||||
----
|
||||
C:\> choco install qutebrowser
|
||||
----
|
||||
* Scoop's client
|
||||
----
|
||||
C:\> scoop bucket add extras
|
||||
C:\> scoop install qutebrowser
|
||||
----
|
||||
|
||||
Manual install
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
* Use the installer from http://www.python.org/downloads[python.org] to get
|
||||
Python 3 (be sure to install pip).
|
||||
* Install https://testrun.org/tox/latest/index.html[tox] via
|
||||
https://pip.pypa.io/en/latest/[pip]:
|
||||
|
||||
----
|
||||
$ pip install tox
|
||||
----
|
||||
|
||||
Then <<tox,install qutebrowser via tox>>.
|
||||
|
||||
On macOS
|
||||
--------
|
||||
|
||||
Prebuilt binary
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The easiest way to install qutebrowser on macOS is to use the prebuilt `.app`
|
||||
files from the
|
||||
https://github.com/qutebrowser/qutebrowser/releases[release page].
|
||||
|
||||
Note that you'll need to upgrade to new versions manually (subscribe to the
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce
|
||||
mailinglist] to get notified on new releases).
|
||||
|
||||
The binary release ships with a QtWebEngine built without proprietary codec
|
||||
support. To get support for e.g. h264/h265 videos, you'll need to build
|
||||
QtWebEngine from source yourself with support for that enabled.
|
||||
|
||||
This binary is also available through the
|
||||
https://caskroom.github.io/[Homebrew Cask] package manager:
|
||||
|
||||
----
|
||||
$ brew cask install qutebrowser
|
||||
----
|
||||
|
||||
Manual Install
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Alternatively, you can install the dependencies via a package manager (like
|
||||
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]) and run
|
||||
qutebrowser from source.
|
||||
|
||||
==== Homebrew
|
||||
|
||||
----
|
||||
$ brew install qt5
|
||||
$ pip3 install qutebrowser
|
||||
----
|
||||
|
||||
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
|
||||
|
||||
Homebrew's builds of Qt and PyQt don't come with QtWebKit (and `--with-qtwebkit`
|
||||
uses an old version of QtWebKit which qutebrowser doesn't support anymore). If
|
||||
you want QtWebKit support, you'll need to build an up-to-date QtWebKit
|
||||
https://github.com/annulen/webkit/wiki/Building-QtWebKit-on-OS-X[manually].
|
||||
|
||||
Packagers
|
||||
---------
|
||||
|
||||
There are example .desktop and icon files provided. They would go in the
|
||||
standard location for your distro (`/usr/share/applications` and
|
||||
`/usr/share/pixmaps` for example).
|
||||
|
||||
The normal `setup.py install` doesn't install these files, so you'll have to do
|
||||
it as part of the packaging process.
|
||||
|
||||
[[tox]]
|
||||
Installing qutebrowser with tox
|
||||
-------------------------------
|
||||
|
||||
Getting the repository
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
First of all, clone the repository using http://git-scm.org/[git] and switch
|
||||
into the repository folder:
|
||||
|
||||
----
|
||||
$ git clone https://github.com/qutebrowser/qutebrowser.git
|
||||
$ cd qutebrowser
|
||||
----
|
||||
|
||||
Installing dependencies (including Qt)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Then run tox inside the qutebrowser repository to set up a
|
||||
https://docs.python.org/3/library/venv.html[virtual environment]:
|
||||
|
||||
----
|
||||
$ tox -e mkvenv-pypi
|
||||
----
|
||||
|
||||
If your system comes with Python 3.5.3 or older (such as Ubuntu 16.04 LTS), use
|
||||
`tox -e mkvenv-pypi-old` instead. This installs an older Qt version (5.10) due
|
||||
to bugs in newer versions.
|
||||
|
||||
This installs all needed Python dependencies in a `.venv` subfolder.
|
||||
|
||||
This comes with an up-to-date Qt/PyQt including QtWebEngine, but has a few
|
||||
caveats:
|
||||
|
||||
- Make sure your `python3` is Python 3.5 or newer, otherwise you'll get a "No
|
||||
matching distribution found" error. Note that qutebrowser itself also requires
|
||||
this.
|
||||
- It only works on 64-bit x86 systems, with other architectures you'll get the
|
||||
same error.
|
||||
- If your distribution uses OpenSSL 1.1 (like Debian Stretch or Archlinux),
|
||||
you'll need to set `LD_LIBRARY_PATH` to the OpenSSL 1.0 directory
|
||||
(`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
|
||||
`:adblock-update` or `:download`).
|
||||
- It comes with a QtWebEngine compiled without proprietary codec support (such
|
||||
as h.264).
|
||||
|
||||
See the next section for an alternative.
|
||||
|
||||
Installing dependencies (system-wide Qt)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Alternatively, you can use `tox -e mkvenv` (without `-pypi`) to symlink your
|
||||
local Qt install instead of installing PyQt in the virtualenv. However, unless
|
||||
you have a new QtWebKit or QtWebEngine available, qutebrowser will not work. It
|
||||
also typically means you'll be using an older release of QtWebEngine.
|
||||
|
||||
On Windows, run `set PYTHON=C:\path\to\python.exe` (CMD) or ``$Env:PYTHON =
|
||||
"..."` (Powershell) first.
|
||||
|
||||
Creating a wrapper script
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Running `tox` does not install a system-wide `qutebrowser` script. You can
|
||||
launch qutebrowser by doing:
|
||||
|
||||
----
|
||||
.venv/bin/python3 -m qutebrowser
|
||||
----
|
||||
|
||||
You can create a simple wrapper script to start qutebrowser somewhere in your
|
||||
`$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
|
||||
|
||||
----
|
||||
#!/bin/bash
|
||||
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser "$@"
|
||||
----
|
||||
|
||||
Building the docs
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
To build the documentation, install `asciidoc` (note that LaTeX which comes as
|
||||
optional/recommended dependency with some distributions is not required).
|
||||
|
||||
Then, run:
|
||||
|
||||
----
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
Updating
|
||||
~~~~~~~~
|
||||
|
||||
When you updated your local copy of the code (e.g. by pulling the git repo, or
|
||||
extracting a new version), the virtualenv should automatically use the updated
|
||||
code. However, if dependencies got added, this won't be reflected in the
|
||||
virtualenv. Thus it's recommended to run the following command to recreate the
|
||||
virtualenv:
|
||||
|
||||
----
|
||||
$ tox -r -e mkvenv-pypi
|
||||
----
|
||||
196
doc/notes
Normal file
@@ -0,0 +1,196 @@
|
||||
henk's thoughts
|
||||
===============
|
||||
|
||||
1. Power to the user! Protect privacy!
|
||||
Things the browser should only do with explicit consent from the user, if
|
||||
applicable the user should be able to choose which protocol/host/port triplets
|
||||
to white/blacklist:
|
||||
|
||||
- load/run executable code, like js, flash, java applets, ... (think NoScript)
|
||||
- requests to other domains, ports or using a different protocol than what the
|
||||
user requested (think RequestPolicy)
|
||||
- accept cookies
|
||||
- storing/saving/caching things, e.g. open tabs ("session"), cookies, page
|
||||
contents, browsing/download history, form data, ...
|
||||
- send referrer
|
||||
- disclose any (presence, type, version, settings, capabilities, etc.)
|
||||
information about OS, browser, installed fonts, plugins, addons, etc.
|
||||
|
||||
2. Be efficient!
|
||||
I tend to leave a lot of tabs open and nobody can deny that some websites
|
||||
simply suck, so the browser should, unless told otherwise by the user:
|
||||
|
||||
- load tabs only when needed
|
||||
- run code in tabs only when needed, i.e. when the tab is currently being
|
||||
used/viewed (background tabs doing some JS magic even when they are not being
|
||||
used can create a lot of unnecessary load on the machine)
|
||||
- finish requests to the domain the user requested (e.g. www.example.org)
|
||||
before doing any requests to other subdomains (e.g. images.example.org) and
|
||||
finish those before doing requests to thirdparty domains (e.g. example.com)
|
||||
|
||||
3. Be stable!
|
||||
- one site should not make the complete browser crash, only that site's tab
|
||||
|
||||
|
||||
Upstream Bugs
|
||||
=============
|
||||
|
||||
- Web inspector is blank unless .hide()/.show() is called.
|
||||
Asked on SO: http://stackoverflow.com/q/23499159/2085149
|
||||
TODO: Report to PyQt/Qt
|
||||
|
||||
- Report some other crashes
|
||||
|
||||
|
||||
/u/angelic_sedition's thoughts
|
||||
==============================
|
||||
|
||||
Well support for greasemonkey scripts and bookmarklets/js (which was mentioned
|
||||
in the arch forum post) would be a big addition. What I've usually missed when
|
||||
using other vim-like browsers is things that allow for different settings and
|
||||
key bindings for different contexts. With that implemented I think I could
|
||||
switch to a lightweight browser (and believe me, I'd like to) for the most part
|
||||
and only use firefox when I needed downthemall or something.
|
||||
|
||||
For example, I have different bindings based on tab position that are reloaded
|
||||
with a pentadactyl autocmd so that <space><homerow keys> will take me to tab
|
||||
1-10 if I'm in that range or 2-20 if I'm in that range. I have an autocmd that
|
||||
will run on completed downloads that passes the file path to a script that will
|
||||
open ranger in a floating window with that file cut (this is basically like
|
||||
using ranger to save files instead of the crappy gui popup).
|
||||
|
||||
I also have a few bindings based on tabgroups. Tabgroups are a firefox feature,
|
||||
but I find them very useful for sorting things by topic so that only the tabs
|
||||
I'm interested at the moment are visible.
|
||||
|
||||
Pentadactyl has a feature it calls groups. You can create a group that will
|
||||
activate for sites/urls that match a pattern with some regex support. This
|
||||
allows me, for example, to set up different (more convenient) bindings for
|
||||
zooming only on images. I'll never need use the equivalent of vim n (next text
|
||||
search match), so I can bind that to zoom. This allows setting up custom
|
||||
quickmarks/gotos using the same keys for different websites. For example, on
|
||||
reddit I have different g(some key) bindings to go to different subreddits.
|
||||
This can also be used to pass certain keys directly to the site (e.g. for use
|
||||
with RES). For sites that don't have modifiable bindings, I can use this with
|
||||
pentadactyl's feedkeys or xdotool to create my own custom bindings. I even have
|
||||
a binding that will call out to bash script with different arguments depending
|
||||
on the site to download an image or an image gallery depending on the site (in
|
||||
some cases passing the url to some cli program).
|
||||
|
||||
I've also noticed the lack of completion. For example, on "o" pentadactyl will
|
||||
show sites (e.g. from history) that can be completed. I think I've been spoiled
|
||||
by pentadactyl having completion for just about everything.
|
||||
|
||||
|
||||
suckless surf ML post
|
||||
=====================
|
||||
|
||||
From: Ben Woolley <tautolog_AT_gmail.com>
|
||||
Date: Wed, 7 Jan 2015 18:29:25 -0800
|
||||
|
||||
Hi all,
|
||||
|
||||
This patch is a bit of a beast for surf. It is intended to be applied after
|
||||
the disk cache patch. It breaks some internal interfaces, so it could
|
||||
conflict with other patches.
|
||||
|
||||
I have been wanting a browser to implement a complete same-origin policy,
|
||||
and have been investigating how to do this in various browsers for many
|
||||
months. When I saw how surf opened new windows in a separate process, and
|
||||
was so simple, I knew I could do it quickly. Over the last two weeks, I
|
||||
have been developing this implementation on surf.
|
||||
|
||||
The basic idea is to prevent browser-based tracking as you browse from site
|
||||
to site, or origin to origin. By "origin" domain, I mean the "first-party"
|
||||
domain, the domain normally in the location bar (of the typical browser
|
||||
interface). Each origin domain effectively gets its own browser profile,
|
||||
and a browser process only ever deals with one origin domain at a time.
|
||||
This isolates origins vertically, preventing cookies, disk cache, memory
|
||||
cache, and window.name vulnerabilities. Basically, all known
|
||||
vulnerabilities that google and Mozilla cite as counter-examples when they
|
||||
explain why they haven't disabled third-party cookies yet.
|
||||
|
||||
When you are on msnbc.com, the tracking pixels will be stored in a cookie
|
||||
file for msnbc.com. When you go to cnn.com, the tracking pixels will be
|
||||
stored in a cookie file for cnn.com. You will not be tracked between them.
|
||||
However, third-party cookies, and the caching of third party resources will
|
||||
still work, but they will be isolated between origin domains. Instead of
|
||||
blocking cookies and cache entries, they are "double-keyed", or *also*
|
||||
keyed by origin.
|
||||
|
||||
There is a unidirectional communication channel, however, from one origin
|
||||
to the next, through navigation from one origin to the next. That is, the
|
||||
query string is passed from one origin to the next, and may embed
|
||||
identifiers. One example is an affiliate link that identifies where the
|
||||
lead came from. I have implemented what I call "horizontal isolation", in
|
||||
the form of an "Origin Crossing Gate".
|
||||
|
||||
Whenever you follow a link to a new domain, or even are just redirected to
|
||||
a new domain, a new window/tab is opened, and passed the referring origin
|
||||
via -R. The page passed to -O, for example -O originprompt.html, is an HTML
|
||||
page that is loaded in the new origin's context. That page tells you the
|
||||
origin you were on, the new origin, and the full link, and you can decide
|
||||
to go just to the new origin, or go to the full URL, after reviewing it for
|
||||
tracking data.
|
||||
|
||||
Also, you may click links that store your trust of that relationship with
|
||||
various expiration times, the same way you would trust geolocation requests
|
||||
for a particular origin for a period of time. The database used is actually
|
||||
the new origin's cookie file. Since the origin prompt is loaded in the new
|
||||
origin's context, I can set a cookie on behalf of the new origin. The
|
||||
expiration time of the trust is the expiration time of the cookie. The
|
||||
cookie implementation in webkit automatically expires the trust as part of
|
||||
how cookies work. Each time you cross an origin, the origin crossing page
|
||||
checks the cookie to see if trust is still established. If so, it will use
|
||||
window.location.replace() to continue on automatically. The initial page
|
||||
renders blank until the trust is invalidated, in which case the content of
|
||||
the gate is made visible.
|
||||
|
||||
However, the new origin is technically able to mess with those cookies, so
|
||||
a website could set trust for an origin crossing. I have addressed that by
|
||||
hashing the key with a salt, and setting the real expiration time as the
|
||||
value, along with an HMAC to verify the contents of the value. If the
|
||||
cookie is messed with in any way, the trust will be disabled, and the
|
||||
prompt will appear again. So it has a fail-safe function.
|
||||
|
||||
I know it seems a bit convoluted, but it just started out as a nice little
|
||||
rabbit hole, and I just wanted to get something workable. At first I
|
||||
thought using the cookie expiration time was convenient, but then when I
|
||||
realized that I needed to protect the cookie, things got a bit hairy. But
|
||||
it works.
|
||||
|
||||
Each profile is, by default, stored in ~/.surf/origins/$origin/
|
||||
The interesting side effect is that if there is a problem where a website
|
||||
relies on the cross-site cookie vulnerability to make a connection, you can
|
||||
simply make a symbolic link from one origin folder to another, and they
|
||||
will share the same profile. And if you want to delete cookies and/or cache
|
||||
for a particular origin, you just rm -rf the origin's profile folder, and
|
||||
don't have to interfere with your other sites that are working just fine.
|
||||
|
||||
One thing I don't handle are cross-origins POSTs. They just end up as GET
|
||||
requests right now. I intend to do something about that, but I haven't
|
||||
figured that out yet.
|
||||
|
||||
I have only been using this functionality for a few days myself, so I have
|
||||
absolutely no feedback yet. I wanted to provide the first implementation of
|
||||
the management of identity as a system resource the same way that things
|
||||
like geolocation, camera, and microphone resources are managed in browsers
|
||||
and mobile apps.
|
||||
|
||||
Currently, Mozilla and Tor have are working on third-party tracking issues
|
||||
in Firefox.
|
||||
https://blog.mozilla.org/privacy/2014/11/10/introducing-polaris-privacy-initiative-to-accelerate-user-focused-privacy-online/
|
||||
|
||||
Up to this point, Tor has provided a patch that double-keys cookies with
|
||||
the origin domain, but no other progress is visible. I have seen no
|
||||
discussion of how horizontal isolation is supposed to happen, and I wanted
|
||||
to show people that it can be done, and this is one way it can be done, and
|
||||
to compel the other browser makers to catch up, and hopefully the community
|
||||
can work toward a standard *without* the tracking loopholes, by showing
|
||||
people what a *complete* solution looks like.
|
||||
|
||||
Thank you,
|
||||
|
||||
Ben Woolley
|
||||
|
||||
Patch: http://lists.suckless.org/dev/att-25070/0005-same-origin-policy.patch
|
||||
@@ -22,16 +22,16 @@ Basic keybindings to get you started
|
||||
What to do now
|
||||
--------------
|
||||
|
||||
* View the link:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[key binding cheatsheet]
|
||||
* View the link:http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
|
||||
to make yourself familiar with the key bindings: +
|
||||
image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"]
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* There's also a https://www.shortcutfoo.com/app/dojos/qutebrowser[free training
|
||||
course] on shortcutfoo for the keybindings - note that you need to be in
|
||||
insert mode (i) for it to work.
|
||||
* Run `:adblock-update` to download adblock lists and activate adblocking.
|
||||
* If you just cloned the repository, you'll need to run
|
||||
`scripts/asciidoc2html.py` to generate the documentation.
|
||||
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it.
|
||||
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it. (Currently not available with the QtWebEngine backend and on the OS X build - use the `:set` command instead)
|
||||
* Subscribe to
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[the announce-only mailinglist].
|
||||
|
||||
@@ -17,7 +17,7 @@ qutebrowser - a keyboard-driven, vim-like browser based on PyQt5.
|
||||
|
||||
== DESCRIPTION
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
|
||||
on Python and Qt5 and is free software, licensed under the GPL.
|
||||
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
|
||||
|
||||
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
|
||||
|
||||
@@ -38,13 +38,13 @@ show it.
|
||||
*-h*, *--help*::
|
||||
show this help message and exit
|
||||
|
||||
*-B* 'BASEDIR', *--basedir* 'BASEDIR'::
|
||||
*--basedir* 'BASEDIR'::
|
||||
Base directory for all storage.
|
||||
|
||||
*-V*, *--version*::
|
||||
Show version and quit.
|
||||
|
||||
*-s* 'OPTION' 'VALUE', *--set* 'OPTION' 'VALUE'::
|
||||
*-s* 'SECTION' 'OPTION' 'VALUE', *--set* 'SECTION' 'OPTION' 'VALUE'::
|
||||
Set a temporary setting for this session.
|
||||
|
||||
*-r* 'SESSION', *--restore* 'SESSION'::
|
||||
@@ -60,7 +60,7 @@ show it.
|
||||
Which backend to use.
|
||||
|
||||
*--enable-webengine-inspector*::
|
||||
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details. This is not needed anymore since Qt 5.11 where the inspector is always enabled and secure.
|
||||
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details.
|
||||
|
||||
=== debug arguments
|
||||
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
|
||||
@@ -72,7 +72,7 @@ show it.
|
||||
*--loglines* 'LOGLINES'::
|
||||
How many lines of the debug log to keep in RAM (-1: unlimited).
|
||||
|
||||
*-d*, *--debug*::
|
||||
*--debug*::
|
||||
Turn on debugging options.
|
||||
|
||||
*--json-logging*::
|
||||
@@ -84,10 +84,16 @@ show it.
|
||||
*--force-color*::
|
||||
Force colored logging
|
||||
|
||||
*--harfbuzz* '{old,new,system,auto}'::
|
||||
HarfBuzz engine version to use. Default: auto.
|
||||
|
||||
*--relaxed-config*::
|
||||
Silently remove unknown config options.
|
||||
|
||||
*--nowindow*::
|
||||
Don't show the main window.
|
||||
|
||||
*-T*, *--temp-basedir*::
|
||||
*--temp-basedir*::
|
||||
Use a temporary basedir.
|
||||
|
||||
*--no-err-windows*::
|
||||
@@ -99,15 +105,15 @@ show it.
|
||||
*--qt-flag* 'QT_FLAG'::
|
||||
Pass an argument to Qt as flag.
|
||||
|
||||
*-D* 'DEBUG_FLAGS', *--debug-flag* 'DEBUG_FLAGS'::
|
||||
*--debug-flag* 'DEBUG_FLAGS'::
|
||||
Pass name of debugging feature to be turned on.
|
||||
// QUTE_OPTIONS_END
|
||||
|
||||
== FILES
|
||||
|
||||
- '~/.config/qutebrowser/config.py': Configuration file.
|
||||
- '~/.config/qutebrowser/autoconfig.yml': Configuration done via the GUI.
|
||||
- '~/.config/qutebrowser/qutebrowser.conf': Main config file.
|
||||
- '~/.config/qutebrowser/quickmarks': Saved quickmarks.
|
||||
- '~/.config/qutebrowser/keys.conf': Defined key bindings.
|
||||
- '~/.local/share/qutebrowser/': Various state information.
|
||||
- '~/.cache/qutebrowser/': Temporary data.
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ is available in the repositories:
|
||||
Archlinux
|
||||
^^^^^^^^^
|
||||
|
||||
For Archlinux, no debug information is provided. You can either compile Qt
|
||||
For Archlinux, no debug informations are provided. You can either compile Qt
|
||||
yourself (which will take a few hours even on a modern machine) or use
|
||||
debugging symbols compiled/packaged by me (x86_64 only).
|
||||
|
||||
@@ -57,7 +57,7 @@ Then edit your `/etc/pacman.conf` to add the repository to the bottom:
|
||||
|
||||
----
|
||||
[qt-debug]
|
||||
Server = https://qutebrowser.org/qt-debug/$arch
|
||||
Server = http://qutebrowser.org/qt-debug/$arch
|
||||
----
|
||||
|
||||
Then install the packages:
|
||||
|
||||
@@ -31,7 +31,7 @@ The following environment variables will be set when a userscript is launched:
|
||||
|
||||
- `QUTE_MODE`: Either `hints` (started via hints) or `command` (started via
|
||||
command or key binding).
|
||||
- `QUTE_USER_AGENT`: The currently set user agent, if customized.
|
||||
- `QUTE_USER_AGENT`: The currently set user agent.
|
||||
- `QUTE_FIFO`: The FIFO or file to write commands to.
|
||||
- `QUTE_HTML`: Path of a file containing the HTML source of the current page.
|
||||
- `QUTE_TEXT`: Path of a file containing the plaintext of the current page.
|
||||
@@ -45,6 +45,8 @@ In `command` mode:
|
||||
- `QUTE_URL`: The current URL.
|
||||
- `QUTE_TITLE`: The title of the current page.
|
||||
- `QUTE_SELECTED_TEXT`: The text currently selected on the page.
|
||||
- `QUTE_SELECTED_HTML` The HTML currently selected on the page (not supported
|
||||
with QtWebEngine).
|
||||
|
||||
In `hints` mode:
|
||||
|
||||
@@ -58,7 +60,7 @@ Sending commands
|
||||
Normal qutebrowser commands can be written to `$QUTE_FIFO` and will be
|
||||
executed.
|
||||
|
||||
On Unix/macOS, this is a named pipe and commands written to it will get executed
|
||||
On Unix/OS X, this is a named pipe and commands written to it will get executed
|
||||
immediately.
|
||||
|
||||
On Windows, this is a regular file, and the commands in it will be executed as
|
||||
@@ -76,11 +78,3 @@ Opening the currently selected word on http://www.dict.cc/[dict.cc]:
|
||||
|
||||
echo "open -t http://www.dict.cc/?s=$QUTE_SELECTED_TEXT" >> "$QUTE_FIFO"
|
||||
----
|
||||
|
||||
Libraries
|
||||
---------
|
||||
|
||||
Some third-party libraries are available to make writing userscripts easier:
|
||||
|
||||
- Python: https://github.com/hiway/python-qutescript[python-qutescript]
|
||||
- Node.js: https://www.npmjs.com/package/qutejs[qutejs]
|
||||
|
||||
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 45 KiB |
@@ -1,302 +1,207 @@
|
||||
/* XPM */
|
||||
static char * qutebrowser_xpm[] = {
|
||||
"32 32 267 2",
|
||||
" c None",
|
||||
". c #9FD4FD",
|
||||
"+ c #99CBFE",
|
||||
"@ c #90C3FE",
|
||||
"# c #89BFFE",
|
||||
"$ c #81BCFF",
|
||||
"% c #80BBFF",
|
||||
"& c #9BCAFD",
|
||||
"* c #A9DBFB",
|
||||
"= c #88D3FB",
|
||||
"- c #98CBFE",
|
||||
"; c #81BBFF",
|
||||
"> c #7EBAFF",
|
||||
", c #84BDFF",
|
||||
"' c #8DC2FF",
|
||||
") c #96C7FE",
|
||||
"! c #A0CCFE",
|
||||
"~ c #A9D1FE",
|
||||
"{ c #CEE5FD",
|
||||
"] c #C7E3FC",
|
||||
"^ c #8AD3FB",
|
||||
"/ c #9DCFFD",
|
||||
"( c #C3DFFD",
|
||||
"_ c #CDE4FD",
|
||||
": c #A3CEFE",
|
||||
"< c #94C6FE",
|
||||
"[ c #CAE5FC",
|
||||
"} c #7DD0FB",
|
||||
"| c #9ECDFD",
|
||||
"1 c #A1CDFE",
|
||||
"2 c #8BC1FF",
|
||||
"3 c #87BFFF",
|
||||
"4 c #ADD4FE",
|
||||
"5 c #C6E1FD",
|
||||
"6 c #CCE3FC",
|
||||
"7 c #A7DAFB",
|
||||
"8 c #9DCBFE",
|
||||
"9 c #78AFF1",
|
||||
"0 c #6096D4",
|
||||
"a c #4B82C0",
|
||||
"b c #5A84B3",
|
||||
"c c #6589B1",
|
||||
"d c #6F92B9",
|
||||
"e c #90AED0",
|
||||
"f c #C4DBF5",
|
||||
"g c #6286AE",
|
||||
"h c #7D9EC2",
|
||||
"i c #BADFFC",
|
||||
"j c #85BDFE",
|
||||
"k c #78B4F8",
|
||||
"l c #4C83C0",
|
||||
"m c #1E4F87",
|
||||
"n c #0A396E",
|
||||
"o c #345D8D",
|
||||
"p c #CDE4FC",
|
||||
"q c #88A7CA",
|
||||
"r c #1D497C",
|
||||
"s c #799BBF",
|
||||
"t c #8AC1FD",
|
||||
"u c #5E97D7",
|
||||
"v c #14457B",
|
||||
"w c #4F76A0",
|
||||
"x c #A9D5FC",
|
||||
"y c #95C9FD",
|
||||
"z c #4C82C1",
|
||||
"A c #0A3A6F",
|
||||
"B c #C9E3FD",
|
||||
"C c #95CCFC",
|
||||
"D c #629BDB",
|
||||
"E c #0B3A6F",
|
||||
"F c #0C3B6F",
|
||||
"G c #4E749F",
|
||||
"H c #8CACCE",
|
||||
"I c #6185AD",
|
||||
"J c #CBE4FD",
|
||||
"K c #89C0FF",
|
||||
"L c #98CDFA",
|
||||
"M c #27558A",
|
||||
"N c #144175",
|
||||
"O c #9BB8D8",
|
||||
"P c #335D8C",
|
||||
"Q c #AFC9E6",
|
||||
"R c #AFD4FE",
|
||||
"S c #91C7FD",
|
||||
"T c #A0C0DE",
|
||||
"U c #194779",
|
||||
"V c #80A1C5",
|
||||
"W c #C8E1F9",
|
||||
"X c #9CB9D8",
|
||||
"Y c #7799BE",
|
||||
"Z c #6489B0",
|
||||
"` c #7092B9",
|
||||
" . c #6E9DCF",
|
||||
".. c #79B5F9",
|
||||
"+. c #83BDFE",
|
||||
"@. c #7395BA",
|
||||
"#. c #315C8B",
|
||||
"$. c #7C9EC2",
|
||||
"%. c #C0D9F3",
|
||||
"&. c #7294BA",
|
||||
"*. c #5C94D4",
|
||||
"=. c #91CCFC",
|
||||
"-. c #88CBFA",
|
||||
";. c #5179A3",
|
||||
">. c #6E91B7",
|
||||
",. c #6084AC",
|
||||
"'. c #96B3D4",
|
||||
"). c #275283",
|
||||
"!. c #0C3C71",
|
||||
"~. c #629CDC",
|
||||
"{. c #94C6FD",
|
||||
"]. c #A7D2FC",
|
||||
"^. c #36659A",
|
||||
"/. c #2C5788",
|
||||
"(. c #9DBAD9",
|
||||
"_. c #B4CEEA",
|
||||
":. c #476E9A",
|
||||
"<. c #7EB9FE",
|
||||
"[. c #8DC3FD",
|
||||
"}. c #8CC2FE",
|
||||
"|. c #2F619B",
|
||||
"1. c #87A6C9",
|
||||
"2. c #7A9BC0",
|
||||
"3. c #CBE2FB",
|
||||
"4. c #C7DFF8",
|
||||
"5. c #6C8FB5",
|
||||
"6. c #113F73",
|
||||
"7. c #0F3D71",
|
||||
"8. c #547AA4",
|
||||
"9. c #9CBAD9",
|
||||
"0. c #B9D3EE",
|
||||
"a. c #A3C0DE",
|
||||
"b. c #31629A",
|
||||
"c. c #659EE0",
|
||||
"d. c #87BFFE",
|
||||
"e. c #C3E0FD",
|
||||
"f. c #4371A4",
|
||||
"g. c #7496BB",
|
||||
"h. c #90AFD1",
|
||||
"i. c #245081",
|
||||
"j. c #416A96",
|
||||
"k. c #B0CBE7",
|
||||
"l. c #CCE4FD",
|
||||
"m. c #7DB8FD",
|
||||
"n. c #1E5088",
|
||||
"o. c #497EBC",
|
||||
"p. c #C9E3FC",
|
||||
"q. c #7193B9",
|
||||
"r. c #C6E0FB",
|
||||
"s. c #A2CDFE",
|
||||
"t. c #97C8FE",
|
||||
"u. c #A7D0FE",
|
||||
"v. c #BDDCFD",
|
||||
"w. c #9EC2E8",
|
||||
"x. c #416996",
|
||||
"y. c #366AA6",
|
||||
"z. c #C0DEFC",
|
||||
"A. c #A2BFDD",
|
||||
"B. c #326299",
|
||||
"C. c #649DDF",
|
||||
"D. c #71ABED",
|
||||
"E. c #3569A4",
|
||||
"F. c #0D3C71",
|
||||
"G. c #6998CD",
|
||||
"H. c #30639D",
|
||||
"I. c #A8D3F8",
|
||||
"J. c #2B5686",
|
||||
"K. c #3A679B",
|
||||
"L. c #ADCAEA",
|
||||
"M. c #85A6C9",
|
||||
"N. c #33639B",
|
||||
"O. c #9CCBFD",
|
||||
"P. c #86C2F7",
|
||||
"Q. c #0E3C71",
|
||||
"R. c #1B4C83",
|
||||
"S. c #5D95D5",
|
||||
"T. c #557BA5",
|
||||
"U. c #85C0F6",
|
||||
"V. c #55A8EF",
|
||||
"W. c #94B3D3",
|
||||
"X. c #1C497C",
|
||||
"Y. c #13437A",
|
||||
"Z. c #487DBB",
|
||||
"`. c #7BB7FB",
|
||||
" + c #76B1F5",
|
||||
".+ c #4E85C3",
|
||||
"++ c #ACD3FE",
|
||||
"@+ c #2F5989",
|
||||
"#+ c #7597BC",
|
||||
"$+ c #53A7EF",
|
||||
"%+ c #C6E1FC",
|
||||
"&+ c #B6D5F7",
|
||||
"*+ c #5890D0",
|
||||
"=+ c #4076B2",
|
||||
"-+ c #619ADB",
|
||||
";+ c #7CB7FC",
|
||||
">+ c #7DB9FE",
|
||||
",+ c #5087C6",
|
||||
"'+ c #134479",
|
||||
")+ c #23548D",
|
||||
"!+ c #24558D",
|
||||
"~+ c #8AAACC",
|
||||
"{+ c #A2C1E1",
|
||||
"]+ c #86C1F5",
|
||||
"^+ c #B4D7FE",
|
||||
"/+ c #6CA5E8",
|
||||
"(+ c #22548C",
|
||||
"_+ c #6D94BF",
|
||||
":+ c #98B6D6",
|
||||
"<+ c #134174",
|
||||
"[+ c #84BDF5",
|
||||
"}+ c #CAE4FC",
|
||||
"|+ c #CBE3FD",
|
||||
"1+ c #8FC3FF",
|
||||
"2+ c #3F72AD",
|
||||
"3+ c #49719C",
|
||||
"4+ c #0C3B70",
|
||||
"5+ c #9CBBDB",
|
||||
"6+ c #79B7F3",
|
||||
"7+ c #BFDCFD",
|
||||
"8+ c #7FBBFF",
|
||||
"9+ c #7E9FC3",
|
||||
"0+ c #77B6F3",
|
||||
"a+ c #A5CEF7",
|
||||
"b+ c #9FCBFE",
|
||||
"c+ c #3267A1",
|
||||
"d+ c #A4CDF7",
|
||||
"e+ c #B9D9FA",
|
||||
"f+ c #C7E1FD",
|
||||
"g+ c #90C3FF",
|
||||
"h+ c #15457C",
|
||||
"i+ c #558CCB",
|
||||
"j+ c #2E5889",
|
||||
"k+ c #7B9CC1",
|
||||
"l+ c #C4DDF6",
|
||||
"m+ c #BBDAFA",
|
||||
"n+ c #CDE5FD",
|
||||
"o+ c #B3D6FE",
|
||||
"p+ c #80BAFF",
|
||||
"q+ c #4E84C3",
|
||||
"r+ c #3E73AF",
|
||||
"s+ c #78B3F7",
|
||||
"t+ c #5991D1",
|
||||
"u+ c #477DBA",
|
||||
"v+ c #4075B2",
|
||||
"w+ c #5783B6",
|
||||
"x+ c #BDD6F0",
|
||||
"y+ c #A1CBF6",
|
||||
"z+ c #90C4FF",
|
||||
"A+ c #BCDBFD",
|
||||
"B+ c #73B0F1",
|
||||
"C+ c #C5E0FB",
|
||||
"D+ c #91C5FF",
|
||||
"E+ c #AED3FE",
|
||||
"F+ c #C9E2FC",
|
||||
"G+ c #76B2F2",
|
||||
"H+ c #8BBFF9",
|
||||
"I+ c #81BBFE",
|
||||
"J+ c #9ECBFE",
|
||||
"K+ c #84B8F3",
|
||||
"L+ c #79B4F4",
|
||||
"M+ c #88BEFA",
|
||||
"N+ c #83BCFE",
|
||||
"O+ c #A4CFFC",
|
||||
"P+ c #A6CDF6",
|
||||
"Q+ c #82B8F2",
|
||||
"R+ c #529BEC",
|
||||
" . + @ # $ % & * = ",
|
||||
" - ; > > , ' ) ! ~ { { { ] ^ ",
|
||||
" / ; > > > > ; ( _ : < { { { { { [ } ",
|
||||
" | 1 2 > > > 2 3 4 5 { { { { { 6 { { { 7 ",
|
||||
" 8 $ < 9 0 a b c d e { { { { f g h { { { { i ",
|
||||
" j k l m n n n n n n o { { p q r n s { { { { { i ",
|
||||
" t u v n n n n n n n n o { { w n n n s { { { { { { x ",
|
||||
" y z A n n n n n n n n n o { { o n n n s { { { { { { B C ",
|
||||
" D E n n n F G H I n n n o { { o n n n s { { { { { J K % ",
|
||||
" L M n n n N O { { s n n n o { { o n n P Q { { { { { R > > S ",
|
||||
" T n n n n H { { { s n n n o { { o U V 6 W X Y Z ` ...> > +. ",
|
||||
" @.n n n #.{ { { { s n n n o { { $.%.W &.U n n n n n v *.> > =.",
|
||||
"-.;.n n n >.{ { { { s n n n ,.{ { { '.).n n n n n n n n !.~.> {.",
|
||||
"].^.n n n q { { { { s n /.(.{ { _.:.n n n n n n n n n n n m <.[.",
|
||||
"}.|.n n n H { { { { 1.2.3.{ 4.5.6.n n n 7.8.9.0.a.b.n n n n c.d.",
|
||||
"e.f.n n n g.{ { { { { { { h.i.n n n n j.k.{ { { l.m.n.n n n o.$ ",
|
||||
"p.q.n n n /.r.s.t.u.v.w.x.n n n n i.h.{ { { { { { u.o.n n n y.$ ",
|
||||
"z.A.n n n n B.C.D.u E.F.n n n 6.5.4.{ 3.2.1.{ { { { G.n n n H.d.",
|
||||
"I.p J.n n n n n n n n n n n K.L.{ { (./.n s { { { { M.n n n N.O.",
|
||||
"P.{ (.Q.n n n n n n n n R.S.> K _ ,.n n n s { { { { 5.n n n T.U.",
|
||||
"V.{ { W.X.n n n n n Y.Z.`. +.+> ++o n n n s { { { { @+n n n #+$+",
|
||||
" %+{ { &+*+Z.=+a -+;+>+,+'+)+> > !+n n n s { { { ~+n n n n {+ ",
|
||||
" ]+{ { ^+> > > > > /+(+n n )+> > )+n n n _+{ { :+<+n n n o [+ ",
|
||||
" }+{ |+1+> > > > l n n n )+> > )+n n n 2+~+3+E n n n 4+5+ ",
|
||||
" 6+{ { 7+8+> > > l n n n )+> > )+n n n n n n n n n F 9+0+ ",
|
||||
" a+{ { b+> > > l n n n c+> > )+n n n n n n n n r O d+ ",
|
||||
" e+{ f+g+> > l n h+i+<.> > )+n n n n n E j+k+l+m+ ",
|
||||
" e+{ n+o+p+q+r+s+> > > > t+u+v+w+2.W.x+{ { e+ ",
|
||||
" y+{ { z+>+> > > > > > > > > A+{ { { { d+ ",
|
||||
" B+C+) > > > > > > > > D+E+{ { { F+G+ ",
|
||||
" H+I+> > > > > > J+{ { { C+K+ ",
|
||||
" L+M+# N+; 8+O+P+Q+R+ "};
|
||||
static char *qutebrowser[] = {
|
||||
/* columns rows colors chars-per-pixel */
|
||||
"32 32 169 2 ",
|
||||
" c #0A396E",
|
||||
". c #0B3C72",
|
||||
"X c #0B4077",
|
||||
"o c #0C437B",
|
||||
"O c #134175",
|
||||
"+ c #15467C",
|
||||
"@ c #18477B",
|
||||
"# c #1A497D",
|
||||
"$ c #0D4B86",
|
||||
"% c #0F4E8D",
|
||||
"& c #124A80",
|
||||
"* c #1F4F83",
|
||||
"= c #0E518C",
|
||||
"- c #1F5084",
|
||||
"; c #11508C",
|
||||
": c #0F5193",
|
||||
"> c #115799",
|
||||
", c #115B9C",
|
||||
"< c #204F83",
|
||||
"1 c #245287",
|
||||
"2 c #2A598C",
|
||||
"3 c #325E8F",
|
||||
"4 c #11609F",
|
||||
"5 c #346496",
|
||||
"6 c #3B6898",
|
||||
"7 c #115CA1",
|
||||
"8 c #115EAC",
|
||||
"9 c #1263A3",
|
||||
"0 c #1260AD",
|
||||
"q c #136BAC",
|
||||
"w c #136BB2",
|
||||
"e c #1366BA",
|
||||
"r c #196BB2",
|
||||
"t c #157ABB",
|
||||
"y c #1577BB",
|
||||
"u c #2E6DB0",
|
||||
"i c #387FB1",
|
||||
"p c #456E9A",
|
||||
"a c #4873A1",
|
||||
"s c #4375AA",
|
||||
"d c #507AA6",
|
||||
"f c #597EA4",
|
||||
"g c #4D7EB3",
|
||||
"h c #156FCB",
|
||||
"j c #167AC5",
|
||||
"k c #1675CA",
|
||||
"l c #177BCE",
|
||||
"z c #1777D8",
|
||||
"x c #1476E4",
|
||||
"c c #167BE6",
|
||||
"v c #167DE8",
|
||||
"b c #197EEF",
|
||||
"n c #1A7FF0",
|
||||
"m c #1A80BE",
|
||||
"M c #5F87AF",
|
||||
"N c #5D8BBA",
|
||||
"B c #5A84B1",
|
||||
"V c #6C8FB3",
|
||||
"C c #6F96BE",
|
||||
"Z c #1886CC",
|
||||
"A c #1883D7",
|
||||
"S c #198DD5",
|
||||
"D c #1987D9",
|
||||
"F c #198ADC",
|
||||
"G c #1A96DC",
|
||||
"H c #3090D9",
|
||||
"J c #1682E9",
|
||||
"K c #1983ED",
|
||||
"L c #1689E9",
|
||||
"P c #1A8DEE",
|
||||
"I c #1B95ED",
|
||||
"U c #1C9EEA",
|
||||
"Y c #1B97E4",
|
||||
"T c #1A84F2",
|
||||
"R c #1A8BF2",
|
||||
"E c #1C94F4",
|
||||
"W c #1D9CF5",
|
||||
"Q c #3388E6",
|
||||
"! c #3D90E9",
|
||||
"~ c #228EF3",
|
||||
"^ c #229FF6",
|
||||
"/ c #3294F4",
|
||||
"( c #3D9FF6",
|
||||
") c #339CF4",
|
||||
"_ c #1CA2E5",
|
||||
"` c #1DABEE",
|
||||
"' c #1DA4F6",
|
||||
"] c #1EA9F7",
|
||||
"[ c #1EADF8",
|
||||
"{ c #1FB4F9",
|
||||
"} c #1FB9FA",
|
||||
"| c #20ACF8",
|
||||
" . c #27A4F6",
|
||||
".. c #3DA9F6",
|
||||
"X. c #20B9FA",
|
||||
"o. c #2EB6F9",
|
||||
"O. c #458DC9",
|
||||
"+. c #5C8DC1",
|
||||
"@. c #5795C6",
|
||||
"#. c #709DCB",
|
||||
"$. c #74A8DD",
|
||||
"%. c #4A97EA",
|
||||
"&. c #4896EA",
|
||||
"*. c #559EEA",
|
||||
"=. c #439AF5",
|
||||
"-. c #46A3F6",
|
||||
";. c #5FA9F6",
|
||||
":. c #5EA6F3",
|
||||
">. c #47BCF9",
|
||||
",. c #51B5F8",
|
||||
"<. c #58BDF8",
|
||||
"1. c #68ABEF",
|
||||
"2. c #7DB9E7",
|
||||
"3. c #63AEF7",
|
||||
"4. c #6FB1F7",
|
||||
"5. c #66B9F8",
|
||||
"6. c #61B2F6",
|
||||
"7. c #71B4F7",
|
||||
"8. c #78B7F4",
|
||||
"9. c #72BFF9",
|
||||
"0. c #3BC0FA",
|
||||
"q. c #6FCEFB",
|
||||
"w. c #6CC5FA",
|
||||
"e. c #7BCAF9",
|
||||
"r. c #89A7C3",
|
||||
"t. c #83A2C1",
|
||||
"y. c #98B6D3",
|
||||
"u. c #9DB9D3",
|
||||
"i. c #89B6E4",
|
||||
"p. c #83B6E9",
|
||||
"a. c #81BDF7",
|
||||
"s. c #83BFF8",
|
||||
"d. c #9EC4E9",
|
||||
"f. c #8CC2F9",
|
||||
"g. c #85CDFB",
|
||||
"h. c #87C4F9",
|
||||
"j. c #92C6F9",
|
||||
"k. c #95CAFA",
|
||||
"l. c #9CCBFA",
|
||||
"z. c #89D7FC",
|
||||
"x. c #91D9FC",
|
||||
"c. c #9CDEFD",
|
||||
"v. c #9ED2FB",
|
||||
"b. c #A7CAEC",
|
||||
"n. c #B5CEE3",
|
||||
"m. c #A1CEFA",
|
||||
"M. c #AED0F0",
|
||||
"N. c #ACD6FA",
|
||||
"B. c #A0DFFC",
|
||||
"V. c #AFD8FC",
|
||||
"C. c #B5D9FB",
|
||||
"Z. c #BCDDFC",
|
||||
"A. c #BFDCF5",
|
||||
"S. c #ACE3FD",
|
||||
"D. c #B5E5FE",
|
||||
"F. c #BBE2FC",
|
||||
"G. c #CFE5F5",
|
||||
"H. c #C3E1FC",
|
||||
"J. c #CAE6FD",
|
||||
"K. c #CCEBFD",
|
||||
"L. c #C4EBFE",
|
||||
"P. c #D6EDFE",
|
||||
"I. c #DAEEFD",
|
||||
"U. c #DEF1FE",
|
||||
"Y. c #D6F2FE",
|
||||
"T. c #E4F4FE",
|
||||
"R. c #E9F6FE",
|
||||
"E. c #EBF8FF",
|
||||
"W. c None",
|
||||
/* pixels */
|
||||
"W.W.W.W.W.W.W.W.W.W.W.c.S.L.Y.E.E.S.X.} W.W.W.W.W.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.W.W.D.T.E.E.T.L.D.c.z.} } X.} } W.W.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.B.T.T.R.T.R.U.0.X.z.S.} } } } { { X.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.x.x.K.T.T.T.L.P.q.o.{ } } ` _ { { { { { W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.c.P.D.G.u.r.i 9 Z _ { { G 4 X t { { { { { { W.W.W.W.W.",
|
||||
"W.W.W.W.K.U.n.f O { = t { { { { [ { { W.W.W.W.",
|
||||
"W.W.W.F.I.t.. ' t { { [ [ [ [ [ >.W.W.W.",
|
||||
"W.W.x.P.V ' X t ` [ [ [ [ [ [ o.e.W.W.",
|
||||
"W.W.J.y. X t S Y Z $ ' . y [ [ [ ] [ [ | Z.J.W.W.",
|
||||
"W.<.e.& , _ ] ] [ ] U . ' . y [ ' [ ] ] ] w.K.J.g.W.",
|
||||
"W.' S o ' ' [ ' [ ' ] o ' . y Y 9 = = 9 @.J.J.J.F.W.",
|
||||
"W.| , j ' ' ' ' ' ' ' o ' . $ p A.J.J.g.",
|
||||
"' .. G ' ' ' ' ' ' ' o ' . M H.H.h.",
|
||||
",.2. . W ' W ' ' ' ' W . ' . M.A.x.",
|
||||
"N.M.. . W W W ' W W W W .w 9 I U 0 #.Z.m.",
|
||||
" .9.O D W W W W ' W j $ % F W W W .5 d Z.C.",
|
||||
"W W ; 9 9.h.5...Q % o j W W W W W W O. 3 C.N.",
|
||||
"E W 7 B b.d.a . w E E W W W E W E A @ C.l.",
|
||||
"I E l u W E W E W E E E E A . - k.6.",
|
||||
"P E E 7 m.o E E E E E E E E l . = E P ",
|
||||
"L E E E > . O s.o E E E E E E E E 7 , E L ",
|
||||
"W.R E R ) #.5 1 6 N i.2 s.+ E E E E E E R L . k R W.",
|
||||
"W.L R E -.m.m.m.m.m.m.2 m.@ N m.m.s.( R R % X E J W.",
|
||||
"W.W.K R ~ a.m.l.l.l.l.2 s.+ < i.l.m.j.h % e K W.W.",
|
||||
"W.W.J R R / l.l.l.l.k.2 s.+ * 5 + 8 R J W.W.",
|
||||
"W.W.W.v T R 3.k.k.j.k.2 2 j.& . 8 R v W.W.W.",
|
||||
"W.W.W.W.J T ~ 7.j.j.j.g +.p.j.s.+. . . : z T v W.W.W.W.",
|
||||
"W.W.W.W.W.c T T =.f.j.j.s.j.j.j.j.$.g s u e h b T T v W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.c b n 4.f.f.s.m.s.s.s.j.s.j./ T n T b c W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.c x 1.s.s.s.s.s.s.s.s.4.=.n T n c c W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.W.W.&.*.1.a.s.s.s.s.3.n n v x x W.W.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.W.W.W.W.%.%.%.%.*.*.Q x x x W.W.W.W.W.W.W.W.W.W.W."
|
||||
};
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
PYTHON = python3
|
||||
PREFIX = /usr/local
|
||||
DESTDIR =
|
||||
ICONSIZES = 16 24 32 48 64 128 256 512
|
||||
|
||||
SETUPTOOLSOPTIONS =
|
||||
ifdef DESTDIR
|
||||
SETUPTOOLSOPTS = --root="$(DESTDIR)"
|
||||
endif
|
||||
|
||||
.PHONY: install
|
||||
|
||||
doc/qutebrowser.1.html:
|
||||
a2x -f manpage doc/qutebrowser.1.asciidoc
|
||||
|
||||
install: doc/qutebrowser.1.html
|
||||
$(PYTHON) setup.py install --prefix="$(PREFIX)" --optimize=1 $(SETUPTOOLSOPTS)
|
||||
install -Dm644 misc/qutebrowser.appdata.xml \
|
||||
"$(DESTDIR)$(PREFIX)/share/metainfo/qutebrowser.appdata.xml"
|
||||
install -Dm644 doc/qutebrowser.1 \
|
||||
"$(DESTDIR)$(PREFIX)/share/man/man1/qutebrowser.1"
|
||||
install -Dm644 misc/qutebrowser.desktop \
|
||||
"$(DESTDIR)$(PREFIX)/share/applications/qutebrowser.desktop"
|
||||
$(foreach i,$(ICONSIZES),install -Dm644 "icons/qutebrowser-$(i)x$(i).png" \
|
||||
"$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";)
|
||||
install -Dm644 icons/qutebrowser.svg \
|
||||
"$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/qutebrowser.svg"
|
||||
install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/userscripts/" \
|
||||
$(wildcard misc/userscripts/*)
|
||||
install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/scripts/" \
|
||||
$(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \
|
||||
scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \
|
||||
scripts/link_pyqt.py,$(wildcard scripts/*))
|
||||
@@ -13,7 +13,7 @@
|
||||
height="682.66669"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
||||
inkscape:version="0.92.1 r"
|
||||
version="1.0"
|
||||
sodipodi:docname="cheatsheet.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
@@ -32,9 +32,9 @@
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.7536248"
|
||||
inkscape:cx="430.72917"
|
||||
inkscape:cy="268.64059"
|
||||
inkscape:zoom="1.7582312"
|
||||
inkscape:cx="513.85167"
|
||||
inkscape:cy="273.37342"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
width="1024px"
|
||||
@@ -47,9 +47,7 @@
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:snap-text-baseline="true"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0">
|
||||
inkscape:snap-text-baseline="true">
|
||||
<inkscape:grid
|
||||
id="GridFromPre046Settings"
|
||||
type="xygrid"
|
||||
@@ -86,9 +84,9 @@
|
||||
height="64"
|
||||
width="74.666664"
|
||||
id="rect3328"
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
<rect
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
id="rect3330"
|
||||
width="64"
|
||||
height="64"
|
||||
@@ -717,7 +715,7 @@
|
||||
height="64"
|
||||
width="63.461262"
|
||||
id="rect3720"
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
<path
|
||||
id="path3724"
|
||||
d="m 854.35172,271.52738 c 21.26539,0 42.53077,0 63.79615,0"
|
||||
@@ -987,7 +985,7 @@
|
||||
<g
|
||||
id="g7167"
|
||||
transform="translate(74.666662,16.594076)"
|
||||
style="fill:#eeeeec;fill-opacity:1">
|
||||
style="fill:#babdb6;fill-opacity:1">
|
||||
<rect
|
||||
ry="4.7797003"
|
||||
y="296.53333"
|
||||
@@ -995,9 +993,9 @@
|
||||
height="64"
|
||||
width="63.461262"
|
||||
id="rect7169"
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
<rect
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
id="rect7171"
|
||||
width="63.461262"
|
||||
height="32"
|
||||
@@ -1007,7 +1005,7 @@
|
||||
<path
|
||||
id="path7173"
|
||||
d="m 640.14582,329.06667 c 21.0911,0 42.18218,0 63.27327,0"
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:#000000;stroke-width:1.16182172px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:#000000;stroke-width:1.16182172px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<text
|
||||
@@ -1305,7 +1303,7 @@
|
||||
height="64"
|
||||
width="63.461262"
|
||||
id="rect3980"
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
|
||||
<path
|
||||
id="path3982"
|
||||
d="m 11.247578,121.66071 c 21.091093,0 42.182176,0 63.273269,0"
|
||||
@@ -2477,21 +2475,21 @@
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">prev</tspan></text>
|
||||
<text
|
||||
id="text9514-60-8"
|
||||
y="355.28558"
|
||||
y="357.28558"
|
||||
x="588.79791"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="355.28558"
|
||||
y="357.28558"
|
||||
x="588.79791"
|
||||
sodipodi:role="line"
|
||||
id="tspan5524"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">save</tspan><tspan
|
||||
y="362.96558"
|
||||
y="364.96558"
|
||||
x="588.79791"
|
||||
sodipodi:role="line"
|
||||
id="tspan5530"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">quick-</tspan><tspan
|
||||
y="370.64557"
|
||||
y="372.64557"
|
||||
x="588.79791"
|
||||
sodipodi:role="line"
|
||||
id="tspan5532"
|
||||
@@ -2690,8 +2688,7 @@
|
||||
id="flowPara5711"> </flowPara></flowRoot> <flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-0"
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
transform="translate(0,-10)"><flowRegion
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"><flowRegion
|
||||
id="flowRegion5693-7"
|
||||
style="font-family:sans-serif;stroke-width:1.06666672"><rect
|
||||
id="rect5695-0"
|
||||
@@ -3042,8 +3039,6 @@
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3792">;I - hint images in new tab</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6096">;t - hint inputs</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3794">;o - put hinted URL in cmd. line</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3796">;O - like <flowSpan
|
||||
@@ -3193,9 +3188,7 @@
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara4148"><Ctrl-P> - prev. history item</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3935-9"><Ctrl-N> - next history item</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6189"><Ctrl-D> - delete current item</flowPara></flowRoot> <rect
|
||||
id="flowPara3935-9"><Ctrl-N> - next history item</flowPara></flowRoot> <rect
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
id="rect3764-9"
|
||||
width="64"
|
||||
@@ -3447,8 +3440,7 @@
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4-9-3-6-6"
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
transform="translate(0,10)"><flowRegion
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"><flowRegion
|
||||
id="flowRegion5693-9-1-7-3-8"
|
||||
style="font-family:sans-serif;stroke-width:1.06666672"><rect
|
||||
id="rect5695-9-8-7-7-6"
|
||||
@@ -3506,221 +3498,5 @@
|
||||
sodipodi:role="line"
|
||||
id="tspan4112"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">mode</tspan></text>
|
||||
<text
|
||||
id="text10564-5"
|
||||
y="274.2934"
|
||||
x="873.4303"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="274.2934"
|
||||
x="873.4303"
|
||||
sodipodi:role="line"
|
||||
id="tspan10566-6"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
id="tspan10570-91"
|
||||
y="282.1763"
|
||||
x="873.4303"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">jump to</tspan><tspan
|
||||
y="289.85632"
|
||||
x="873.4303"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6066">scroll</tspan><tspan
|
||||
y="297.53632"
|
||||
x="873.4303"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6068">mark</tspan></text>
|
||||
<text
|
||||
id="text10564-2"
|
||||
y="362.50635"
|
||||
x="731.82947"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan10568-0"
|
||||
y="362.50635"
|
||||
x="731.82947"
|
||||
sodipodi:role="line"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672">repeat</tspan><tspan
|
||||
id="tspan10570-93"
|
||||
y="370.38925"
|
||||
x="731.82947"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">cmd</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
x="183.06667"
|
||||
y="97.639633"
|
||||
id="text7245-1-6"><tspan
|
||||
sodipodi:role="line"
|
||||
x="183.06667"
|
||||
y="97.639633"
|
||||
id="tspan7366-3-0"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="183.06667"
|
||||
y="105.52255"
|
||||
id="tspan7249-4-6"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">run</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="183.06667"
|
||||
y="113.20255"
|
||||
id="tspan5293-2"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">macro</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
x="117.44301"
|
||||
y="203.05061"
|
||||
id="text7245-1-61"><tspan
|
||||
sodipodi:role="line"
|
||||
x="117.44301"
|
||||
y="203.05061"
|
||||
id="tspan7366-3-8"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="117.44301"
|
||||
y="210.93353"
|
||||
id="tspan5293-9"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">record</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="117.44301"
|
||||
y="218.61353"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6136">macro</tspan></text>
|
||||
<text
|
||||
id="text10564-5-2"
|
||||
y="125.17836"
|
||||
x="37.344757"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="125.17836"
|
||||
x="37.344757"
|
||||
sodipodi:role="line"
|
||||
id="tspan10566-6-0"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
id="tspan10570-91-2"
|
||||
y="133.06128"
|
||||
x="37.344757"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">set</tspan><tspan
|
||||
y="140.74127"
|
||||
x="37.344757"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6066-3">scroll</tspan><tspan
|
||||
y="148.42128"
|
||||
x="37.344757"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6068-7">mark</tspan></text>
|
||||
<text
|
||||
id="text9514-60-8-5"
|
||||
y="323.89648"
|
||||
x="590.26257"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="323.89648"
|
||||
x="590.26257"
|
||||
sodipodi:role="line"
|
||||
id="tspan5524-9"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">save</tspan><tspan
|
||||
y="331.57648"
|
||||
x="590.26257"
|
||||
sodipodi:role="line"
|
||||
id="tspan5530-2"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">book-</tspan><tspan
|
||||
y="339.25647"
|
||||
x="590.26257"
|
||||
sodipodi:role="line"
|
||||
id="tspan5532-2"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">mark</tspan></text>
|
||||
<text
|
||||
id="text10564-5-2-8"
|
||||
y="200.40416"
|
||||
x="21.280243"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
xml:space="preserve"><tspan
|
||||
y="200.40416"
|
||||
x="21.280243"
|
||||
sodipodi:role="line"
|
||||
id="tspan10566-6-0-9"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
id="tspan10570-91-2-7"
|
||||
y="208.28708"
|
||||
x="21.280243"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">cycle</tspan><tspan
|
||||
y="215.96707"
|
||||
x="21.280243"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6068-7-6">completion</tspan><tspan
|
||||
y="223.64708"
|
||||
x="21.280243"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6220">items</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.80000019px;line-height:0%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
|
||||
x="417.29486"
|
||||
y="205.18887"
|
||||
id="text7245-1-3"><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="205.18887"
|
||||
id="tspan7366-3-6"
|
||||
style="font-size:9.60000038px;line-height:0.89999998;stroke-width:1.06666672"> </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="213.07179"
|
||||
id="tspan5293-53"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672">toggle</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="220.75179"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672;fill:#ff0000"
|
||||
id="tspan6091">(12)</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="225.70012"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6087" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="417.29486"
|
||||
y="225.70012"
|
||||
style="font-size:8.53333378px;line-height:0.89999998;stroke-width:1.06666672"
|
||||
id="tspan6089" /></text>
|
||||
<flowRoot
|
||||
transform="translate(-1.2953814,90.2721)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-0-5"
|
||||
style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"><flowRegion
|
||||
id="flowRegion5693-7-6"
|
||||
style="font-family:sans-serif;stroke-width:1.06666672"><rect
|
||||
id="rect5695-0-2"
|
||||
width="344"
|
||||
height="173.33333"
|
||||
x="19.42783"
|
||||
y="520.07886"
|
||||
style="font-family:sans-serif;fill:#000000;stroke-width:1.13777781" /></flowRegion><flowPara
|
||||
style="font-weight:bold;font-size:10.66666698px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'Sans Bold';fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara5701-9-2"><flowSpan
|
||||
style="font-weight:bold;font-family:sans-serif;-inkscape-font-specification:'Sans Bold';fill:#ff0000;stroke-width:1.06666672"
|
||||
id="flowSpan5705-5-1">(12)</flowSpan> toggling settings:</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6196">tsh - toggle scripts for the current host (temporarily)</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6200">tSh - like <flowSpan
|
||||
style="font-style:italic"
|
||||
id="flowSpan6202">tsh</flowSpan>, but permanently</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6206">tsH/tsu - like <flowSpan
|
||||
style="font-style:italic"
|
||||
id="flowSpan6210">tsh</flowSpan>, but including subdomains / with exact URL</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6208">tph - toggle plugins</flowPara></flowRoot> </g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 170 KiB |
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2017 suve <veg@svgames.pl> -->
|
||||
<component type="desktop">
|
||||
<id>org.qutebrowser.qutebrowser</id>
|
||||
<metadata_license>CC-BY-SA-3.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<name>qutebrowser</name>
|
||||
<summary>A keyboard-driven web browser</summary>
|
||||
<description>
|
||||
<p>
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI.
|
||||
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl,
|
||||
and is based on Python and PyQt5.
|
||||
</p>
|
||||
</description>
|
||||
<categories>
|
||||
<category>Network</category>
|
||||
<category>WebBrowser</category>
|
||||
</categories>
|
||||
<provides>
|
||||
<binary>qutebrowser</binary>
|
||||
</provides>
|
||||
<launchable type="desktop-id">qutebrowser.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/main.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/downloads.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/completion.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/hints.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<url type="homepage">https://www.qutebrowser.org</url>
|
||||
<url type="faq">https://qutebrowser.org/doc/faq.html</url>
|
||||
<url type="help">https://qutebrowser.org/doc/help/</url>
|
||||
<url type="bugtracker">https://github.com/qutebrowser/qutebrowser/issues/</url>
|
||||
<url type="donation">https://github.com/qutebrowser/qutebrowser#donating</url>
|
||||
<releases>
|
||||
<release version="1.3.0" date="2018-05-04"/>
|
||||
<release version="1.2.1" date="2018-03-14"/>
|
||||
<release version="1.2.0" date="2018-03-09"/>
|
||||
</releases>
|
||||
</component>
|
||||
@@ -23,7 +23,7 @@ SetCompressor /solid lzma
|
||||
!define MUI_ICON "../icons/qutebrowser.ico"
|
||||
!define MUI_UNICON "../icons/qutebrowser.ico"
|
||||
|
||||
!insertmacro MUI_PAGE_LICENSE "..\LICENSE"
|
||||
!insertmacro MUI_PAGE_LICENSE "..\COPYING"
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
|
||||
@@ -15,8 +15,7 @@ def get_data_files():
|
||||
('../qutebrowser/img', 'img'),
|
||||
('../qutebrowser/javascript', 'javascript'),
|
||||
('../qutebrowser/html/doc', 'html/doc'),
|
||||
('../qutebrowser/git-commit-id', '.'),
|
||||
('../qutebrowser/config/configdata.yml', 'config'),
|
||||
('../qutebrowser/git-commit-id', '')
|
||||
]
|
||||
|
||||
# if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
|
||||
@@ -58,19 +57,22 @@ exe = EXE(pyz,
|
||||
icon=icon,
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=False,
|
||||
console=False,
|
||||
version='misc/file_version_info.txt')
|
||||
upx=True,
|
||||
console=False )
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=False,
|
||||
upx=True,
|
||||
name='qutebrowser')
|
||||
|
||||
app = BUNDLE(coll,
|
||||
name='qutebrowser.app',
|
||||
icon=icon,
|
||||
info_plist={
|
||||
'NSHighResolutionCapable': 'True',
|
||||
'NSSupportsAutomaticGraphicsSwitching': 'True',
|
||||
},
|
||||
# https://github.com/pyinstaller/pyinstaller/blob/b78bfe530cdc2904f65ce098bdf2de08c9037abb/PyInstaller/hooks/hook-PyQt5.QtWebEngineWidgets.py#L24
|
||||
bundle_identifier='org.qt-project.Qt.QtWebEngineCore')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
This directory contains various `requirements` files which are used by `tox` to
|
||||
have reproducible tests with pinned versions.
|
||||
have reproducable tests with pinned versions.
|
||||
|
||||
The files are generated based on unpinned requirements in `*.txt-raw` files.
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
check-manifest==0.37
|
||||
check-manifest==0.35
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
certifi==2018.4.16
|
||||
certifi==2017.4.17
|
||||
chardet==3.0.4
|
||||
codecov==2.0.15
|
||||
coverage==4.5.1
|
||||
idna==2.7
|
||||
requests==2.19.1
|
||||
urllib3==1.22
|
||||
codecov==2.0.9
|
||||
coverage==4.4.1
|
||||
idna==2.5
|
||||
requests==2.18.1
|
||||
urllib3==1.21.1
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==18.1.0
|
||||
flake8==3.5.0
|
||||
flake8-bugbear==18.2.0
|
||||
flake8-builtins==1.4.1 # rq.filter: != 1.4.0
|
||||
flake8-comprehensions==1.4.1
|
||||
flake8==2.6.2 # rq.filter: < 3.0.0
|
||||
flake8-copyright==0.2.0
|
||||
flake8-debugger==3.1.0
|
||||
flake8-deprecated==1.3
|
||||
flake8-docstrings==1.3.0
|
||||
flake8-future-import==0.4.4
|
||||
flake8-debugger==1.4.0 # rq.filter: != 2.0.0
|
||||
flake8-deprecated==1.2
|
||||
flake8-docstrings==1.0.3 # rq.filter: < 1.1.0
|
||||
flake8-future-import==0.4.3
|
||||
flake8-mock==0.3
|
||||
flake8-per-file-ignores==0.6
|
||||
flake8-polyfill==1.0.2
|
||||
flake8-pep3101==1.0 # rq.filter: < 1.1
|
||||
flake8-polyfill==1.0.1
|
||||
flake8-putty==0.4.0
|
||||
flake8-string-format==0.2.3
|
||||
flake8-tidy-imports==1.1.0
|
||||
flake8-tidy-imports==1.0.6
|
||||
flake8-tuple==0.2.13
|
||||
mccabe==0.6.1
|
||||
pathmatch==0.2.1
|
||||
pep8-naming==0.7.0
|
||||
pycodestyle==2.3.1 # rq.filter: < 2.4.0
|
||||
pydocstyle==2.1.1
|
||||
pyflakes==2.0.0
|
||||
six==1.11.0
|
||||
snowballstemmer==1.2.1
|
||||
typing==3.6.4
|
||||
packaging==16.8
|
||||
pep8-naming==0.4.1
|
||||
pycodestyle==2.3.1
|
||||
pydocstyle==1.1.1 # rq.filter: < 2.0.0
|
||||
pyflakes==1.5.0
|
||||
pyparsing==2.2.0
|
||||
six==1.10.0
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
flake8
|
||||
flake8-bugbear
|
||||
flake8-builtins!=1.4.0
|
||||
flake8-comprehensions
|
||||
flake8<3.0.0
|
||||
flake8-copyright
|
||||
flake8-debugger
|
||||
flake8-debugger!=2.0.0
|
||||
flake8-deprecated
|
||||
flake8-docstrings
|
||||
flake8-docstrings<1.1.0
|
||||
flake8-future-import
|
||||
flake8-mock
|
||||
flake8-per-file-ignores
|
||||
flake8-pep3101<1.1
|
||||
flake8-putty
|
||||
flake8-string-format
|
||||
flake8-tidy-imports
|
||||
flake8-tuple
|
||||
pep8-naming
|
||||
pydocstyle
|
||||
pydocstyle<2.0.0
|
||||
pyflakes
|
||||
|
||||
# https://github.com/PyCQA/pycodestyle/issues/741
|
||||
#@ filter: pycodestyle < 2.4.0
|
||||
# Pinned to 2.0.0 otherwise
|
||||
pycodestyle==2.3.1
|
||||
# Pinned to 0.5.3 otherwise
|
||||
mccabe==0.6.1
|
||||
|
||||
# https://github.com/gforcada/flake8-builtins/issues/36
|
||||
#@ filter: flake8-builtins != 1.4.0
|
||||
# Waiting until flake8-putty updated
|
||||
#@ filter: flake8 < 3.0.0
|
||||
#@ filter: pydocstyle < 2.0.0
|
||||
#@ filter: flake8-docstrings < 1.1.0
|
||||
#@ filter: flake8-pep3101 < 1.1
|
||||
|
||||
# https://github.com/JBKahn/flake8-debugger/issues/5
|
||||
#@ filter: flake8-debugger != 2.0.0
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
appdirs==1.4.3
|
||||
packaging==17.1
|
||||
packaging==16.8
|
||||
pyparsing==2.2.0
|
||||
setuptools==40.0.0
|
||||
six==1.11.0
|
||||
wheel==0.31.1
|
||||
setuptools==36.0.1
|
||||
six==1.10.0
|
||||
wheel==0.29.0
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
altgraph==0.15
|
||||
future==0.16.0
|
||||
macholib==1.9
|
||||
pefile==2017.11.5
|
||||
PyInstaller==3.3.1
|
||||
-e git+https://github.com/xoviat/pyinstaller.git@qtweb#egg=PyInstaller
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
PyInstaller
|
||||
-e git+https://github.com/xoviat/pyinstaller.git@qtweb#egg=PyInstaller
|
||||
|
||||
# remove @commit-id for scm installs
|
||||
#@ replace: @.*# @qtweb#
|
||||
@@ -1,18 +1,18 @@
|
||||
# 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
|
||||
certifi==2017.4.17
|
||||
chardet==3.0.4
|
||||
github3.py==1.1.0
|
||||
idna==2.7
|
||||
isort==4.3.4
|
||||
github3.py==0.9.6
|
||||
idna==2.5
|
||||
isort==4.2.15
|
||||
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.19.1
|
||||
six==1.11.0
|
||||
requests==2.18.1
|
||||
six==1.10.0
|
||||
uritemplate==3.0.0
|
||||
urllib3==1.22
|
||||
wrapt==1.10.11
|
||||
uritemplate.py==3.0.2
|
||||
urllib3==1.21.1
|
||||
wrapt==1.10.10
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==1.6.5
|
||||
certifi==2018.4.16
|
||||
astroid==1.5.3
|
||||
certifi==2017.4.17
|
||||
chardet==3.0.4
|
||||
github3.py==1.1.0
|
||||
idna==2.7
|
||||
isort==4.3.4
|
||||
github3.py==0.9.6
|
||||
idna==2.5
|
||||
isort==4.2.15
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
pylint==1.9.2
|
||||
python-dateutil==2.7.3
|
||||
pylint==1.7.2
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.19.1
|
||||
six==1.11.0
|
||||
requests==2.18.1
|
||||
six==1.10.0
|
||||
uritemplate==3.0.0
|
||||
urllib3==1.22
|
||||
wrapt==1.10.11
|
||||
uritemplate.py==3.0.2
|
||||
urllib3==1.21.1
|
||||
wrapt==1.10.10
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.11.2
|
||||
PyQt5-sip==4.19.11
|
||||
PyQt5==5.9
|
||||
sip==4.19.3
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.14
|
||||
pyroma==2.3.1
|
||||
docutils==0.13.1
|
||||
pyroma==2.2
|
||||
|
||||
@@ -4,4 +4,3 @@ pyPEG2
|
||||
PyYAML
|
||||
colorama
|
||||
cssutils
|
||||
attrs
|
||||
|
||||
@@ -4,6 +4,7 @@ hg+https://bitbucket.org/ned/coveragepy
|
||||
git+https://github.com/micheles/decorator.git
|
||||
git+https://github.com/pallets/flask.git
|
||||
git+https://github.com/miracle2k/python-glob2.git
|
||||
git+https://github.com/Runscope/httpbin.git
|
||||
git+https://github.com/HypothesisWorks/hypothesis-python.git
|
||||
git+https://github.com/pallets/itsdangerous.git
|
||||
git+https://bitbucket.org/zzzeek/mako.git
|
||||
@@ -12,6 +13,12 @@ git+https://github.com/jenisys/parse_type.git
|
||||
hg+https://bitbucket.org/pytest-dev/py
|
||||
git+https://github.com/pytest-dev/pytest.git@features
|
||||
git+https://github.com/pytest-dev/pytest-bdd.git
|
||||
|
||||
# This is broken at the moment because logfail tries to access
|
||||
# LogCaptureHandler
|
||||
# git+https://github.com/eisensheng/pytest-catchlog.git
|
||||
pytest-catchlog==1.2.2
|
||||
|
||||
git+https://github.com/pytest-dev/pytest-cov.git
|
||||
git+https://github.com/pytest-dev/pytest-faulthandler.git
|
||||
git+https://github.com/pytest-dev/pytest-instafail.git
|
||||
@@ -34,5 +41,8 @@ git+https://github.com/pallets/jinja.git
|
||||
git+https://github.com/pallets/markupsafe.git
|
||||
hg+http://bitbucket.org/birkenfeld/pygments-main
|
||||
hg+https://bitbucket.org/fdik/pypeg
|
||||
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,40 +1,39 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==18.1.0
|
||||
beautifulsoup4==4.6.0
|
||||
cheroot==6.3.2.post0
|
||||
cheroot==5.7.0
|
||||
click==6.7
|
||||
# colorama==0.3.9
|
||||
coverage==4.5.1
|
||||
coverage==4.4.1
|
||||
decorator==4.0.11
|
||||
EasyProcess==0.2.3
|
||||
fields==5.0.0
|
||||
Flask==1.0.2
|
||||
glob2==0.6
|
||||
hunter==2.0.2
|
||||
hypothesis==3.66.1
|
||||
Flask==0.12.2
|
||||
glob2==0.5
|
||||
httpbin==0.5.0
|
||||
hunter==1.4.1
|
||||
hypothesis==3.11.6
|
||||
itsdangerous==0.24
|
||||
# Jinja2==2.10
|
||||
Mako==1.0.7
|
||||
# Jinja2==2.9.6
|
||||
Mako==1.0.6
|
||||
# MarkupSafe==1.0
|
||||
more-itertools==4.2.0
|
||||
parse==1.8.4
|
||||
parse-type==0.4.2
|
||||
pluggy==0.6.0
|
||||
py==1.5.4
|
||||
py-cpuinfo==4.0.0
|
||||
pytest==3.6.3
|
||||
pytest-bdd==2.21.0
|
||||
pytest-benchmark==3.1.1
|
||||
parse==1.8.2
|
||||
parse-type==0.3.4
|
||||
py==1.4.34
|
||||
pytest==3.1.2
|
||||
pytest-bdd==2.18.2
|
||||
pytest-benchmark==3.0.0
|
||||
pytest-catchlog==1.2.2
|
||||
pytest-cov==2.5.1
|
||||
pytest-faulthandler==1.5.0
|
||||
pytest-instafail==0.4.0
|
||||
pytest-mock==1.10.0
|
||||
pytest-qt==2.4.1
|
||||
pytest-faulthandler==1.3.1
|
||||
pytest-instafail==0.3.0
|
||||
pytest-mock==1.6.0
|
||||
pytest-qt==2.1.0
|
||||
pytest-repeat==0.4.1
|
||||
pytest-rerunfailures==4.1
|
||||
pytest-travis-fold==1.3.0
|
||||
pytest-xvfb==1.1.0
|
||||
pytest-rerunfailures==2.2
|
||||
pytest-travis-fold==1.2.0
|
||||
pytest-xvfb==1.0.0
|
||||
PyVirtualDisplay==0.2.1
|
||||
six==1.11.0
|
||||
vulture==0.28
|
||||
Werkzeug==0.14.1
|
||||
six==1.10.0
|
||||
vulture==0.14
|
||||
Werkzeug==0.12.2
|
||||
|
||||
@@ -3,10 +3,12 @@ cheroot
|
||||
coverage
|
||||
Flask
|
||||
hunter
|
||||
httpbin
|
||||
hypothesis
|
||||
pytest
|
||||
pytest-bdd
|
||||
pytest-benchmark
|
||||
pytest-catchlog
|
||||
pytest-cov
|
||||
pytest-faulthandler
|
||||
pytest-instafail
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
pluggy==0.6.0
|
||||
py==1.5.4
|
||||
six==1.11.0
|
||||
tox==3.1.1
|
||||
virtualenv==16.0.0
|
||||
pluggy==0.4.0
|
||||
py==1.4.34
|
||||
tox==2.7.0
|
||||
virtualenv==15.1.0
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
tox
|
||||
|
||||
# The latest tox release still depends on pluggy < 0.4...
|
||||
pluggy==0.4.0
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
vulture==0.28
|
||||
vulture==0.14
|
||||
|
||||
@@ -133,24 +133,18 @@ echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
file_to_cast=${tmpdir}/qutecast
|
||||
program_=$(command -v castnow)
|
||||
|
||||
if [[ "${program_}" == "" ]]; then
|
||||
msg error "castnow can't be found..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# kill any running instance of castnow
|
||||
pkill -f "${program_}"
|
||||
pkill -f /usr/bin/castnow
|
||||
|
||||
# start youtube download in stream mode (-o -) into temporary file
|
||||
youtube-dl -qo - "$1" > "${file_to_cast}" &
|
||||
youtube-dl -qo - "$1" > ${file_to_cast} &
|
||||
ytdl_pid=$!
|
||||
|
||||
msg info "Casting $1" >> "$QUTE_FIFO"
|
||||
# start castnow in stream mode to cast on ChromeCast
|
||||
tail -F "${file_to_cast}" | ${program_} -
|
||||
tail -F "${file_to_cast}" | castnow -
|
||||
|
||||
# cleanup remaining background process and file on disk
|
||||
kill ${ytdl_pid}
|
||||
rm -rf "${tmpdir}"
|
||||
rm -rf ${tmpdir}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
# If run from qutebrowser as a userscript, it runs :open on the URL
|
||||
# If not, it opens a new qutebrowser window at the URL
|
||||
#
|
||||
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
|
||||
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
|
||||
# :bind o spawn --userscript dmenu_qutebrowser
|
||||
#
|
||||
# Use the hotkey to open in new tab/window, press 'o' to open URL in current tab/window
|
||||
@@ -41,7 +41,7 @@
|
||||
[ -z "$QUTE_URL" ] && QUTE_URL='http://google.com'
|
||||
|
||||
url=$(echo "$QUTE_URL" | cat - "$QUTE_CONFIG_DIR/quickmarks" "$QUTE_DATA_DIR/history" | dmenu -l 15 -p qutebrowser)
|
||||
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | grep -E "https?:" || echo "$url")
|
||||
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | egrep "https?:" || echo "$url")
|
||||
|
||||
[ -z "${url// }" ] && exit
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -euo pipefail
|
||||
#
|
||||
# Behavior:
|
||||
# Userscript for qutebrowser which will take the raw JSON text of the current
|
||||
# page, format it using `jq`, will add syntax highlighting using `pygments`,
|
||||
# and open the syntax highlighted pretty printed html in a new tab. If the file
|
||||
# is larger than 10MB then this script will only indent the json and will forego
|
||||
# syntax highlighting using pygments.
|
||||
#
|
||||
# In order to use this script, just start it using `spawn --userscript` from
|
||||
# qutebrowser. I recommend using an alias, e.g. put this in the
|
||||
# [alias]-section of qutebrowser.conf:
|
||||
#
|
||||
# json = spawn --userscript /path/to/json_format
|
||||
#
|
||||
# Note that the color style defaults to monokai, but a different pygments style
|
||||
# can be passed as the first parameter to the script. A full list of the pygments
|
||||
# styles can be found at: https://help.farbox.com/pygments.html
|
||||
#
|
||||
# Bryan Gilbert, 2017
|
||||
|
||||
# do not run pygmentize on files larger than this amount of bytes
|
||||
MAX_SIZE_PRETTIFY=10485760 # 10 MB
|
||||
# default style to monokai if none is provided
|
||||
STYLE=${1:-monokai}
|
||||
|
||||
TEMP_FILE="$(mktemp)"
|
||||
jq . "$QUTE_TEXT" >"$TEMP_FILE"
|
||||
|
||||
# try GNU stat first and then OSX stat if the former fails
|
||||
FILE_SIZE=$(
|
||||
stat --printf="%s" "$TEMP_FILE" 2>/dev/null ||
|
||||
stat -f%z "$TEMP_FILE" 2>/dev/null
|
||||
)
|
||||
if [ "$FILE_SIZE" -lt "$MAX_SIZE_PRETTIFY" ]; then
|
||||
pygmentize -l json -f html -O full,style="$STYLE" <"$TEMP_FILE" >"${TEMP_FILE}_"
|
||||
mv -f "${TEMP_FILE}_" "$TEMP_FILE"
|
||||
fi
|
||||
|
||||
# send the command to qutebrowser to open the new file containing the formatted json
|
||||
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Qutebrowser userscript scraping the current web page for DOIs and downloading
|
||||
corresponding bibtex information.
|
||||
|
||||
Set the environment variable 'QUTE_BIB_FILEPATH' to indicate the path to
|
||||
download to. Otherwise, bibtex information is downloaded to '/tmp' and hence
|
||||
deleted at reboot.
|
||||
|
||||
Installation: see qute://help/userscripts.html
|
||||
|
||||
Inspired by
|
||||
https://ocefpaf.github.io/python4oceanographers/blog/2014/05/19/doi2bibtex/
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import re
|
||||
from collections import Counter
|
||||
from urllib import parse as url_parse
|
||||
from urllib import request as url_request
|
||||
|
||||
|
||||
FIFO_PATH = os.getenv("QUTE_FIFO")
|
||||
|
||||
def message_fifo(message, level="warning"):
|
||||
"""Send message to qutebrowser FIFO. The level must be one of 'info',
|
||||
'warning' (default) or 'error'."""
|
||||
with open(FIFO_PATH, "w") as fifo:
|
||||
fifo.write("message-{} '{}'".format(level, message))
|
||||
|
||||
|
||||
source = os.getenv("QUTE_TEXT")
|
||||
with open(source) as f:
|
||||
text = f.read()
|
||||
|
||||
# find DOIs on page using regex
|
||||
dval = re.compile(r'(10\.(\d)+/([^(\s\>\"\<)])+)')
|
||||
# https://stackoverflow.com/a/10324802/3865876, too strict
|
||||
# dval = re.compile(r'\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'<>])\S)+)\b')
|
||||
dois = dval.findall(text)
|
||||
dois = Counter(e[0] for e in dois)
|
||||
try:
|
||||
doi = dois.most_common(1)[0][0]
|
||||
except IndexError:
|
||||
message_fifo("No DOIs found on page")
|
||||
sys.exit()
|
||||
message_fifo("Found {} DOIs on page, selecting {}".format(len(dois), doi),
|
||||
level="info")
|
||||
|
||||
# get bibtex data corresponding to DOI
|
||||
url = "http://dx.doi.org/" + url_parse.quote(doi)
|
||||
headers = dict(Accept='text/bibliography; style=bibtex')
|
||||
request = url_request.Request(url, headers=headers)
|
||||
response = url_request.urlopen(request)
|
||||
status_code = response.getcode()
|
||||
if status_code >= 400:
|
||||
message_fifo("Request returned {}".format(status_code))
|
||||
sys.exit()
|
||||
|
||||
# obtain content and format it
|
||||
bibtex = response.read().decode("utf-8").strip()
|
||||
bibtex = bibtex.replace(" ", "\n ", 1).\
|
||||
replace("}, ", "},\n ").replace("}}", "}\n}")
|
||||
|
||||
# append to file
|
||||
bib_filepath = os.getenv("QUTE_BIB_FILEPATH", "/tmp/qute.bib")
|
||||
with open(bib_filepath, "a") as f:
|
||||
f.write(bibtex + "\n\n")
|
||||
@@ -12,7 +12,7 @@
|
||||
# - rofi (in a recent version)
|
||||
# - xdg-open and xdg-mime
|
||||
# - You should configure qutebrowser to download files to a single directory
|
||||
# - It comes in handy if you enable downloads.remove_finished. If you want to
|
||||
# - It comes in handy if you enable remove-finished-downloads. If you want to
|
||||
# see the recent downloads, just press "sd".
|
||||
#
|
||||
# Thorsten Wißmann, 2015 (thorsten` on freenode)
|
||||
@@ -52,7 +52,7 @@ die() {
|
||||
if ! [ -d "$DOWNLOAD_DIR" ] ; then
|
||||
die "Download directory »$DOWNLOAD_DIR« not found!"
|
||||
fi
|
||||
if ! command -v "${ROFI_CMD}" > /dev/null ; then
|
||||
if ! which "${ROFI_CMD}" > /dev/null ; then
|
||||
die "Rofi command »${ROFI_CMD}« not found in PATH!"
|
||||
fi
|
||||
|
||||
@@ -76,7 +76,6 @@ crop-first-column() {
|
||||
ls-files() {
|
||||
# add the slash at the end of the download dir enforces to follow the
|
||||
# symlink, if the DOWNLOAD_DIR itself is a symlink
|
||||
# shellcheck disable=SC2010
|
||||
ls -Q --quoting-style escape -h -o -1 -A -t "${DOWNLOAD_DIR}/" \
|
||||
| grep '^[-]' \
|
||||
| cut -d' ' -f3- \
|
||||
@@ -92,10 +91,10 @@ if [ "${#entries[@]}" -eq 0 ] ; then
|
||||
die "Download directory »${DOWNLOAD_DIR}« empty"
|
||||
fi
|
||||
|
||||
line=$(printf '%s\n' "${entries[@]}" \
|
||||
line=$(printf "%s\n" "${entries[@]}" \
|
||||
| crop-first-column 55 \
|
||||
| column -s $'\t' -t \
|
||||
| $ROFI_CMD "${rofi_default_args[@]}" "$ROFI_ARGS") || true
|
||||
| $ROFI_CMD "${rofi_default_args[@]}" $ROFI_ARGS) || true
|
||||
if [ -z "$line" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
# Opens all links to feeds defined in the head of a site
|
||||
#
|
||||
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
|
||||
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
|
||||
# :bind gF spawn --userscript openfeeds
|
||||
#
|
||||
# Use the hotkey to open the feeds in new tab/window, press 'gF' to open
|
||||
|
||||
@@ -64,7 +64,7 @@ die() {
|
||||
javascript_escape() {
|
||||
# print the first argument in an escaped way, such that it can safely
|
||||
# be used within javascripts double quotes
|
||||
sed "s,[\\\\'\"],\\\\&,g" <<< "$1"
|
||||
sed "s,[\\\'\"],\\\&,g" <<< "$1"
|
||||
}
|
||||
|
||||
# ======================================================= #
|
||||
@@ -178,7 +178,7 @@ choose_entry_menu() {
|
||||
if [ "$nr" -eq 1 ] && ! ((menu_if_one_entry)) ; then
|
||||
file="${files[0]}"
|
||||
else
|
||||
file=$( printf '%s\n' "${files[@]}" | "${MENU_COMMAND[@]}" )
|
||||
file=$( printf "%s\n" "${files[@]}" | "${MENU_COMMAND[@]}" )
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ choose_entry_rofi() {
|
||||
}
|
||||
|
||||
choose_entry_zenity() {
|
||||
MENU_COMMAND=( zenity --list --title "qutebrowser password fill"
|
||||
MENU_COMMAND=( zenity --list --title "Qutebrowser password fill"
|
||||
--text "Pick the password entry:"
|
||||
--column "Name" )
|
||||
choose_entry_menu || true
|
||||
@@ -199,7 +199,7 @@ choose_entry_zenity_radio() {
|
||||
zenity_helper() {
|
||||
awk '{ print $0 ; print $0 }' \
|
||||
| zenity --list --radiolist \
|
||||
--title "qutebrowser password fill" \
|
||||
--title "Qutebrowser password fill" \
|
||||
--text "Pick the password entry:" \
|
||||
--column " " --column "Name"
|
||||
}
|
||||
@@ -220,7 +220,7 @@ user_pattern='^(user|username|login): '
|
||||
GPG_OPTS=( "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" )
|
||||
GPG="gpg"
|
||||
export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
|
||||
command -v gpg2 &>/dev/null && GPG="gpg2"
|
||||
which gpg2 &>/dev/null && GPG="gpg2"
|
||||
[[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" )
|
||||
|
||||
pass_backend() {
|
||||
@@ -236,7 +236,7 @@ pass_backend() {
|
||||
if ((match_line)) ; then
|
||||
# add entries with matching URL-tag
|
||||
while read -r -d "" passfile ; do
|
||||
if $GPG "${GPG_OPTS[@]}" -d "$passfile" \
|
||||
if $GPG "${GPG_OPTS}" -d "$passfile" \
|
||||
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
|
||||
then
|
||||
passfile="${passfile#$PREFIX}"
|
||||
@@ -269,7 +269,7 @@ pass_backend() {
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done < <($GPG "${GPG_OPTS[@]}" -d "$path" )
|
||||
done < <($GPG "${GPG_OPTS}" -d "$path" )
|
||||
}
|
||||
}
|
||||
# =======================================================
|
||||
@@ -283,8 +283,8 @@ secret_backend() {
|
||||
query_entries() {
|
||||
local domain="$1"
|
||||
while read -r line ; do
|
||||
if [[ "$line" == "attribute.username = "* ]] ; then
|
||||
files+=("$domain ${line:21}")
|
||||
if [[ "$line" =~ "attribute.username = " ]] ; then
|
||||
files+=("$domain ${line#${BASH_REMATCH[0]}}")
|
||||
fi
|
||||
done < <( secret-tool search --unlock --all domain "$domain" 2>&1 )
|
||||
}
|
||||
@@ -303,7 +303,6 @@ pass_backend
|
||||
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
|
||||
PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
|
||||
if [ -f "$PWFILL_CONFIG" ] ; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$PWFILL_CONFIG"
|
||||
fi
|
||||
init
|
||||
@@ -312,7 +311,7 @@ simplify_url "$QUTE_URL"
|
||||
query_entries "${simple_url}"
|
||||
no_entries_found
|
||||
# remove duplicates
|
||||
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort | uniq )
|
||||
mapfile -t files < <(printf "%s\n" "${files[@]}" | sort | uniq )
|
||||
choose_entry
|
||||
if [ -z "$file" ] ; then
|
||||
# choose_entry didn't want any of these entries
|
||||
@@ -328,17 +327,6 @@ open_entry "$file"
|
||||
|
||||
js() {
|
||||
cat <<EOF
|
||||
function isVisible(elem) {
|
||||
var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
|
||||
|
||||
if (style.getPropertyValue("visibility") !== "visible" ||
|
||||
style.getPropertyValue("display") === "none" ||
|
||||
style.getPropertyValue("opacity") === "0") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
|
||||
};
|
||||
function hasPasswordField(form) {
|
||||
var inputs = form.getElementsByTagName("input");
|
||||
for (var j = 0; j < inputs.length; j++) {
|
||||
@@ -353,7 +341,7 @@ cat <<EOF
|
||||
var inputs = form.getElementsByTagName("input");
|
||||
for (var j = 0; j < inputs.length; j++) {
|
||||
var input = inputs[j];
|
||||
if (isVisible(input) && (input.type == "text" || input.type == "email")) {
|
||||
if (input.type == "text" || input.type == "email") {
|
||||
input.focus();
|
||||
input.value = "$(javascript_escape "${username}")";
|
||||
input.blur();
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# 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/>.
|
||||
|
||||
"""This userscript allows for insertion of usernames and passwords from keepass
|
||||
databases using pykeepass. Since it is a userscript, it must be run from
|
||||
qutebrowser.
|
||||
|
||||
A sample invocation of this script is:
|
||||
|
||||
:spawn --userscript qute-keepass -p ~/KeePassFiles/MainDatabase.kdbx
|
||||
|
||||
And a sample binding
|
||||
|
||||
:bind --mode=insert <ctrl-i> spawn --userscript qute-keepass -p ~/KeePassFiles/MainDatabase.kdbx
|
||||
|
||||
-p or --path is a required argument.
|
||||
|
||||
--keyfile-path allows you to specify a keepass keyfile. If you only use a
|
||||
keyfile, also add --no-password as well. Specifying --no-password without
|
||||
--keyfile-path will lead to an error.
|
||||
|
||||
login information is inserted using :insert-text and :fake-key <Tab>, which
|
||||
means you must have a cursor in position before initiating this userscript. If
|
||||
you do not do this, you will get 'element not editable' errors.
|
||||
|
||||
If keepass takes a while to open the DB, you might want to consider reducing
|
||||
the number of transform rounds in your database settings.
|
||||
|
||||
Dependencies: pykeepass (in python3), PyQt5. Without pykeepass, you will get an
|
||||
exit code of 100.
|
||||
|
||||
********************!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!******************
|
||||
|
||||
WARNING: The login details are viewable as plaintext in qutebrowser's debug log
|
||||
(qute://log) and could be compromised if you decide to submit a crash report!
|
||||
|
||||
********************!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!******************
|
||||
|
||||
"""
|
||||
|
||||
# pylint: disable=bad-builtin
|
||||
|
||||
import argparse
|
||||
import enum
|
||||
import functools
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtWidgets import QApplication, QInputDialog, QLineEdit
|
||||
|
||||
try:
|
||||
import pykeepass
|
||||
except ImportError as e:
|
||||
print("pykeepass not found: {}".format(str(e)), file=sys.stderr)
|
||||
|
||||
# Since this is a common error, try to print it to the FIFO if we can.
|
||||
if 'QUTE_FIFO' in os.environ:
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
|
||||
fifo.write('message-error "pykeepass failed to be imported."\n')
|
||||
fifo.flush()
|
||||
sys.exit(100)
|
||||
|
||||
argument_parser = argparse.ArgumentParser(
|
||||
description="Fill passwords using keepass.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__)
|
||||
argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
|
||||
argument_parser.add_argument('--path', '-p', required=True,
|
||||
help='Path to the keepass db.')
|
||||
argument_parser.add_argument('--keyfile-path', '-k', default=None,
|
||||
help='Path to a keepass keyfile')
|
||||
argument_parser.add_argument(
|
||||
'--no-password', action='store_true',
|
||||
help='Supply if no password is required to unlock this database. '
|
||||
'Only allowed with --keyfile-path')
|
||||
argument_parser.add_argument(
|
||||
'--dmenu-invocation', '-d', default='dmenu',
|
||||
help='Invocation used to execute a dmenu-provider')
|
||||
argument_parser.add_argument(
|
||||
'--dmenu-format', '-f', default='{title}: {username}',
|
||||
help='Format string for keys to display in dmenu.'
|
||||
' Must generate a unique string.')
|
||||
argument_parser.add_argument(
|
||||
'--no-insert-mode', '-n', dest='insert_mode', action='store_false',
|
||||
help="Don't automatically enter insert mode")
|
||||
argument_parser.add_argument(
|
||||
'--io-encoding', '-i', default='UTF-8',
|
||||
help='Encoding used to communicate with subprocesses')
|
||||
group = argument_parser.add_mutually_exclusive_group()
|
||||
group.add_argument('--username-fill-only', '-e',
|
||||
action='store_true', help='Only insert username')
|
||||
group.add_argument('--password-fill-only', '-w',
|
||||
action='store_true', help='Only insert password')
|
||||
|
||||
CMD_DELAY = 50
|
||||
|
||||
|
||||
class ExitCodes(enum.IntEnum):
|
||||
"""Stores various exit codes groups to use."""
|
||||
SUCCESS = 0
|
||||
FAILURE = 1
|
||||
# 1 is automatically used if Python throws an exception
|
||||
NO_CANDIDATES = 2
|
||||
USER_QUIT = 3
|
||||
DB_OPEN_FAIL = 4
|
||||
|
||||
INTERNAL_ERROR = 10
|
||||
|
||||
|
||||
def qute_command(command):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
|
||||
fifo.write(command + '\n')
|
||||
fifo.flush()
|
||||
|
||||
|
||||
def stderr(to_print):
|
||||
"""Extra functionality to echo out errors to qb ui."""
|
||||
print(to_print, file=sys.stderr)
|
||||
qute_command('message-error "{}"'.format(to_print))
|
||||
|
||||
|
||||
def dmenu(items, invocation, encoding):
|
||||
"""Runs dmenu with given arguments."""
|
||||
command = shlex.split(invocation)
|
||||
process = subprocess.run(command, input='\n'.join(items).encode(encoding),
|
||||
stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(encoding).strip()
|
||||
|
||||
|
||||
def get_password():
|
||||
"""Get a keepass db password from user."""
|
||||
_app = QApplication(sys.argv)
|
||||
text, ok = QInputDialog.getText(
|
||||
None, "KeePass DB Password",
|
||||
"Please enter your KeePass Master Password",
|
||||
QLineEdit.Password)
|
||||
if not ok:
|
||||
stderr('Password Prompt Rejected.')
|
||||
sys.exit(ExitCodes.USER_QUIT)
|
||||
return text
|
||||
|
||||
|
||||
def find_candidates(args, host):
|
||||
"""Finds candidates that match host"""
|
||||
file_path = os.path.expanduser(args.path)
|
||||
|
||||
# TODO find a way to keep the db open, so we don't open (and query
|
||||
# password) it every time
|
||||
|
||||
pw = None
|
||||
if not args.no_password:
|
||||
pw = get_password()
|
||||
|
||||
kf = args.keyfile_path
|
||||
if kf:
|
||||
kf = os.path.expanduser(kf)
|
||||
|
||||
try:
|
||||
kp = pykeepass.PyKeePass(file_path, password=pw, keyfile=kf)
|
||||
except Exception as e:
|
||||
stderr("There was an error opening the DB: {}".format(str(e)))
|
||||
|
||||
return kp.find_entries(url="{}{}{}".format(".*", host, ".*"), regex=True)
|
||||
|
||||
|
||||
def candidate_to_str(args, candidate):
|
||||
"""Turns candidate into a human readable string for dmenu"""
|
||||
return args.dmenu_format.format(title=candidate.title,
|
||||
url=candidate.url,
|
||||
username=candidate.username,
|
||||
path=candidate.path,
|
||||
uuid=candidate.uuid)
|
||||
|
||||
|
||||
def candidate_to_secret(candidate):
|
||||
"""Turns candidate into a generic (user, password) tuple"""
|
||||
return (candidate.username, candidate.password)
|
||||
|
||||
|
||||
def run(args):
|
||||
"""Runs qute-keepass"""
|
||||
if not args.url:
|
||||
argument_parser.print_help()
|
||||
return ExitCodes.FAILURE
|
||||
|
||||
url_host = QUrl(args.url).host()
|
||||
|
||||
if not url_host:
|
||||
stderr('{} was not parsed as a valid URL!'.format(args.url))
|
||||
return ExitCodes.INTERNAL_ERROR
|
||||
|
||||
# Find candidates matching the host of the given URL
|
||||
candidates = find_candidates(args, url_host)
|
||||
if not candidates:
|
||||
stderr('No candidates for URL {!r} found!'.format(args.url))
|
||||
return ExitCodes.NO_CANDIDATES
|
||||
|
||||
# Create a map so we can get turn the resulting string from dmenu back into
|
||||
# a candidate
|
||||
candidates_strs = list(map(functools.partial(candidate_to_str, args),
|
||||
candidates))
|
||||
candidates_map = dict(zip(candidates_strs, candidates))
|
||||
|
||||
if len(candidates) == 1:
|
||||
selection = candidates.pop()
|
||||
else:
|
||||
selection = dmenu(candidates_strs,
|
||||
args.dmenu_invocation,
|
||||
args.io_encoding)
|
||||
|
||||
if selection not in candidates_map:
|
||||
stderr("'{}' was not a valid entry!").format(selection)
|
||||
return ExitCodes.USER_QUIT
|
||||
|
||||
selection = candidates_map[selection]
|
||||
|
||||
username, password = candidate_to_secret(selection)
|
||||
|
||||
insert_mode = ';; enter-mode insert' if args.insert_mode else ''
|
||||
if args.username_fill_only:
|
||||
qute_command('insert-text {}{}'.format(username, insert_mode))
|
||||
elif args.password_fill_only:
|
||||
qute_command('insert-text {}{}'.format(password, insert_mode))
|
||||
else:
|
||||
# Enter username and password using insert-key and fake-key <Tab>
|
||||
# (which supports more passwords than fake-key only), then switch back
|
||||
# into insert-mode, so the form can be directly submitted by hitting
|
||||
# enter afterwards. It dosen't matter when we go into insert mode, but
|
||||
# the other commands need to be be executed sequentially, so we add
|
||||
# delays with later.
|
||||
qute_command('insert-text {} ;;'
|
||||
'later {} fake-key <Tab> ;;'
|
||||
'later {} insert-text {}{}'
|
||||
.format(username, CMD_DELAY,
|
||||
CMD_DELAY * 2, password, insert_mode))
|
||||
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
arguments = argument_parser.parse_args()
|
||||
sys.exit(run(arguments))
|
||||
@@ -1,172 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017 Chris Braun (cryzed) <cryzed@googlemail.com>
|
||||
# Adapted for LastPass by Wayne Cheng (welps) <waynethecheng@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 bjy
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
Insert login information using lastpass CLI and a dmenu-compatible application (e.g. dmenu, rofi -dmenu, ...).
|
||||
A short demonstration can be seen here: https://i.imgur.com/zA61NrF.gifv.
|
||||
"""
|
||||
|
||||
USAGE = """The domain of the site has to be in the name of the LastPass entry, for example: "github.com/cryzed" or
|
||||
"websites/github.com". The login information is inserted by emulating key events using qutebrowser's fake-key command in this manner:
|
||||
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms.
|
||||
|
||||
You must log into LastPass CLI using `lpass login <email>` prior to use of this script. The LastPass CLI agent only holds your master password for an hour by default. If you wish to change this, please see `man lpass`.
|
||||
|
||||
To use in qutebrowser, run: `spawn --userscript qute-lastpass`
|
||||
"""
|
||||
|
||||
EPILOG = """Dependencies: tldextract (Python 3 module), LastPass CLI (1.3 or newer)
|
||||
|
||||
WARNING: The login details are viewable as plaintext in qutebrowser's debug log (qute://log) and might be shared if
|
||||
you decide to submit a crash report!"""
|
||||
|
||||
import argparse
|
||||
import enum
|
||||
import fnmatch
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import json
|
||||
import tldextract
|
||||
|
||||
argument_parser = argparse.ArgumentParser(
|
||||
description=__doc__, usage=USAGE, epilog=EPILOG)
|
||||
argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
|
||||
argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu',
|
||||
help='Invocation used to execute a dmenu-provider')
|
||||
argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false',
|
||||
help="Don't automatically enter insert mode")
|
||||
argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
|
||||
help='Encoding used to communicate with subprocesses')
|
||||
argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
|
||||
help='Merge pass candidates for fully-qualified and registered domain name')
|
||||
group = argument_parser.add_mutually_exclusive_group()
|
||||
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')
|
||||
|
||||
stderr = functools.partial(print, file=sys.stderr)
|
||||
|
||||
class ExitCodes(enum.IntEnum):
|
||||
SUCCESS = 0
|
||||
FAILURE = 1
|
||||
# 1 is automatically used if Python throws an exception
|
||||
NO_PASS_CANDIDATES = 2
|
||||
COULD_NOT_MATCH_USERNAME = 3
|
||||
COULD_NOT_MATCH_PASSWORD = 4
|
||||
|
||||
def qute_command(command):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
|
||||
fifo.write(command + '\n')
|
||||
fifo.flush()
|
||||
|
||||
def pass_(domain, encoding):
|
||||
args = ['lpass', 'show', '-x', '-j', '-G', '.*{:s}.*'.format(domain)]
|
||||
process = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
err = process.stderr.decode(encoding).strip()
|
||||
if err:
|
||||
msg = "LastPass CLI returned for {:s} - {:s}".format(domain, err)
|
||||
stderr(msg)
|
||||
return '[]'
|
||||
|
||||
out = process.stdout.decode(encoding).strip()
|
||||
|
||||
return out
|
||||
|
||||
def dmenu(items, invocation, encoding):
|
||||
command = shlex.split(invocation)
|
||||
process = subprocess.run(command, input='\n'.join(
|
||||
items).encode(encoding), stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(encoding).strip()
|
||||
|
||||
|
||||
def fake_key_raw(text):
|
||||
for character in text:
|
||||
# Escape all characters by default, space requires special handling
|
||||
sequence = '" "' if character == ' ' else '\{}'.format(character)
|
||||
qute_command('fake-key {}'.format(sequence))
|
||||
|
||||
|
||||
def main(arguments):
|
||||
if not arguments.url:
|
||||
argument_parser.print_help()
|
||||
return ExitCodes.FAILURE
|
||||
|
||||
extract_result = tldextract.extract(arguments.url)
|
||||
|
||||
# Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains),
|
||||
# the registered domain name and finally: the IPv4 address if that's what
|
||||
# the URL represents
|
||||
candidates = []
|
||||
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.subdomain + extract_result.domain, extract_result.domain, extract_result.ipv4]):
|
||||
target_candidates = json.loads(pass_(target, arguments.io_encoding))
|
||||
if not target_candidates:
|
||||
continue
|
||||
|
||||
candidates = candidates + target_candidates
|
||||
if not arguments.merge_candidates:
|
||||
break
|
||||
else:
|
||||
if not candidates:
|
||||
stderr('No pass candidates for URL {!r} found!'.format(
|
||||
arguments.url))
|
||||
return ExitCodes.NO_PASS_CANDIDATES
|
||||
|
||||
if len(candidates) == 1:
|
||||
selection = candidates.pop()
|
||||
else:
|
||||
choices = ["{:s} | {:s} | {:s} | {:s}".format(c["id"], c["name"], c["url"], c["username"]) for c in candidates]
|
||||
choice = dmenu(choices, arguments.dmenu_invocation, arguments.io_encoding)
|
||||
choiceId = choice.split("|")[0].strip()
|
||||
selection = next((c for (i, c) in enumerate(candidates) if c["id"] == choiceId), None)
|
||||
|
||||
# Nothing was selected, simply return
|
||||
if not selection:
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
username = selection["username"]
|
||||
password = selection["password"]
|
||||
|
||||
if arguments.username_only:
|
||||
fake_key_raw(username)
|
||||
elif arguments.password_only:
|
||||
fake_key_raw(password)
|
||||
else:
|
||||
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
|
||||
# back into insert-mode, so the form can be directly submitted by
|
||||
# hitting enter afterwards
|
||||
fake_key_raw(username)
|
||||
qute_command('fake-key <Tab>')
|
||||
fake_key_raw(password)
|
||||
|
||||
if arguments.insert_mode:
|
||||
qute_command('enter-mode insert')
|
||||
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
arguments = argument_parser.parse_args()
|
||||
sys.exit(main(arguments))
|
||||
@@ -1,207 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017 Chris Braun (cryzed) <cryzed@googlemail.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/>.
|
||||
|
||||
"""
|
||||
Insert login information using pass and a dmenu-compatible application (e.g. dmenu, rofi -dmenu, ...). A short
|
||||
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
|
||||
"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:
|
||||
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms.
|
||||
|
||||
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.
|
||||
|
||||
WARNING: The login details are viewable as plaintext in qutebrowser's debug log (qute://log) and might be shared if
|
||||
you decide to submit a crash report!"""
|
||||
|
||||
import argparse
|
||||
import enum
|
||||
import fnmatch
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import tldextract
|
||||
|
||||
argument_parser = argparse.ArgumentParser(description=__doc__, usage=USAGE, epilog=EPILOG)
|
||||
argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
|
||||
argument_parser.add_argument('--password-store', '-p', default=os.path.expanduser('~/.password-store'),
|
||||
help='Path to your pass password-store')
|
||||
argument_parser.add_argument('--username-pattern', '-u', default=r'.*/(.+)',
|
||||
help='Regular expression that matches the username')
|
||||
argument_parser.add_argument('--username-target', '-U', choices=['path', 'secret'], default='path',
|
||||
help='The target for the username regular expression')
|
||||
argument_parser.add_argument('--password-pattern', '-P', default=r'(.*)',
|
||||
help='Regular expression that matches the password')
|
||||
argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu',
|
||||
help='Invocation used to execute a dmenu-provider')
|
||||
argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false',
|
||||
help="Don't automatically enter insert mode")
|
||||
argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
|
||||
help='Encoding used to communicate with subprocesses')
|
||||
argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
|
||||
help='Merge pass candidates for fully-qualified and registered domain name')
|
||||
group = argument_parser.add_mutually_exclusive_group()
|
||||
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('--otp-only', '-o', action='store_true', help='Only insert OTP code')
|
||||
|
||||
stderr = functools.partial(print, file=sys.stderr)
|
||||
|
||||
|
||||
class ExitCodes(enum.IntEnum):
|
||||
SUCCESS = 0
|
||||
FAILURE = 1
|
||||
# 1 is automatically used if Python throws an exception
|
||||
NO_PASS_CANDIDATES = 2
|
||||
COULD_NOT_MATCH_USERNAME = 3
|
||||
COULD_NOT_MATCH_PASSWORD = 4
|
||||
|
||||
|
||||
def qute_command(command):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
|
||||
fifo.write(command + '\n')
|
||||
fifo.flush()
|
||||
|
||||
|
||||
def find_pass_candidates(domain, password_store_path):
|
||||
candidates = []
|
||||
for path, directories, file_names in os.walk(password_store_path):
|
||||
if directories or domain not in path.split(os.path.sep):
|
||||
continue
|
||||
|
||||
# Strip password store path prefix to get the relative pass path
|
||||
pass_path = path[len(password_store_path) + 1:]
|
||||
secrets = fnmatch.filter(file_names, '*.gpg')
|
||||
candidates.extend(os.path.join(pass_path, os.path.splitext(secret)[0]) for secret in secrets)
|
||||
return candidates
|
||||
|
||||
|
||||
def _run_pass(command, encoding):
|
||||
process = subprocess.run(command, stdout=subprocess.PIPE)
|
||||
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):
|
||||
command = shlex.split(invocation)
|
||||
process = subprocess.run(command, input='\n'.join(items).encode(encoding), stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(encoding).strip()
|
||||
|
||||
|
||||
def fake_key_raw(text):
|
||||
for character in text:
|
||||
# Escape all characters by default, space requires special handling
|
||||
sequence = '" "' if character == ' ' else '\{}'.format(character)
|
||||
qute_command('fake-key {}'.format(sequence))
|
||||
|
||||
|
||||
def main(arguments):
|
||||
if not arguments.url:
|
||||
argument_parser.print_help()
|
||||
return ExitCodes.FAILURE
|
||||
|
||||
extract_result = tldextract.extract(arguments.url)
|
||||
|
||||
# Expand potential ~ in paths, since this script won't be called from a shell that does it for us
|
||||
password_store_path = os.path.expanduser(arguments.password_store)
|
||||
|
||||
# Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains),
|
||||
# the registered domain name and finally: the IPv4 address if that's what the URL represents
|
||||
candidates = set()
|
||||
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4]):
|
||||
target_candidates = find_pass_candidates(target, password_store_path)
|
||||
if not target_candidates:
|
||||
continue
|
||||
|
||||
candidates.update(target_candidates)
|
||||
if not arguments.merge_candidates:
|
||||
break
|
||||
else:
|
||||
if not candidates:
|
||||
stderr('No pass candidates for URL {!r} found!'.format(arguments.url))
|
||||
return ExitCodes.NO_PASS_CANDIDATES
|
||||
|
||||
selection = candidates.pop() if len(candidates) == 1 else dmenu(sorted(candidates), arguments.dmenu_invocation,
|
||||
arguments.io_encoding)
|
||||
# Nothing was selected, simply return
|
||||
if not selection:
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
secret = pass_(selection, arguments.io_encoding)
|
||||
|
||||
# Match username
|
||||
target = selection if arguments.username_target == 'path' else secret
|
||||
match = re.match(arguments.username_pattern, target)
|
||||
if not match:
|
||||
stderr('Failed to match username pattern on {}!'.format(arguments.username_target))
|
||||
return ExitCodes.COULD_NOT_MATCH_USERNAME
|
||||
username = match.group(1)
|
||||
|
||||
# Match password
|
||||
match = re.match(arguments.password_pattern, secret)
|
||||
if not match:
|
||||
stderr('Failed to match password pattern on secret!')
|
||||
return ExitCodes.COULD_NOT_MATCH_PASSWORD
|
||||
password = match.group(1)
|
||||
|
||||
if arguments.username_only:
|
||||
fake_key_raw(username)
|
||||
elif arguments.password_only:
|
||||
fake_key_raw(password)
|
||||
elif arguments.otp_only:
|
||||
otp = pass_otp(selection, arguments.io_encoding)
|
||||
fake_key_raw(otp)
|
||||
else:
|
||||
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
|
||||
# back into insert-mode, so the form can be directly submitted by hitting enter afterwards
|
||||
fake_key_raw(username)
|
||||
qute_command('fake-key <Tab>')
|
||||
fake_key_raw(password)
|
||||
|
||||
if arguments.insert_mode:
|
||||
qute_command('enter-mode insert')
|
||||
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
arguments = argument_parser.parse_args()
|
||||
sys.exit(main(arguments))
|
||||
33
misc/userscripts/qutebrowser_viewsource
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#
|
||||
# This script fetches the unprocessed HTML source for a page and opens it in vim.
|
||||
# :bind gf spawn --userscript qutebrowser_viewsource
|
||||
#
|
||||
# Caveat: Does not use authentication of any kind. Add it in if you want it to.
|
||||
#
|
||||
|
||||
path=$(mktemp --tmpdir qutebrowser_XXXXXXXX.html)
|
||||
|
||||
curl "$QUTE_URL" > "$path"
|
||||
urxvt -e vim "$path"
|
||||
|
||||
rm "$path"
|
||||
@@ -35,12 +35,17 @@ get_selection() {
|
||||
|
||||
# Main
|
||||
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/font
|
||||
[[ -s $confdir/dmenu/font ]] && read -r font < "$confdir"/dmenu/font
|
||||
if [[ -s $confdir/dmenu/font ]]; then
|
||||
read -r font < "$confdir"/dmenu/font
|
||||
fi
|
||||
|
||||
[[ $font ]] && opts+=(-fn "$font")
|
||||
if [[ $font ]]; then
|
||||
opts+=(-fn "$font")
|
||||
fi
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
[[ -s $optsfile ]] && source "$optsfile"
|
||||
if [[ -s $optsfile ]]; then
|
||||
source "$optsfile"
|
||||
fi
|
||||
|
||||
url=$(get_selection)
|
||||
url=${url/*http/http}
|
||||
|
||||
@@ -2,36 +2,20 @@
|
||||
#
|
||||
# Executes python-readability on current page and opens the summary as new tab.
|
||||
#
|
||||
# Depends on the python-readability package, or its fork:
|
||||
#
|
||||
# - https://github.com/buriy/python-readability
|
||||
# - https://github.com/bookieio/breadability
|
||||
#
|
||||
# Usage:
|
||||
# :spawn --userscript readability
|
||||
#
|
||||
from __future__ import absolute_import
|
||||
import codecs, os
|
||||
from readability.readability import Document
|
||||
|
||||
tmpfile = os.path.join(
|
||||
os.environ.get('QUTE_DATA_DIR',
|
||||
os.path.expanduser('~/.local/share/qutebrowser')),
|
||||
'userscripts/readability.html')
|
||||
|
||||
tmpfile=os.path.expanduser('~/.local/share/qutebrowser/userscripts/readability.html')
|
||||
if not os.path.exists(os.path.dirname(tmpfile)):
|
||||
os.makedirs(os.path.dirname(tmpfile))
|
||||
|
||||
with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source:
|
||||
data = source.read()
|
||||
|
||||
try:
|
||||
from breadability.readable import Article as reader
|
||||
doc = reader(data)
|
||||
content = doc.readable
|
||||
except ImportError:
|
||||
from readability import Document
|
||||
doc = Document(data)
|
||||
content = doc.summary().replace('<html>', '<html><head><title>%s</title></head>' % doc.title())
|
||||
doc = Document(source.read())
|
||||
content = doc.summary().replace('<html>', '<html><head><title>%s</title></head>' % doc.title())
|
||||
|
||||
with codecs.open(tmpfile, 'w', 'utf-8') as target:
|
||||
target.write('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')
|
||||
|
||||
@@ -32,7 +32,7 @@ add_feed () {
|
||||
if grep -Fq "$1" "feeds"; then
|
||||
notice "$1 is saved already."
|
||||
else
|
||||
printf '%s\n' "$1" >> "feeds"
|
||||
printf "%s\n" "$1" >> "feeds"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ notice () {
|
||||
|
||||
# Update a database of a feed and open new URLs
|
||||
read_items () {
|
||||
cd read_urls || return 1
|
||||
cd read_urls
|
||||
feed_file="$(echo "$1" | tr -d /)"
|
||||
feed_temp_file="$(mktemp "$feed_file.tmp.XXXXXXXXXX")"
|
||||
feed_new_items="$(mktemp "$feed_file.new.XXXXXXXXXX")"
|
||||
@@ -75,7 +75,7 @@ read_items () {
|
||||
cat "$feed_new_items" >> "$feed_file"
|
||||
sort -o "$feed_file" "$feed_file"
|
||||
rm "$feed_temp_file" "$feed_new_items"
|
||||
fi | while read -r item; do
|
||||
fi | while read item; do
|
||||
echo "open -t $item" > "$QUTE_FIFO"
|
||||
done
|
||||
}
|
||||
@@ -85,7 +85,7 @@ if [ ! -d "$config_dir/read_urls" ]; then
|
||||
mkdir -p "$config_dir/read_urls"
|
||||
fi
|
||||
|
||||
cd "$config_dir" || exit 1
|
||||
cd "$config_dir"
|
||||
|
||||
if [ $# != 0 ]; then
|
||||
for arg in "$@"; do
|
||||
@@ -115,7 +115,7 @@ if < /dev/null grep --help 2>&1 | grep -q -- -a; then
|
||||
text_only="-a"
|
||||
fi
|
||||
|
||||
while read -r feed_url; do
|
||||
while read feed_url; do
|
||||
read_items "$feed_url" &
|
||||
done < "$config_dir/feeds"
|
||||
|
||||
|
||||
@@ -25,10 +25,12 @@
|
||||
[[ $QUTE_MODE == 'hints' ]] && title=$QUTE_SELECTED_TEXT || title=$QUTE_TITLE
|
||||
|
||||
# try to add the task and grab the output
|
||||
if msg="$(task add "$title" "$*" 2>&1)"; then
|
||||
msg="$(task add $title $@ 2>&1)"
|
||||
|
||||
if [[ $? == 0 ]]; then
|
||||
# annotate the new task with the url, send the output back to the browser
|
||||
task +LATEST annotate "$QUTE_URL"
|
||||
echo "message-info '$(echo "$msg" | head -n 1)'" >> "$QUTE_FIFO"
|
||||
echo "message-info '$msg'" >> $QUTE_FIFO
|
||||
else
|
||||
echo "message-error '$(echo "$msg" | head -n 1)'" >> "$QUTE_FIFO"
|
||||
echo "message-error '$msg'" >> $QUTE_FIFO
|
||||
fi
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2018 jnphilipp <mail@jnphilipp.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Change your tor identity.
|
||||
#
|
||||
# Set a hotkey to launch this script, then:
|
||||
# :bind ti spawn --userscript tor_identity PASSWORD
|
||||
#
|
||||
# Use the hotkey to change your tor identity, press 'ti' to change it.
|
||||
# https://stem.torproject.org/faq.html#how-do-i-request-a-new-identity-from-tor
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from stem import Signal
|
||||
from stem.control import Controller
|
||||
except ImportError:
|
||||
if os.getenv('QUTE_FIFO'):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as f:
|
||||
f.write('message-error "Failed to import stem."')
|
||||
else:
|
||||
print('Failed to import stem.')
|
||||
|
||||
|
||||
password = sys.argv[1]
|
||||
with Controller.from_port(port=9051) as controller:
|
||||
controller.authenticate(password)
|
||||
controller.signal(Signal.NEWNYM)
|
||||
if os.getenv('QUTE_FIFO'):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as f:
|
||||
f.write('message-info "Tor identity changed."')
|
||||
else:
|
||||
print('Tor identity changed.')
|
||||
@@ -49,8 +49,8 @@ msg() {
|
||||
|
||||
MPV_COMMAND=${MPV_COMMAND:-mpv}
|
||||
# Warning: spaces in single flags are not supported
|
||||
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl}
|
||||
IFS=" " read -r -a video_command <<< "$MPV_COMMAND $MPV_FLAGS"
|
||||
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl }
|
||||
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
|
||||
|
||||
js() {
|
||||
cat <<EOF
|
||||
@@ -140,4 +140,4 @@ printjs() {
|
||||
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
|
||||
|
||||
msg info "Opening $QUTE_URL with mpv"
|
||||
"${video_command[@]}" "$@" "$QUTE_URL"
|
||||
"${video_command[@]}" "$QUTE_URL"
|
||||
|
||||
33
pytest.ini
@@ -1,14 +1,12 @@
|
||||
[pytest]
|
||||
log_level = NOTSET
|
||||
addopts = --strict -rfEw --faulthandler-timeout=90 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median
|
||||
testpaths = tests
|
||||
addopts = --strict -rfEw --faulthandler-timeout=70 --instafail --pythonwarnings error
|
||||
markers =
|
||||
gui: Tests using the GUI (e.g. spawning widgets)
|
||||
posix: Tests which only can run on a POSIX OS.
|
||||
windows: Tests which only can run on Windows.
|
||||
linux: Tests which only can run on Linux.
|
||||
mac: Tests which only can run on macOS.
|
||||
not_mac: Tests which can not run on macOS.
|
||||
osx: Tests which only can run on OS X.
|
||||
not_osx: Tests which can not run on OS X.
|
||||
not_frozen: Tests which can't be run if sys.frozen is True.
|
||||
no_xvfb: Tests which can't be run with Xvfb.
|
||||
frozen: Tests which can only be run if sys.frozen is True.
|
||||
@@ -16,21 +14,17 @@ markers =
|
||||
end2end: End to end tests which run qutebrowser as subprocess
|
||||
xfail_norun: xfail the test with out running it
|
||||
ci: Tests which should only run on CI.
|
||||
no_ci: Tests which should not run on CI.
|
||||
qtwebengine_todo: Features still missing with QtWebEngine
|
||||
qtwebengine_skip: Tests not applicable with QtWebEngine
|
||||
qtwebkit_skip: Tests not applicable with QtWebKit
|
||||
qtwebkit_ng_xfail: Tests failing with QtWebKit-NG
|
||||
qtwebkit_ng_skip: Tests skipped with QtWebKit-NG
|
||||
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
|
||||
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
|
||||
qtwebengine_osx_xfail: Tests which fail on OS X with QtWebEngine
|
||||
js_prompt: Tests needing to display a javascript prompt
|
||||
this: Used to mark tests during development
|
||||
no_invalid_lines: Don't fail on unparseable lines in end2end tests
|
||||
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
|
||||
issue3572: Tests which are broken with QtWebEngine and Qt 5.10, https://github.com/qutebrowser/qutebrowser/issues/3572
|
||||
qtbug60673: Tests which are broken if the conversion from orange selection to real selection is flaky
|
||||
fake_os: Fake utils.is_* to a fake operating system
|
||||
unicode_locale: Tests which need an unicode locale to work
|
||||
qtwebkit6021_skip: Tests which would fail on WebKit version 602.1
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
^SpellCheck: .*
|
||||
@@ -49,22 +43,17 @@ qt_log_ignore =
|
||||
^QWaitCondition: Destroyed while threads are still waiting
|
||||
^QXcbXSettings::QXcbXSettings\(QXcbScreen\*\) Failed to get selection owner for XSETTINGS_S atom
|
||||
^QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to .*
|
||||
^QGeoclueMaster error creating GeoclueMasterClient\.
|
||||
^Geoclue error: Process org\.freedesktop\.Geoclue\.Master exited with status 127
|
||||
^QDBusConnection: name 'org.freedesktop.Geoclue.Master' had owner '' but we thought it was ':1.1'
|
||||
^Failed to create Geoclue client interface. Geoclue error: org\.freedesktop\.DBus\.Error\.Disconnected
|
||||
^QObject::connect: Cannot connect \(null\)::stateChanged\(QNetworkSession::State\) to QNetworkReplyHttpImpl::_q_networkSessionStateChanged\(QNetworkSession::State\)
|
||||
^QXcbClipboard: Cannot transfer data, no data available
|
||||
^load glyph failed
|
||||
^Error when parsing the netrc file
|
||||
^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST=
|
||||
^QPainter::end: Painter ended with \d+ saved states
|
||||
^QSslSocket: cannot resolve .*
|
||||
^QSslSocket: cannot call unresolved function .*
|
||||
^Incompatible version of OpenSSL
|
||||
^QSslSocket: cannot resolve SSLv[23]_(client|server)_method
|
||||
^QQuickWidget::invalidateRenderControl could not make context current
|
||||
^libpng warning: iCCP: known incorrect sRGB profile
|
||||
^inotify_add_watch\(".*"\) failed: "No space left on device"
|
||||
^QSettings::value: Empty key passed
|
||||
^Icon theme ".*" not found
|
||||
^Error receiving trust for a CA certificate
|
||||
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
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Name=qutebrowser
|
||||
GenericName=Web Browser
|
||||
Comment=A keyboard-driven, vim-like browser based on PyQt5
|
||||
Icon=qutebrowser
|
||||
Type=Application
|
||||
Categories=Network;WebBrowser;
|
||||
Exec=qutebrowser %u
|
||||
Terminal=false
|
||||
StartupNotify=false
|
||||
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute;
|
||||
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;
|
||||
Keywords=Browser
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -22,11 +22,11 @@
|
||||
import os.path
|
||||
|
||||
__author__ = "Florian Bruhin"
|
||||
__copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
|
||||
__copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 4, 0)
|
||||
__version_info__ = (0, 11, 1)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -17,29 +17,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Initialization of qutebrowser and application-wide things.
|
||||
|
||||
The run() function will get called once early initialization (in
|
||||
qutebrowser.py/earlyinit.py) is done. See the qutebrowser.py docstring for
|
||||
details about early initialization.
|
||||
|
||||
As we need to access the config before the QApplication is created, we
|
||||
initialize everything the config needs before the QApplication is created, and
|
||||
then leave it in a partially initialized state (no saving, no config errors
|
||||
shown yet).
|
||||
|
||||
We then set up the QApplication object and initialize a few more low-level
|
||||
things.
|
||||
|
||||
After that, init() and _init_modules() take over and initialize the rest.
|
||||
|
||||
After all initialization is done, the qt_mainloop() function is called, which
|
||||
blocks and spins the Qt mainloop.
|
||||
"""
|
||||
"""Initialization of qutebrowser and application-wide things."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import configparser
|
||||
import functools
|
||||
import json
|
||||
import shutil
|
||||
@@ -51,7 +34,7 @@ import tokenize
|
||||
from PyQt5.QtWidgets import QApplication, QWidget
|
||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QWindow
|
||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
|
||||
QObject, QEvent, pyqtSignal, Qt)
|
||||
QObject, QEvent, pyqtSignal)
|
||||
try:
|
||||
import hunter
|
||||
except ImportError:
|
||||
@@ -59,27 +42,22 @@ except ImportError:
|
||||
|
||||
import qutebrowser
|
||||
import qutebrowser.resources
|
||||
from qutebrowser.completion import completiondelegate
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
from qutebrowser.completion.models import instances as completionmodels
|
||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import config, websettings, configfiles, configinit
|
||||
from qutebrowser.config import style, config, websettings, configexc
|
||||
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
||||
qtnetworkdownloads, downloads, greasemonkey)
|
||||
downloads)
|
||||
from qutebrowser.browser.network import proxy
|
||||
from qutebrowser.browser.webkit import cookies, cache
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
from qutebrowser.keyinput import macros
|
||||
from qutebrowser.mainwindow import mainwindow, prompt
|
||||
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
|
||||
crashsignal, earlyinit, sql, cmdhistory,
|
||||
backendproblem)
|
||||
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
|
||||
usertypes, standarddir, error)
|
||||
# pylint: disable=unused-import
|
||||
# We import those to run the cmdutils.register decorators.
|
||||
from qutebrowser.mainwindow.statusbar import command
|
||||
from qutebrowser.misc import utilcmds
|
||||
# pylint: enable=unused-import
|
||||
crashsignal, earlyinit, objects)
|
||||
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
|
||||
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
|
||||
objreg, usertypes, standarddir, error, debug)
|
||||
# We import utilcmds to run the cmdutils.register decorators.
|
||||
|
||||
|
||||
qApp = None
|
||||
@@ -93,13 +71,6 @@ def run(args):
|
||||
quitter = Quitter(args)
|
||||
objreg.register('quitter', quitter)
|
||||
|
||||
log.init.debug("Initializing directories...")
|
||||
standarddir.init(args)
|
||||
utils.preload_resources()
|
||||
|
||||
log.init.debug("Initializing config...")
|
||||
configinit.early_init(args)
|
||||
|
||||
global qApp
|
||||
qApp = Application(args)
|
||||
qApp.setOrganizationName("qutebrowser")
|
||||
@@ -107,6 +78,9 @@ def run(args):
|
||||
qApp.setApplicationVersion(qutebrowser.__version__)
|
||||
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
|
||||
|
||||
log.init.debug("Initializing directories...")
|
||||
standarddir.init(args)
|
||||
|
||||
if args.version:
|
||||
print(version.version())
|
||||
sys.exit(usertypes.Exit.ok)
|
||||
@@ -173,6 +147,8 @@ def init(args, crash_handler):
|
||||
objreg.register('event-filter', event_filter)
|
||||
|
||||
log.init.debug("Connecting signals...")
|
||||
config_obj = objreg.get('config')
|
||||
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
|
||||
qApp.focusChanged.connect(on_focus_changed)
|
||||
|
||||
_process_args(args)
|
||||
@@ -181,7 +157,7 @@ def init(args, crash_handler):
|
||||
QDesktopServices.setUrlHandler('https', open_desktopservices_url)
|
||||
QDesktopServices.setUrlHandler('qute', open_desktopservices_url)
|
||||
|
||||
objreg.get('web-history').import_txt()
|
||||
QTimer.singleShot(10, functools.partial(_init_late_modules, args))
|
||||
|
||||
log.init.debug("Init done!")
|
||||
crash_handler.raise_crashdlg()
|
||||
@@ -207,6 +183,13 @@ def _init_icon():
|
||||
|
||||
def _process_args(args):
|
||||
"""Open startpage etc. and process commandline args."""
|
||||
config_obj = objreg.get('config')
|
||||
for sect, opt, val in args.temp_settings:
|
||||
try:
|
||||
config_obj.set('temp', sect, opt, val)
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
message.error("set: {} - {}".format(e.__class__.__name__, e))
|
||||
|
||||
if not args.override_restore:
|
||||
_load_session(args.session)
|
||||
session_manager = objreg.get('session-manager')
|
||||
@@ -231,12 +214,13 @@ def _load_session(name):
|
||||
Args:
|
||||
name: The name of the session to load, or None to read state file.
|
||||
"""
|
||||
state_config = objreg.get('state-config')
|
||||
session_manager = objreg.get('session-manager')
|
||||
if name is None and session_manager.exists('_autosave'):
|
||||
name = '_autosave'
|
||||
elif name is None:
|
||||
try:
|
||||
name = configfiles.state['general']['session']
|
||||
name = state_config['general']['session']
|
||||
except KeyError:
|
||||
# No session given as argument and none in the session file ->
|
||||
# start without loading a session
|
||||
@@ -249,7 +233,7 @@ def _load_session(name):
|
||||
except sessions.SessionError as e:
|
||||
message.error("Failed to load session {}: {}".format(name, e))
|
||||
try:
|
||||
del configfiles.state['general']['session']
|
||||
del state_config['general']['session']
|
||||
except KeyError:
|
||||
pass
|
||||
# If this was a _restart session, delete it.
|
||||
@@ -281,7 +265,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||
win_id = mainwindow.get_window(via_ipc, force_tab=True)
|
||||
log.init.debug("Startup cmd {!r}".format(cmd))
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
commandrunner.run_safely(cmd[1:])
|
||||
commandrunner.run_safely_init(cmd[1:])
|
||||
elif not cmd:
|
||||
log.init.debug("Empty argument")
|
||||
win_id = mainwindow.get_window(via_ipc, force_window=True)
|
||||
@@ -289,7 +273,11 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||
if via_ipc and target_arg and target_arg != 'auto':
|
||||
open_target = target_arg
|
||||
else:
|
||||
open_target = None
|
||||
open_target = config.get('general', 'new-instance-open-target')
|
||||
win_id = mainwindow.get_window(via_ipc, force_target=open_target)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
log.init.debug("Startup URL {}".format(cmd))
|
||||
if not cwd: # could also be an empty string due to the PyQt signal
|
||||
cwd = None
|
||||
try:
|
||||
@@ -298,30 +286,9 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||
message.error("Error in startup argument '{}': {}".format(
|
||||
cmd, e))
|
||||
else:
|
||||
win_id = open_url(url, target=open_target, via_ipc=via_ipc)
|
||||
|
||||
|
||||
def open_url(url, target=None, no_raise=False, via_ipc=True):
|
||||
"""Open an URL in new window/tab.
|
||||
|
||||
Args:
|
||||
url: An URL to open.
|
||||
target: same as new_instance_open_target (used as a default).
|
||||
no_raise: suppress target window raising.
|
||||
via_ipc: Whether the arguments were transmitted over IPC.
|
||||
|
||||
Return:
|
||||
ID of a window that was used to open URL
|
||||
"""
|
||||
target = target or config.val.new_instance_open_target
|
||||
background = target in {'tab-bg', 'tab-bg-silent'}
|
||||
win_id = mainwindow.get_window(via_ipc, force_target=target,
|
||||
no_raise=no_raise)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
log.init.debug("About to open URL: {}".format(url.toDisplayString()))
|
||||
tabbed_browser.tabopen(url, background=background, related=False)
|
||||
return win_id
|
||||
background = open_target in ['tab-bg', 'tab-bg-silent']
|
||||
tabbed_browser.tabopen(url, background=background,
|
||||
explicit=True)
|
||||
|
||||
|
||||
def _open_startpage(win_id=None):
|
||||
@@ -340,10 +307,16 @@ def _open_startpage(win_id=None):
|
||||
for cur_win_id in list(window_ids): # Copying as the dict could change
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=cur_win_id)
|
||||
if tabbed_browser.widget.count() == 0:
|
||||
log.init.debug("Opening start pages")
|
||||
for url in config.val.url.start_pages:
|
||||
tabbed_browser.tabopen(url)
|
||||
if tabbed_browser.count() == 0:
|
||||
log.init.debug("Opening startpage")
|
||||
for urlstr in config.get('general', 'startpage'):
|
||||
try:
|
||||
url = urlutils.fuzzy_url(urlstr, do_search=False)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
message.error("Error when opening startpage: {}".format(e))
|
||||
tabbed_browser.tabopen(QUrl('about:blank'))
|
||||
else:
|
||||
tabbed_browser.tabopen(url)
|
||||
|
||||
|
||||
def _open_special_pages(args):
|
||||
@@ -360,29 +333,36 @@ def _open_special_pages(args):
|
||||
# With --basedir given, don't open anything.
|
||||
return
|
||||
|
||||
general_sect = configfiles.state['general']
|
||||
state_config = objreg.get('state-config')
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
|
||||
# Legacy QtWebKit warning
|
||||
|
||||
needs_warning = (objects.backend == usertypes.Backend.QtWebKit and
|
||||
not qtutils.is_qtwebkit_ng())
|
||||
warning_shown = state_config['general'].get('backend-warning-shown') == '1'
|
||||
|
||||
if not warning_shown and needs_warning:
|
||||
tabbed_browser.tabopen(QUrl('qute://backend-warning'),
|
||||
background=False)
|
||||
state_config['general']['backend-warning-shown'] = '1'
|
||||
|
||||
# Quickstart page
|
||||
|
||||
quickstart_done = general_sect.get('quickstart-done') == '1'
|
||||
quickstart_done = state_config['general'].get('quickstart-done') == '1'
|
||||
|
||||
if not quickstart_done:
|
||||
tabbed_browser.tabopen(
|
||||
QUrl('https://www.qutebrowser.org/quickstart.html'))
|
||||
general_sect['quickstart-done'] = '1'
|
||||
state_config['general']['quickstart-done'] = '1'
|
||||
|
||||
# Setting migration page
|
||||
|
||||
needs_migration = os.path.exists(
|
||||
os.path.join(standarddir.config(), 'qutebrowser.conf'))
|
||||
migration_shown = general_sect.get('config-migration-shown') == '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 _save_version():
|
||||
"""Save the current version to the state config."""
|
||||
state_config = objreg.get('state-config', None)
|
||||
if state_config is not None:
|
||||
state_config['general']['version'] = qutebrowser.__version__
|
||||
|
||||
|
||||
def on_focus_changed(_old, new):
|
||||
@@ -418,16 +398,14 @@ def _init_modules(args, crash_handler):
|
||||
args: The argparse namespace.
|
||||
crash_handler: The CrashHandler instance.
|
||||
"""
|
||||
# pylint: disable=too-many-statements
|
||||
log.init.debug("Initializing prompts...")
|
||||
prompt.init()
|
||||
|
||||
log.init.debug("Initializing save manager...")
|
||||
save_manager = savemanager.SaveManager(qApp)
|
||||
objreg.register('save-manager', save_manager)
|
||||
configinit.late_init(save_manager)
|
||||
|
||||
log.init.debug("Checking backend requirements...")
|
||||
backendproblem.init()
|
||||
|
||||
log.init.debug("Initializing prompts...")
|
||||
prompt.init()
|
||||
save_manager.add_saveable('version', _save_version)
|
||||
|
||||
log.init.debug("Initializing network...")
|
||||
networkmanager.init()
|
||||
@@ -439,25 +417,12 @@ def _init_modules(args, crash_handler):
|
||||
readline_bridge = readline.ReadlineBridge()
|
||||
objreg.register('readline-bridge', readline_bridge)
|
||||
|
||||
try:
|
||||
log.init.debug("Initializing sql...")
|
||||
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
|
||||
log.init.debug("Initializing config...")
|
||||
config.init(qApp)
|
||||
save_manager.init_autosave()
|
||||
|
||||
log.init.debug("Initializing web history...")
|
||||
history.init(qApp)
|
||||
except sql.SqlError as e:
|
||||
if e.environmental:
|
||||
error.handle_fatal_exc(e, args, 'Error initializing SQL',
|
||||
pre_text='Error initializing SQL')
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
else:
|
||||
raise
|
||||
|
||||
log.init.debug("Initializing completion...")
|
||||
completiondelegate.init()
|
||||
|
||||
log.init.debug("Initializing command history...")
|
||||
cmdhistory.init()
|
||||
log.init.debug("Initializing web history...")
|
||||
history.init(qApp)
|
||||
|
||||
log.init.debug("Initializing crashlog...")
|
||||
if not args.no_err_windows:
|
||||
@@ -492,19 +457,36 @@ def _init_modules(args, crash_handler):
|
||||
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
|
||||
objreg.register('cache', diskcache)
|
||||
|
||||
log.init.debug("Initializing downloads...")
|
||||
download_manager = qtnetworkdownloads.DownloadManager(parent=qApp)
|
||||
objreg.register('qtnetwork-download-manager', download_manager)
|
||||
|
||||
log.init.debug("Initializing Greasemonkey...")
|
||||
greasemonkey.init()
|
||||
log.init.debug("Initializing completions...")
|
||||
completionmodels.init()
|
||||
|
||||
log.init.debug("Misc initialization...")
|
||||
if config.get('ui', 'hide-wayland-decoration'):
|
||||
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
|
||||
else:
|
||||
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
|
||||
macros.init()
|
||||
# Init backend-specific stuff
|
||||
browsertab.init()
|
||||
|
||||
|
||||
def _init_late_modules(args):
|
||||
"""Initialize modules which can be inited after the window is shown."""
|
||||
log.init.debug("Reading web history...")
|
||||
reader = objreg.get('web-history').async_read()
|
||||
with debug.log_time(log.init, 'Reading history'):
|
||||
while True:
|
||||
QApplication.processEvents()
|
||||
try:
|
||||
next(reader)
|
||||
except StopIteration:
|
||||
break
|
||||
except (OSError, UnicodeDecodeError) as e:
|
||||
error.handle_fatal_exc(e, args, "Error while initializing!",
|
||||
pre_text="Error while initializing")
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
|
||||
|
||||
class Quitter:
|
||||
|
||||
"""Utility class to quit/restart the QApplication.
|
||||
@@ -547,13 +529,12 @@ class Quitter:
|
||||
with tokenize.open(os.path.join(dirpath, fn)) as f:
|
||||
compile(f.read(), fn, 'exec')
|
||||
|
||||
def _get_restart_args(self, pages=(), session=None, override_args=None):
|
||||
def _get_restart_args(self, pages=(), session=None):
|
||||
"""Get the current working directory and args to relaunch qutebrowser.
|
||||
|
||||
Args:
|
||||
pages: The pages to re-open.
|
||||
session: The session to load, or None.
|
||||
override_args: Argument overrides as a dict.
|
||||
|
||||
Return:
|
||||
An (args, cwd) tuple.
|
||||
@@ -569,8 +550,8 @@ class Quitter:
|
||||
cwd = os.path.abspath(os.path.dirname(sys.executable))
|
||||
else:
|
||||
args = [sys.executable, '-m', 'qutebrowser']
|
||||
cwd = os.path.join(
|
||||
os.path.abspath(os.path.dirname(qutebrowser.__file__)), '..')
|
||||
cwd = os.path.join(os.path.abspath(os.path.dirname(
|
||||
qutebrowser.__file__)), '..')
|
||||
if not os.path.isdir(cwd):
|
||||
# Probably running from a python egg. Let's fallback to
|
||||
# cwd=None and see if that works out.
|
||||
@@ -604,9 +585,6 @@ class Quitter:
|
||||
argdict['temp_basedir'] = False
|
||||
argdict['temp_basedir_restarted'] = True
|
||||
|
||||
if override_args is not None:
|
||||
argdict.update(override_args)
|
||||
|
||||
# Dump the data
|
||||
data = json.dumps(argdict)
|
||||
args += ['--json-args', data]
|
||||
@@ -631,7 +609,7 @@ class Quitter:
|
||||
if ok:
|
||||
self.shutdown(restart=True)
|
||||
|
||||
def restart(self, pages=(), session=None, override_args=None):
|
||||
def restart(self, pages=(), session=None):
|
||||
"""Inner logic to restart qutebrowser.
|
||||
|
||||
The "better" way to restart is to pass a session (_restart usually) as
|
||||
@@ -644,7 +622,6 @@ class Quitter:
|
||||
Args:
|
||||
pages: A list of URLs to open.
|
||||
session: The session to load, or None.
|
||||
override_args: Argument overrides as a dict.
|
||||
|
||||
Return:
|
||||
True if the restart succeeded, False otherwise.
|
||||
@@ -654,19 +631,13 @@ class Quitter:
|
||||
log.destroy.debug("sys.path: {}".format(sys.path))
|
||||
log.destroy.debug("sys.argv: {}".format(sys.argv))
|
||||
log.destroy.debug("frozen: {}".format(hasattr(sys, 'frozen')))
|
||||
|
||||
# Save the session if one is given.
|
||||
if session is not None:
|
||||
session_manager = objreg.get('session-manager')
|
||||
session_manager.save(session, with_private=True)
|
||||
|
||||
# Make sure we're not accepting a connection from the new process
|
||||
# before we fully exited.
|
||||
ipc.server.shutdown()
|
||||
|
||||
# Open a new process and immediately shutdown the existing one
|
||||
try:
|
||||
args, cwd = self._get_restart_args(pages, session, override_args)
|
||||
args, cwd = self._get_restart_args(pages, session)
|
||||
if cwd is None:
|
||||
subprocess.Popen(args)
|
||||
else:
|
||||
@@ -677,24 +648,8 @@ class Quitter:
|
||||
else:
|
||||
return True
|
||||
|
||||
@cmdutils.register(instance='quitter', name='quit')
|
||||
@cmdutils.argument('session', completion=miscmodels.session)
|
||||
def quit(self, save=False, session=None):
|
||||
"""Quit qutebrowser.
|
||||
Args:
|
||||
save: When given, save the open windows even if auto_save.session
|
||||
is turned off.
|
||||
session: The name of the session to save.
|
||||
"""
|
||||
if session is not None and not save:
|
||||
raise cmdexc.CommandError("Session name given without --save!")
|
||||
if save:
|
||||
if session is None:
|
||||
session = sessions.default
|
||||
self.shutdown(session=session)
|
||||
else:
|
||||
self.shutdown()
|
||||
|
||||
@cmdutils.register(instance='quitter', name=['quit', 'q'],
|
||||
ignore_args=True)
|
||||
def shutdown(self, status=0, session=None, last_window=False,
|
||||
restart=False):
|
||||
"""Quit qutebrowser.
|
||||
@@ -716,7 +671,7 @@ class Quitter:
|
||||
if session is not None:
|
||||
session_manager.save(session, last_window=last_window,
|
||||
load_next_time=True)
|
||||
elif config.val.auto_save.session:
|
||||
elif config.get('general', 'save-session'):
|
||||
session_manager.save(sessions.default, last_window=last_window,
|
||||
load_next_time=True)
|
||||
|
||||
@@ -753,7 +708,7 @@ class Quitter:
|
||||
QApplication.closeAllWindows()
|
||||
# Shut down IPC
|
||||
try:
|
||||
ipc.server.shutdown()
|
||||
objreg.get('ipc-server').shutdown()
|
||||
except KeyError:
|
||||
pass
|
||||
# Save everything
|
||||
@@ -772,8 +727,6 @@ class Quitter:
|
||||
pre_text="Error while saving {}".format(key))
|
||||
# Disable storage so removing tempdir will work
|
||||
websettings.shutdown()
|
||||
# Disable application proxy factory to fix segfaults with Qt 5.10.1
|
||||
proxy.shutdown()
|
||||
# Re-enable faulthandler to stdout, then remove crash log
|
||||
log.destroy.debug("Deactivating crash log...")
|
||||
objreg.get('crash-handler').destroy_crashlogfile()
|
||||
@@ -797,6 +750,16 @@ class Quitter:
|
||||
# segfaults.
|
||||
QTimer.singleShot(0, functools.partial(qApp.exit, status))
|
||||
|
||||
@cmdutils.register(instance='quitter', name='wq')
|
||||
@cmdutils.argument('name', completion=usertypes.Completion.sessions)
|
||||
def save_and_quit(self, name=sessions.default):
|
||||
"""Save open pages and quit.
|
||||
|
||||
Args:
|
||||
name: The name of the session.
|
||||
"""
|
||||
self.shutdown(session=name)
|
||||
|
||||
|
||||
class Application(QApplication):
|
||||
|
||||
@@ -817,7 +780,7 @@ class Application(QApplication):
|
||||
"""
|
||||
self._last_focus_object = None
|
||||
|
||||
qt_args = configinit.qt_args(args)
|
||||
qt_args = qtutils.get_args(args)
|
||||
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
|
||||
super().__init__(qt_args)
|
||||
|
||||
@@ -829,7 +792,6 @@ class Application(QApplication):
|
||||
|
||||
self.launch_time = datetime.datetime.now()
|
||||
self.focusObjectChanged.connect(self.on_focus_object_changed)
|
||||
self.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def on_focus_object_changed(self, obj):
|
||||
@@ -839,19 +801,6 @@ class Application(QApplication):
|
||||
log.misc.debug("Focus object changed: {}".format(output))
|
||||
self._last_focus_object = output
|
||||
|
||||
def event(self, e):
|
||||
"""Handle macOS FileOpen events."""
|
||||
if e.type() == QEvent.FileOpen:
|
||||
url = e.url()
|
||||
if url.isValid():
|
||||
open_url(url, no_raise=True)
|
||||
else:
|
||||
message.error("Invalid URL: {}".format(url.errorString()))
|
||||
else:
|
||||
return super().event(e)
|
||||
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
@@ -882,9 +831,12 @@ class EventFilter(QObject):
|
||||
super().__init__(parent)
|
||||
self._activated = True
|
||||
self._handlers = {
|
||||
QEvent.MouseButtonDblClick: self._handle_mouse_event,
|
||||
QEvent.MouseButtonPress: self._handle_mouse_event,
|
||||
QEvent.MouseButtonRelease: self._handle_mouse_event,
|
||||
QEvent.MouseMove: self._handle_mouse_event,
|
||||
QEvent.KeyPress: self._handle_key_event,
|
||||
QEvent.KeyRelease: self._handle_key_event,
|
||||
QEvent.ShortcutOverride: self._handle_key_event,
|
||||
}
|
||||
|
||||
def _handle_key_event(self, event):
|
||||
@@ -902,11 +854,24 @@ class EventFilter(QObject):
|
||||
return False
|
||||
try:
|
||||
man = objreg.get('mode-manager', scope='window', window='current')
|
||||
return man.handle_event(event)
|
||||
return man.eventFilter(event)
|
||||
except objreg.RegistryUnavailableError:
|
||||
# No window available yet, or not a MainWindow
|
||||
return False
|
||||
|
||||
def _handle_mouse_event(self, _event):
|
||||
"""Handle a mouse event.
|
||||
|
||||
Args:
|
||||
_event: The QEvent which is about to be delivered.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False if it's passed through.
|
||||
"""
|
||||
# Mouse cursor shown (overrideCursor None) -> don't filter event
|
||||
# Mouse cursor hidden (overrideCursor not None) -> filter event
|
||||
return qApp.overrideCursor() is not None
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Handle an event.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -67,7 +67,11 @@ def is_whitelisted_host(host):
|
||||
Args:
|
||||
host: The host of the request as string.
|
||||
"""
|
||||
for pattern in config.val.content.host_blocking.whitelist:
|
||||
whitelist = config.get('content', 'host-blocking-whitelist')
|
||||
if whitelist is None:
|
||||
return False
|
||||
|
||||
for pattern in whitelist:
|
||||
if fnmatch.fnmatch(host, pattern.lower()):
|
||||
return True
|
||||
return False
|
||||
@@ -94,8 +98,14 @@ class HostBlocker:
|
||||
_done_count: How many files have been read successfully.
|
||||
_local_hosts_file: The path to the blocked-hosts file.
|
||||
_config_hosts_file: The path to a blocked-hosts in ~/.config
|
||||
|
||||
Class attributes:
|
||||
WHITELISTED: Hosts which never should be blocked.
|
||||
"""
|
||||
|
||||
WHITELISTED = ('localhost', 'localhost.localdomain', 'broadcasthost',
|
||||
'local')
|
||||
|
||||
def __init__(self):
|
||||
self._blocked_hosts = set()
|
||||
self._config_blocked_hosts = set()
|
||||
@@ -104,16 +114,16 @@ class HostBlocker:
|
||||
|
||||
data_dir = standarddir.data()
|
||||
self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
|
||||
self._update_files()
|
||||
self.on_config_changed()
|
||||
|
||||
config_dir = standarddir.config()
|
||||
self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')
|
||||
|
||||
config.instance.changed.connect(self._update_files)
|
||||
objreg.get('config').changed.connect(self.on_config_changed)
|
||||
|
||||
def is_blocked(self, url):
|
||||
"""Check if the given URL (as QUrl) is blocked."""
|
||||
if not config.val.content.host_blocking.enabled:
|
||||
if not config.get('content', 'host-blocking-enabled'):
|
||||
return False
|
||||
host = url.host()
|
||||
return ((host in self._blocked_hosts or
|
||||
@@ -154,9 +164,9 @@ class HostBlocker:
|
||||
|
||||
if not found:
|
||||
args = objreg.get('args')
|
||||
if (config.val.content.host_blocking.lists and
|
||||
if (config.get('content', 'host-block-lists') is not None and
|
||||
args.basedir is None and
|
||||
config.val.content.host_blocking.enabled):
|
||||
config.get('content', 'host-blocking-enabled')):
|
||||
message.info("Run :adblock-update to get adblock lists.")
|
||||
|
||||
@cmdutils.register(instance='host-blocker')
|
||||
@@ -170,15 +180,18 @@ class HostBlocker:
|
||||
self._config_blocked_hosts)
|
||||
self._blocked_hosts = set()
|
||||
self._done_count = 0
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
for url in config.val.content.host_blocking.lists:
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window='last-focused')
|
||||
if urls is None:
|
||||
return
|
||||
for url in urls:
|
||||
if url.scheme() == 'file':
|
||||
filename = url.toLocalFile()
|
||||
try:
|
||||
fileobj = open(filename, 'rb')
|
||||
fileobj = open(url.path(), 'rb')
|
||||
except OSError as e:
|
||||
message.error("adblock: Error while reading {}: {}".format(
|
||||
filename, e.strerror))
|
||||
url.path(), e.strerror))
|
||||
continue
|
||||
download = FakeDownload(fileobj)
|
||||
self._in_progress.append(download)
|
||||
@@ -228,14 +241,16 @@ class HostBlocker:
|
||||
parts = line.split()
|
||||
if len(parts) == 1:
|
||||
# "one host per line" format
|
||||
hosts = [parts[0]]
|
||||
else:
|
||||
host = parts[0]
|
||||
elif len(parts) == 2:
|
||||
# /etc/hosts format
|
||||
hosts = parts[1:]
|
||||
host = parts[1]
|
||||
else:
|
||||
log.misc.error("Failed to parse: {!r}".format(line))
|
||||
return False
|
||||
|
||||
for host in hosts:
|
||||
if '.' in host and not host.endswith('.localdomain'):
|
||||
self._blocked_hosts.add(host)
|
||||
if host not in self.WHITELISTED:
|
||||
self._blocked_hosts.add(host)
|
||||
|
||||
return True
|
||||
|
||||
@@ -277,10 +292,11 @@ class HostBlocker:
|
||||
message.info("adblock: Read {} hosts from {} sources.".format(
|
||||
len(self._blocked_hosts), self._done_count))
|
||||
|
||||
@config.change_filter('content.host_blocking.lists')
|
||||
def _update_files(self):
|
||||
@config.change_filter('content', 'host-block-lists')
|
||||
def on_config_changed(self):
|
||||
"""Update files when the config changed."""
|
||||
if not config.val.content.host_blocking.lists:
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
if urls is None:
|
||||
try:
|
||||
os.remove(self._local_hosts_file)
|
||||
except FileNotFoundError:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -19,25 +19,17 @@
|
||||
|
||||
"""Base class for a wrapper over QWebView/QWebEngineView."""
|
||||
|
||||
import enum
|
||||
import itertools
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QApplication
|
||||
|
||||
import pygments
|
||||
import pygments.lexers
|
||||
import pygments.formatters
|
||||
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
|
||||
urlutils, message)
|
||||
from qutebrowser.utils import utils, objreg, usertypes, log, qtutils
|
||||
from qutebrowser.misc import miscwidgets, objects
|
||||
from qutebrowser.browser import mouse, hints
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
tab_id_gen = itertools.count(0)
|
||||
@@ -69,6 +61,9 @@ def init():
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
from qutebrowser.browser.webengine import webenginetab
|
||||
webenginetab.init()
|
||||
else:
|
||||
from qutebrowser.browser.webkit import webkittab
|
||||
webkittab.init()
|
||||
|
||||
|
||||
class WebTabError(Exception):
|
||||
@@ -81,7 +76,7 @@ class UnsupportedOperationError(WebTabError):
|
||||
"""Raised when an operation is not supported with the given backend."""
|
||||
|
||||
|
||||
TerminationStatus = enum.Enum('TerminationStatus', [
|
||||
TerminationStatus = usertypes.enum('TerminationStatus', [
|
||||
'normal',
|
||||
'abnormal', # non-zero exit status
|
||||
'crashed', # e.g. segfault
|
||||
@@ -90,7 +85,6 @@ TerminationStatus = enum.Enum('TerminationStatus', [
|
||||
])
|
||||
|
||||
|
||||
@attr.s
|
||||
class TabData:
|
||||
|
||||
"""A simple namespace with a fixed set of attributes.
|
||||
@@ -100,30 +94,19 @@ class TabData:
|
||||
load.
|
||||
inspector: The QWebInspector used for this webview.
|
||||
viewing_source: Set if we're currently showing a source view.
|
||||
Only used when sources are shown via pygments.
|
||||
open_target: Where to open the next link.
|
||||
Only used for QtWebKit.
|
||||
override_target: Override for open_target for fake clicks (like hints).
|
||||
Only used for QtWebKit.
|
||||
pinned: Flag to pin the tab.
|
||||
fullscreen: Whether the tab has a video shown fullscreen currently.
|
||||
netrc_used: Whether netrc authentication was performed.
|
||||
input_mode: current input mode for the tab.
|
||||
"""
|
||||
|
||||
keep_icon = attr.ib(False)
|
||||
viewing_source = attr.ib(False)
|
||||
inspector = attr.ib(None)
|
||||
open_target = attr.ib(usertypes.ClickTarget.normal)
|
||||
override_target = attr.ib(None)
|
||||
pinned = attr.ib(False)
|
||||
fullscreen = attr.ib(False)
|
||||
netrc_used = attr.ib(False)
|
||||
input_mode = attr.ib(usertypes.KeyMode.normal)
|
||||
|
||||
def should_show_icon(self):
|
||||
return (config.val.tabs.favicons.show == 'always' or
|
||||
config.val.tabs.favicons.show == 'pinned' and self.pinned)
|
||||
def __init__(self):
|
||||
self.keep_icon = False
|
||||
self.viewing_source = False
|
||||
self.inspector = None
|
||||
self.override_target = None
|
||||
self.pinned = False
|
||||
self.fullscreen = False
|
||||
|
||||
|
||||
class AbstractAction:
|
||||
@@ -138,9 +121,8 @@ class AbstractAction:
|
||||
action_class = None
|
||||
action_base = None
|
||||
|
||||
def __init__(self, tab):
|
||||
def __init__(self):
|
||||
self._widget = None
|
||||
self._tab = tab
|
||||
|
||||
def exit_fullscreen(self):
|
||||
"""Exit the fullscreen mode."""
|
||||
@@ -157,31 +139,6 @@ class AbstractAction:
|
||||
raise WebTabError("{} is not a valid web action!".format(name))
|
||||
self._widget.triggerPageAction(member)
|
||||
|
||||
def show_source(self,
|
||||
pygments=False): # pylint: disable=redefined-outer-name
|
||||
"""Show the source of the current page in a new tab."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _show_source_pygments(self):
|
||||
|
||||
def show_source_cb(source):
|
||||
"""Show source as soon as it's ready."""
|
||||
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491
|
||||
# pylint: disable=no-member
|
||||
lexer = pygments.lexers.HtmlLexer()
|
||||
formatter = pygments.formatters.HtmlFormatter(
|
||||
full=True, linenos='table')
|
||||
# pylint: enable=no-member
|
||||
highlighted = pygments.highlight(source, lexer, formatter)
|
||||
|
||||
tb = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._tab.win_id)
|
||||
new_tab = tb.tabopen(background=False, related=True)
|
||||
new_tab.set_html(highlighted, self._tab.url())
|
||||
new_tab.data.viewing_source = True
|
||||
|
||||
self._tab.dump_async(show_source_cb)
|
||||
|
||||
|
||||
class AbstractPrinting:
|
||||
|
||||
@@ -231,28 +188,13 @@ class AbstractSearch(QObject):
|
||||
self.text = None
|
||||
self.search_displayed = False
|
||||
|
||||
def _is_case_sensitive(self, ignore_case):
|
||||
"""Check if case-sensitivity should be used.
|
||||
|
||||
This assumes self.text is already set properly.
|
||||
|
||||
Arguments:
|
||||
ignore_case: The ignore_case value from the config.
|
||||
"""
|
||||
mapping = {
|
||||
'smart': not self.text.islower(),
|
||||
'never': True,
|
||||
'always': False,
|
||||
}
|
||||
return mapping[ignore_case]
|
||||
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
def search(self, text, *, ignore_case=False, reverse=False,
|
||||
result_cb=None):
|
||||
"""Find the given text on the page.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
ignore_case: Search case-insensitively. ('always'/'never/'smart')
|
||||
ignore_case: Search case-insensitively. (True/False/'smart')
|
||||
reverse: Reverse search direction.
|
||||
result_cb: Called with a bool indicating whether a match was found.
|
||||
"""
|
||||
@@ -288,14 +230,13 @@ class AbstractZoom(QObject):
|
||||
_default_zoom_changed: Whether the zoom was changed from the default.
|
||||
"""
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = None
|
||||
self._win_id = win_id
|
||||
self._default_zoom_changed = False
|
||||
self._init_neighborlist()
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
self._zoom_factor = float(config.val.zoom.default) / 100
|
||||
objreg.get('config').changed.connect(self._on_config_changed)
|
||||
|
||||
# # FIXME:qtwebengine is this needed?
|
||||
# # For some reason, this signal doesn't get disconnected automatically
|
||||
@@ -304,20 +245,21 @@ class AbstractZoom(QObject):
|
||||
# self.destroyed.connect(functools.partial(
|
||||
# cfg.changed.disconnect, self.init_neighborlist))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
if option in ['zoom.levels', 'zoom.default']:
|
||||
@pyqtSlot(str, str)
|
||||
def _on_config_changed(self, section, option):
|
||||
if section == 'ui' and option in ['zoom-levels', 'default-zoom']:
|
||||
if not self._default_zoom_changed:
|
||||
factor = float(config.val.zoom.default) / 100
|
||||
self.set_factor(factor)
|
||||
factor = float(config.get('ui', 'default-zoom')) / 100
|
||||
self._set_factor_internal(factor)
|
||||
self._default_zoom_changed = False
|
||||
self._init_neighborlist()
|
||||
|
||||
def _init_neighborlist(self):
|
||||
"""Initialize self._neighborlist."""
|
||||
levels = config.val.zoom.levels
|
||||
levels = config.get('ui', 'zoom-levels')
|
||||
self._neighborlist = usertypes.NeighborList(
|
||||
levels, mode=usertypes.NeighborList.Modes.edge)
|
||||
self._neighborlist.fuzzyval = config.val.zoom.default
|
||||
self._neighborlist.fuzzyval = config.get('ui', 'default-zoom')
|
||||
|
||||
def offset(self, offset):
|
||||
"""Increase/Decrease the zoom level by the given offset.
|
||||
@@ -346,37 +288,25 @@ class AbstractZoom(QObject):
|
||||
self._neighborlist.fuzzyval = int(factor * 100)
|
||||
if factor < 0:
|
||||
raise ValueError("Can't zoom to factor {}!".format(factor))
|
||||
|
||||
default_zoom_factor = float(config.val.zoom.default) / 100
|
||||
self._default_zoom_changed = (factor != default_zoom_factor)
|
||||
|
||||
self._zoom_factor = factor
|
||||
self._default_zoom_changed = True
|
||||
self._set_factor_internal(factor)
|
||||
|
||||
def factor(self):
|
||||
return self._zoom_factor
|
||||
raise NotImplementedError
|
||||
|
||||
def set_default(self):
|
||||
self._set_factor_internal(float(config.val.zoom.default) / 100)
|
||||
|
||||
def set_current(self):
|
||||
self._set_factor_internal(self._zoom_factor)
|
||||
default_zoom = config.get('ui', 'default-zoom')
|
||||
self._set_factor_internal(float(default_zoom) / 100)
|
||||
|
||||
|
||||
class AbstractCaret(QObject):
|
||||
|
||||
"""Attribute of AbstractTab for caret browsing.
|
||||
"""Attribute of AbstractTab for caret browsing."""
|
||||
|
||||
Signals:
|
||||
selection_toggled: Emitted when the selection was toggled.
|
||||
arg: Whether the selection is now active.
|
||||
"""
|
||||
|
||||
selection_toggled = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, tab, mode_manager, parent=None):
|
||||
def __init__(self, win_id, tab, mode_manager, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._win_id = win_id
|
||||
self._widget = None
|
||||
self.selection_enabled = False
|
||||
mode_manager.entered.connect(self._on_mode_entered)
|
||||
@@ -385,7 +315,7 @@ class AbstractCaret(QObject):
|
||||
def _on_mode_entered(self, mode):
|
||||
raise NotImplementedError
|
||||
|
||||
def _on_mode_left(self, mode):
|
||||
def _on_mode_left(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_next_line(self, count=1):
|
||||
@@ -439,15 +369,11 @@ class AbstractCaret(QObject):
|
||||
def drop_selection(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def selection(self, callback):
|
||||
def has_selection(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _follow_enter(self, tab):
|
||||
"""Follow a link by faking an enter press."""
|
||||
if tab:
|
||||
self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier)
|
||||
else:
|
||||
self._tab.key_press(Qt.Key_Enter)
|
||||
def selection(self, html=False):
|
||||
raise NotImplementedError
|
||||
|
||||
def follow_selected(self, *, tab=False):
|
||||
raise NotImplementedError
|
||||
@@ -485,9 +411,6 @@ class AbstractScroller(QObject):
|
||||
def to_point(self, point):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_anchor(self, name):
|
||||
raise NotImplementedError
|
||||
|
||||
def delta(self, x=0, y=0):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -542,23 +465,11 @@ class AbstractHistory:
|
||||
def current_idx(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def back(self, count=1):
|
||||
"""Go back in the tab's history."""
|
||||
idx = self.current_idx() - count
|
||||
if idx >= 0:
|
||||
self._go_to_item(self._item_at(idx))
|
||||
else:
|
||||
self._go_to_item(self._item_at(0))
|
||||
raise WebTabError("At beginning of history.")
|
||||
def back(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def forward(self, count=1):
|
||||
"""Go forward in the tab's history."""
|
||||
idx = self.current_idx() + count
|
||||
if idx < len(self):
|
||||
self._go_to_item(self._item_at(idx))
|
||||
else:
|
||||
self._go_to_item(self._item_at(len(self) - 1))
|
||||
raise WebTabError("At end of history.")
|
||||
def forward(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def can_go_back(self):
|
||||
raise NotImplementedError
|
||||
@@ -566,12 +477,6 @@ class AbstractHistory:
|
||||
def can_go_forward(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _item_at(self, i):
|
||||
raise NotImplementedError
|
||||
|
||||
def _go_to_item(self, item):
|
||||
raise NotImplementedError
|
||||
|
||||
def serialize(self):
|
||||
"""Serialize into an opaque format understood by self.deserialize."""
|
||||
raise NotImplementedError
|
||||
@@ -634,33 +539,6 @@ class AbstractElements:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractAudio(QObject):
|
||||
|
||||
"""Handling of audio/muting for this tab."""
|
||||
|
||||
muted_changed = pyqtSignal(bool)
|
||||
recently_audible_changed = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._widget = None
|
||||
|
||||
def set_muted(self, muted: bool):
|
||||
"""Set this tab as muted or not."""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_muted(self):
|
||||
"""Whether this tab is muted."""
|
||||
raise NotImplementedError
|
||||
|
||||
def toggle_muted(self):
|
||||
self.set_muted(not self.is_muted())
|
||||
|
||||
def is_recently_audible(self):
|
||||
"""Whether this tab has had audio playing recently."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractTab(QWidget):
|
||||
|
||||
"""A wrapper over the given widget to hide its API and expose another one.
|
||||
@@ -692,7 +570,6 @@ class AbstractTab(QWidget):
|
||||
process terminated.
|
||||
arg 0: A TerminationStatus member.
|
||||
arg 1: The exit code.
|
||||
predicted_navigation: Emitted before we tell Qt to open a URL.
|
||||
"""
|
||||
|
||||
window_close_requested = pyqtSignal()
|
||||
@@ -710,7 +587,6 @@ class AbstractTab(QWidget):
|
||||
add_history_item = pyqtSignal(QUrl, QUrl, str) # url, requested url, title
|
||||
fullscreen_requested = pyqtSignal(bool)
|
||||
renderer_process_terminated = pyqtSignal(TerminationStatus, int)
|
||||
predicted_navigation = pyqtSignal(QUrl)
|
||||
|
||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||
self.private = private
|
||||
@@ -724,6 +600,16 @@ class AbstractTab(QWidget):
|
||||
tab_registry[self.tab_id] = self
|
||||
objreg.register('tab', self, registry=self.registry)
|
||||
|
||||
# self.history = AbstractHistory(self)
|
||||
# self.scroller = AbstractScroller(self, parent=self)
|
||||
# self.caret = AbstractCaret(win_id=win_id, tab=self,
|
||||
# mode_manager=mode_manager, parent=self)
|
||||
# self.zoom = AbstractZoom(win_id=win_id)
|
||||
# self.search = AbstractSearch(parent=self)
|
||||
# self.printing = AbstractPrinting()
|
||||
# self.elements = AbstractElements(self)
|
||||
# self.action = AbstractAction()
|
||||
|
||||
self.data = TabData()
|
||||
self._layout = miscwidgets.WrapperLayout(self)
|
||||
self._widget = None
|
||||
@@ -741,8 +627,6 @@ class AbstractTab(QWidget):
|
||||
objreg.register('hintmanager', hintmanager, scope='tab',
|
||||
window=self.win_id, tab=self.tab_id)
|
||||
|
||||
self.predicted_navigation.connect(self._on_predicted_navigation)
|
||||
|
||||
def _set_widget(self, widget):
|
||||
# pylint: disable=protected-access
|
||||
self._widget = widget
|
||||
@@ -755,8 +639,6 @@ class AbstractTab(QWidget):
|
||||
self.printing._widget = widget
|
||||
self.action._widget = widget
|
||||
self.elements._widget = widget
|
||||
self.audio._widget = widget
|
||||
self.settings._settings = widget.settings()
|
||||
|
||||
self._install_event_filter()
|
||||
self.zoom.set_default()
|
||||
@@ -785,26 +667,12 @@ class AbstractTab(QWidget):
|
||||
# This only gives us some mild protection against re-using events, but
|
||||
# it's certainly better than a segfault.
|
||||
if getattr(evt, 'posted', False):
|
||||
raise utils.Unreachable("Can't re-use an event which was already "
|
||||
"posted!")
|
||||
|
||||
raise AssertionError("Can't re-use an event which was already "
|
||||
"posted!")
|
||||
recipient = self.event_target()
|
||||
if recipient is None:
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3888
|
||||
log.webview.warning("Unable to find event target!")
|
||||
return
|
||||
|
||||
evt.posted = True
|
||||
QApplication.postEvent(recipient, evt)
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_predicted_navigation(self, url):
|
||||
"""Adjust the title if we are going to visit an URL soon."""
|
||||
qtutils.ensure_valid(url)
|
||||
url_string = url.toDisplayString()
|
||||
log.webview.debug("Predicted navigation: {}".format(url_string))
|
||||
self.title_changed.emit(url_string)
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_url_changed(self, url):
|
||||
"""Update title when URL has changed and no title is available."""
|
||||
@@ -820,26 +688,9 @@ class AbstractTab(QWidget):
|
||||
self._set_load_status(usertypes.LoadStatus.loading)
|
||||
self.load_started.emit()
|
||||
|
||||
@pyqtSlot(usertypes.NavigationRequest)
|
||||
def _on_navigation_request(self, navigation):
|
||||
"""Handle common acceptNavigationRequest code."""
|
||||
url = utils.elide(navigation.url.toDisplayString(), 100)
|
||||
log.webview.debug("navigation request: url {}, type {}, is_main_frame "
|
||||
"{}".format(url,
|
||||
navigation.navigation_type,
|
||||
navigation.is_main_frame))
|
||||
|
||||
if (navigation.navigation_type == navigation.Type.link_clicked and
|
||||
not navigation.url.isValid()):
|
||||
msg = urlutils.get_errstring(navigation.url,
|
||||
"Invalid link clicked")
|
||||
message.error(msg)
|
||||
self.data.open_target = usertypes.ClickTarget.normal
|
||||
navigation.accepted = False
|
||||
|
||||
def handle_auto_insert_mode(self, ok):
|
||||
"""Handle `input.insert_mode.auto_load` after loading finished."""
|
||||
if not config.val.input.insert_mode.auto_load or not ok:
|
||||
def _handle_auto_insert_mode(self, ok):
|
||||
"""Handle auto-insert-mode after loading finished."""
|
||||
if not config.get('input', 'auto-insert-mode') or not ok:
|
||||
return
|
||||
|
||||
cur_mode = self._mode_manager.mode
|
||||
@@ -859,10 +710,6 @@ class AbstractTab(QWidget):
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_load_finished(self, ok):
|
||||
if sip.isdeleted(self._widget):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3498
|
||||
return
|
||||
|
||||
sess_manager = objreg.get('session-manager')
|
||||
sess_manager.save_autosave()
|
||||
|
||||
@@ -875,13 +722,10 @@ class AbstractTab(QWidget):
|
||||
self._set_load_status(usertypes.LoadStatus.warn)
|
||||
else:
|
||||
self._set_load_status(usertypes.LoadStatus.error)
|
||||
|
||||
self.load_finished.emit(ok)
|
||||
|
||||
if not self.title():
|
||||
self.title_changed.emit(self.url().toDisplayString())
|
||||
|
||||
self.zoom.set_current()
|
||||
self._handle_auto_insert_mode(ok)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_history_trigger(self):
|
||||
@@ -893,6 +737,10 @@ class AbstractTab(QWidget):
|
||||
self._progress = perc
|
||||
self.load_progress.emit(perc)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_ssl_errors(self):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
def url(self, requested=False):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -902,12 +750,11 @@ class AbstractTab(QWidget):
|
||||
def load_status(self):
|
||||
return self._load_status
|
||||
|
||||
def _openurl_prepare(self, url, *, predict=True):
|
||||
def _openurl_prepare(self, url):
|
||||
qtutils.ensure_valid(url)
|
||||
if predict:
|
||||
self.predicted_navigation.emit(url)
|
||||
self.title_changed.emit(url.toDisplayString())
|
||||
|
||||
def openurl(self, url, *, predict=True):
|
||||
def openurl(self, url):
|
||||
raise NotImplementedError
|
||||
|
||||
def reload(self, *, force=False):
|
||||
@@ -924,7 +771,7 @@ class AbstractTab(QWidget):
|
||||
raise NotImplementedError
|
||||
|
||||
def dump_async(self, callback, *, plain=False):
|
||||
"""Dump the current page's html asynchronously.
|
||||
"""Dump the current page to a file ascync.
|
||||
|
||||
The given callback will be called with the result when dumping is
|
||||
complete.
|
||||
@@ -977,9 +824,6 @@ class AbstractTab(QWidget):
|
||||
try:
|
||||
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode),
|
||||
100)
|
||||
except (AttributeError, RuntimeError) as exc:
|
||||
url = '<{}>'.format(exc.__class__.__name__)
|
||||
except AttributeError:
|
||||
url = '<AttributeError>'
|
||||
return utils.get_repr(self, tab_id=self.tab_id, url=url)
|
||||
|
||||
def is_deleted(self):
|
||||
return sip.isdeleted(self._widget)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -27,19 +27,19 @@ import collections
|
||||
import functools
|
||||
import pathlib
|
||||
import tempfile
|
||||
import enum
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||
QTimer, QAbstractListModel, QUrl)
|
||||
QTimer, QAbstractListModel)
|
||||
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
|
||||
qtutils)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole)
|
||||
ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole,
|
||||
is_int=True)
|
||||
|
||||
|
||||
# Remember the last used directory
|
||||
@@ -69,22 +69,15 @@ class UnsupportedOperationError(Exception):
|
||||
|
||||
def download_dir():
|
||||
"""Get the download directory to use."""
|
||||
directory = config.val.downloads.location.directory
|
||||
remember_dir = config.val.downloads.location.remember
|
||||
directory = config.get('storage', 'download-directory')
|
||||
remember_dir = config.get('storage', 'remember-download-directory')
|
||||
|
||||
if remember_dir and last_used_directory is not None:
|
||||
ddir = last_used_directory
|
||||
return last_used_directory
|
||||
elif directory is None:
|
||||
ddir = standarddir.download()
|
||||
return standarddir.download()
|
||||
else:
|
||||
ddir = directory
|
||||
|
||||
try:
|
||||
os.makedirs(ddir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
return ddir
|
||||
return directory
|
||||
|
||||
|
||||
def immediate_download_path(prompt_download_directory=None):
|
||||
@@ -95,16 +88,15 @@ def immediate_download_path(prompt_download_directory=None):
|
||||
Args:
|
||||
prompt_download_directory: If this is something else than None, it
|
||||
will overwrite the
|
||||
downloads.location.prompt setting.
|
||||
storage->prompt-download-directory setting.
|
||||
"""
|
||||
if prompt_download_directory is None:
|
||||
prompt_download_directory = config.val.downloads.location.prompt
|
||||
prompt_download_directory = config.get('storage',
|
||||
'prompt-download-directory')
|
||||
|
||||
if not prompt_download_directory:
|
||||
return download_dir()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _path_suggestion(filename):
|
||||
"""Get the suggested file path.
|
||||
@@ -112,7 +104,7 @@ def _path_suggestion(filename):
|
||||
Args:
|
||||
filename: The filename to use if included in the suggestion.
|
||||
"""
|
||||
suggestion = config.val.downloads.location.suggestion
|
||||
suggestion = config.get('completion', 'download-path-suggestion')
|
||||
if suggestion == 'path':
|
||||
# add trailing '/' if not present
|
||||
return os.path.join(download_dir(), '')
|
||||
@@ -139,8 +131,7 @@ def create_full_filename(basename, filename):
|
||||
encoding = sys.getfilesystemencoding()
|
||||
filename = utils.force_encoding(filename, encoding)
|
||||
basename = utils.force_encoding(basename, encoding)
|
||||
if os.path.isabs(filename) and (os.path.isdir(filename) or
|
||||
filename.endswith(os.sep)):
|
||||
if os.path.isabs(filename) and os.path.isdir(filename):
|
||||
# We got an absolute directory from the user, so we save it under
|
||||
# the default filename in that directory.
|
||||
return os.path.join(filename, basename)
|
||||
@@ -166,7 +157,6 @@ def get_filename_question(*, suggested_filename, url, parent=None):
|
||||
q.title = "Save file to:"
|
||||
q.text = "Please enter a location for <b>{}</b>".format(
|
||||
html.escape(url.toDisplayString()))
|
||||
q.url = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
q.mode = usertypes.PromptMode.download
|
||||
q.completed.connect(q.deleteLater)
|
||||
q.default = _path_suggestion(suggested_filename)
|
||||
@@ -178,12 +168,12 @@ def transform_path(path):
|
||||
|
||||
Returns None if the path is invalid on the current platform.
|
||||
"""
|
||||
if not utils.is_windows:
|
||||
if sys.platform != "win32":
|
||||
return path
|
||||
path = utils.expand_windows_drive(path)
|
||||
# Drive dependent working directories are not supported, e.g.
|
||||
# E:filename is invalid
|
||||
if re.search(r'^[A-Z]:[^\\]', path, re.IGNORECASE):
|
||||
if re.match(r'[A-Z]:[^\\]', path, re.IGNORECASE):
|
||||
return None
|
||||
# Paths like COM1, ...
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/82
|
||||
@@ -192,28 +182,6 @@ def transform_path(path):
|
||||
return path
|
||||
|
||||
|
||||
def suggested_fn_from_title(url_path, title=None):
|
||||
"""Suggest a filename depending on the URL extension and page title.
|
||||
|
||||
Args:
|
||||
url_path: a string with the URL path
|
||||
title: the page title string
|
||||
|
||||
Return:
|
||||
The download filename based on the title, or None if the extension is
|
||||
not found in the whitelist (or if there is no page title).
|
||||
"""
|
||||
ext_whitelist = [".html", ".htm", ".php", ""]
|
||||
_, ext = os.path.splitext(url_path)
|
||||
if ext.lower() in ext_whitelist and title:
|
||||
suggested_fn = utils.sanitize_filename(title)
|
||||
if not suggested_fn.lower().endswith((".html", ".htm")):
|
||||
suggested_fn += ".html"
|
||||
else:
|
||||
suggested_fn = None
|
||||
return suggested_fn
|
||||
|
||||
|
||||
class NoFilenameError(Exception):
|
||||
|
||||
"""Raised when we can't find out a filename in DownloadTarget."""
|
||||
@@ -238,14 +206,11 @@ class FileDownloadTarget(_DownloadTarget):
|
||||
|
||||
Attributes:
|
||||
filename: Filename where the download should be saved.
|
||||
force_overwrite: Whether to overwrite the target without
|
||||
prompting the user.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, force_overwrite=False):
|
||||
def __init__(self, filename):
|
||||
# pylint: disable=super-init-not-called
|
||||
self.filename = filename
|
||||
self.force_overwrite = force_overwrite
|
||||
|
||||
def suggested_filename(self):
|
||||
return os.path.basename(self.filename)
|
||||
@@ -507,13 +472,13 @@ class AbstractDownloadItem(QObject):
|
||||
Args:
|
||||
position: The color type requested, can be 'fg' or 'bg'.
|
||||
"""
|
||||
# pylint: disable=bad-config-call
|
||||
# WORKAROUND for https://bitbucket.org/logilab/astroid/issue/104/
|
||||
assert position in ["fg", "bg"]
|
||||
# pylint: disable=bad-config-option
|
||||
start = getattr(config.val.colors.downloads.start, position)
|
||||
stop = getattr(config.val.colors.downloads.stop, position)
|
||||
system = getattr(config.val.colors.downloads.system, position)
|
||||
error = getattr(config.val.colors.downloads.error, position)
|
||||
# pylint: enable=bad-config-option
|
||||
start = config.get('colors', 'downloads.{}.start'.format(position))
|
||||
stop = config.get('colors', 'downloads.{}.stop'.format(position))
|
||||
system = config.get('colors', 'downloads.{}.system'.format(position))
|
||||
error = config.get('colors', 'downloads.{}.error'.format(position))
|
||||
if self.error_msg is not None:
|
||||
assert not self.successful
|
||||
return error
|
||||
@@ -585,7 +550,7 @@ class AbstractDownloadItem(QObject):
|
||||
Args:
|
||||
cmdline: The command to use as string. A `{}` is expanded to the
|
||||
filename. None means to use the system's default
|
||||
application or `downloads.open_dispatcher` if set. If no
|
||||
application or `default-open-dispatcher` if set. If no
|
||||
`{}` is found, the filename is appended to the cmdline.
|
||||
"""
|
||||
assert self.successful
|
||||
@@ -611,11 +576,6 @@ class AbstractDownloadItem(QObject):
|
||||
"""Ask a confirmation question for the download."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _ask_create_parent_question(self, title, msg,
|
||||
force_overwrite, remember_directory):
|
||||
"""Ask a confirmation question for the parent directory."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _set_fileobj(self, fileobj, *, autoclose=True):
|
||||
"""Set a file object to save the download to.
|
||||
|
||||
@@ -642,6 +602,7 @@ class AbstractDownloadItem(QObject):
|
||||
remember_directory: If True, remember the directory for future
|
||||
downloads.
|
||||
"""
|
||||
global last_used_directory
|
||||
filename = os.path.expanduser(filename)
|
||||
self._ensure_can_set_filename(filename)
|
||||
|
||||
@@ -668,41 +629,11 @@ class AbstractDownloadItem(QObject):
|
||||
self._filename = create_full_filename(self.basename,
|
||||
os.path.expanduser('~'))
|
||||
|
||||
dirname = os.path.dirname(self._filename)
|
||||
if not os.path.exists(dirname):
|
||||
txt = ("<b>{}</b> does not exist. Create it?".
|
||||
format(html.escape(
|
||||
os.path.join(dirname, ""))))
|
||||
self._ask_create_parent_question("Create directory?", txt,
|
||||
force_overwrite,
|
||||
remember_directory)
|
||||
else:
|
||||
self._after_create_parent_question(force_overwrite,
|
||||
remember_directory)
|
||||
|
||||
def _after_create_parent_question(self,
|
||||
force_overwrite, remember_directory):
|
||||
"""After asking about parent directory.
|
||||
|
||||
Args:
|
||||
force_overwrite: Force overwriting existing files.
|
||||
remember_directory: If True, remember the directory for future
|
||||
downloads.
|
||||
"""
|
||||
global last_used_directory
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self._filename))
|
||||
except FileExistsError:
|
||||
pass
|
||||
except OSError as e:
|
||||
self._die(e.strerror)
|
||||
|
||||
self.basename = os.path.basename(self._filename)
|
||||
if remember_directory:
|
||||
last_used_directory = os.path.dirname(self._filename)
|
||||
|
||||
log.downloads.debug("Setting filename to {}".format(self._filename))
|
||||
log.downloads.debug("Setting filename to {}".format(filename))
|
||||
if force_overwrite:
|
||||
self._after_set_filename()
|
||||
elif os.path.isfile(self._filename):
|
||||
@@ -741,8 +672,7 @@ class AbstractDownloadItem(QObject):
|
||||
if isinstance(target, FileObjDownloadTarget):
|
||||
self._set_fileobj(target.fileobj, autoclose=False)
|
||||
elif isinstance(target, FileDownloadTarget):
|
||||
self._set_filename(
|
||||
target.filename, force_overwrite=target.force_overwrite)
|
||||
self._set_filename(target.filename)
|
||||
elif isinstance(target, OpenFileDownloadTarget):
|
||||
try:
|
||||
fobj = temp_download_manager.get_tmpfile(self.basename)
|
||||
@@ -805,7 +735,7 @@ class AbstractDownloadManager(QObject):
|
||||
download.remove_requested.connect(functools.partial(
|
||||
self._remove_item, download))
|
||||
|
||||
delay = config.val.downloads.remove_finished
|
||||
delay = config.get('ui', 'remove-finished-downloads')
|
||||
if delay > -1:
|
||||
download.finished.connect(
|
||||
lambda: QTimer.singleShot(delay, download.remove))
|
||||
@@ -997,7 +927,7 @@ class DownloadModel(QAbstractListModel):
|
||||
if not count:
|
||||
count = len(self)
|
||||
raise cmdexc.CommandError("Download {} is already done!"
|
||||
.format(count))
|
||||
.format(count))
|
||||
download.cancel()
|
||||
|
||||
@cmdutils.register(instance='download-model', scope='window')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,13 +21,13 @@
|
||||
|
||||
import functools
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
|
||||
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.config import style
|
||||
from qutebrowser.utils import qtutils, utils, objreg
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
def update_geometry(obj):
|
||||
@@ -64,8 +64,8 @@ class DownloadView(QListView):
|
||||
|
||||
STYLESHEET = """
|
||||
QListView {
|
||||
background-color: {{ conf.colors.downloads.bar.bg }};
|
||||
font: {{ conf.fonts.downloads }};
|
||||
background-color: {{ color['downloads.bg.bar'] }};
|
||||
font: {{ font['downloads'] }};
|
||||
}
|
||||
|
||||
QListView::item {
|
||||
@@ -76,7 +76,7 @@ class DownloadView(QListView):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setStyle(QStyleFactory.create('Fusion'))
|
||||
config.set_register_stylesheet(self)
|
||||
style.set_register_stylesheet(self)
|
||||
self.setResizeMode(QListView.Adjust)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
|
||||
|
||||
@@ -1,382 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2017-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Load, parse and make available Greasemonkey scripts."""
|
||||
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
import fnmatch
|
||||
import functools
|
||||
import glob
|
||||
import textwrap
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
|
||||
javascript, urlmatch, version, usertypes)
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
def _scripts_dir():
|
||||
"""Get the directory of the scripts."""
|
||||
return os.path.join(standarddir.data(), 'greasemonkey')
|
||||
|
||||
|
||||
class GreasemonkeyScript:
|
||||
|
||||
"""Container class for userscripts, parses metadata blocks."""
|
||||
|
||||
def __init__(self, properties, code):
|
||||
self._code = code
|
||||
self.includes = []
|
||||
self.matches = []
|
||||
self.excludes = []
|
||||
self.requires = []
|
||||
self.description = None
|
||||
self.name = None
|
||||
self.namespace = None
|
||||
self.run_at = None
|
||||
self.script_meta = None
|
||||
self.runs_on_sub_frames = True
|
||||
self.jsworld = "main"
|
||||
for name, value in properties:
|
||||
if name == 'name':
|
||||
self.name = value
|
||||
elif name == 'namespace':
|
||||
self.namespace = value
|
||||
elif name == 'description':
|
||||
self.description = value
|
||||
elif name == 'include':
|
||||
self.includes.append(value)
|
||||
elif name == 'match':
|
||||
self.matches.append(value)
|
||||
elif name in ['exclude', 'exclude_match']:
|
||||
self.excludes.append(value)
|
||||
elif name == 'run-at':
|
||||
self.run_at = value
|
||||
elif name == 'noframes':
|
||||
self.runs_on_sub_frames = False
|
||||
elif name == 'require':
|
||||
self.requires.append(value)
|
||||
elif name == 'qute-js-world':
|
||||
self.jsworld = value
|
||||
|
||||
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
||||
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
||||
|
||||
@classmethod
|
||||
def parse(cls, source):
|
||||
"""GreasemonkeyScript factory.
|
||||
|
||||
Takes a userscript source and returns a GreasemonkeyScript.
|
||||
Parses the Greasemonkey metadata block, if present, to fill out
|
||||
attributes.
|
||||
"""
|
||||
matches = re.split(cls.HEADER_REGEX, source, maxsplit=2)
|
||||
try:
|
||||
_head, props, _code = matches
|
||||
except ValueError:
|
||||
props = ""
|
||||
script = cls(re.findall(cls.PROPS_REGEX, props), source)
|
||||
script.script_meta = props
|
||||
if not script.includes and not script.matches:
|
||||
script.includes = ['*']
|
||||
return script
|
||||
|
||||
def code(self):
|
||||
"""Return the processed JavaScript code of this script.
|
||||
|
||||
Adorns the source code with GM_* methods for Greasemonkey
|
||||
compatibility and wraps it in an IIFE to hide it within a
|
||||
lexical scope. Note that this means line numbers in your
|
||||
browser's debugger/inspector will not match up to the line
|
||||
numbers in the source script directly.
|
||||
"""
|
||||
# Don't use Proxy on this webkit version, the support isn't there.
|
||||
use_proxy = not (
|
||||
objects.backend == usertypes.Backend.QtWebKit and
|
||||
version.qWebKitVersion() == '602.1')
|
||||
template = jinja.js_environment.get_template('greasemonkey_wrapper.js')
|
||||
return template.render(
|
||||
scriptName=javascript.string_escape(
|
||||
"/".join([self.namespace or '', self.name])),
|
||||
scriptInfo=self._meta_json(),
|
||||
scriptMeta=javascript.string_escape(self.script_meta),
|
||||
scriptSource=self._code,
|
||||
use_proxy=use_proxy)
|
||||
|
||||
def _meta_json(self):
|
||||
return json.dumps({
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'matches': self.matches,
|
||||
'includes': self.includes,
|
||||
'excludes': self.excludes,
|
||||
'run-at': self.run_at,
|
||||
})
|
||||
|
||||
def add_required_script(self, source):
|
||||
"""Add the source of a required script to this script."""
|
||||
# The additional source is indented in case it also contains a
|
||||
# metadata block. Because we pass everything at once to
|
||||
# QWebEngineScript and that would parse the first metadata block
|
||||
# found as the valid one.
|
||||
self._code = "\n".join([textwrap.indent(source, " "), self._code])
|
||||
|
||||
|
||||
@attr.s
|
||||
class MatchingScripts(object):
|
||||
|
||||
"""All userscripts registered to run on a particular url."""
|
||||
|
||||
url = attr.ib()
|
||||
start = attr.ib(default=attr.Factory(list))
|
||||
end = attr.ib(default=attr.Factory(list))
|
||||
idle = attr.ib(default=attr.Factory(list))
|
||||
|
||||
|
||||
class GreasemonkeyMatcher:
|
||||
|
||||
"""Check whether scripts should be loaded for a given URL."""
|
||||
|
||||
# https://wiki.greasespot.net/Include_and_exclude_rules#Greaseable_schemes
|
||||
# Limit the schemes scripts can run on due to unreasonable levels of
|
||||
# exploitability
|
||||
GREASEABLE_SCHEMES = ['http', 'https', 'ftp', 'file']
|
||||
|
||||
def __init__(self, url):
|
||||
self._url = url
|
||||
self._url_string = url.toString(QUrl.FullyEncoded)
|
||||
self.is_greaseable = url.scheme() in self.GREASEABLE_SCHEMES
|
||||
|
||||
def _match_pattern(self, pattern):
|
||||
# For include and exclude rules if they start and end with '/' they
|
||||
# should be treated as a (ecma syntax) regular expression.
|
||||
if pattern.startswith('/') and pattern.endswith('/'):
|
||||
matches = re.search(pattern[1:-1], self._url_string, flags=re.I)
|
||||
return matches is not None
|
||||
|
||||
# Otherwise they are glob expressions.
|
||||
return fnmatch.fnmatch(self._url_string, pattern)
|
||||
|
||||
def matches(self, script):
|
||||
"""Check whether the URL matches filtering rules of the script."""
|
||||
assert self.is_greaseable
|
||||
matching_includes = any(self._match_pattern(pat)
|
||||
for pat in script.includes)
|
||||
matching_match = any(urlmatch.UrlPattern(pat).matches(self._url)
|
||||
for pat in script.matches)
|
||||
matching_excludes = any(self._match_pattern(pat)
|
||||
for pat in script.excludes)
|
||||
return (matching_includes or matching_match) and not matching_excludes
|
||||
|
||||
|
||||
class GreasemonkeyManager(QObject):
|
||||
|
||||
"""Manager of userscripts and a Greasemonkey compatible environment.
|
||||
|
||||
Signals:
|
||||
scripts_reloaded: Emitted when scripts are reloaded from disk.
|
||||
Any cached or already-injected scripts should be
|
||||
considered obsolete.
|
||||
"""
|
||||
|
||||
scripts_reloaded = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._run_start = []
|
||||
self._run_end = []
|
||||
self._run_idle = []
|
||||
self._in_progress_dls = []
|
||||
|
||||
self.load_scripts()
|
||||
|
||||
@cmdutils.register(name='greasemonkey-reload',
|
||||
instance='greasemonkey')
|
||||
def load_scripts(self, force=False):
|
||||
"""Re-read Greasemonkey scripts from disk.
|
||||
|
||||
The scripts are read from a 'greasemonkey' subdirectory in
|
||||
qutebrowser's data directory (see `:version`).
|
||||
|
||||
Args:
|
||||
force: For any scripts that have required dependencies,
|
||||
re-download them.
|
||||
"""
|
||||
self._run_start = []
|
||||
self._run_end = []
|
||||
self._run_idle = []
|
||||
|
||||
scripts_dir = os.path.abspath(_scripts_dir())
|
||||
log.greasemonkey.debug("Reading scripts from: {}".format(scripts_dir))
|
||||
for script_filename in glob.glob(os.path.join(scripts_dir, '*.js')):
|
||||
if not os.path.isfile(script_filename):
|
||||
continue
|
||||
script_path = os.path.join(scripts_dir, script_filename)
|
||||
with open(script_path, encoding='utf-8') as script_file:
|
||||
script = GreasemonkeyScript.parse(script_file.read())
|
||||
if not script.name:
|
||||
script.name = script_filename
|
||||
self.add_script(script, force)
|
||||
self.scripts_reloaded.emit()
|
||||
|
||||
def add_script(self, script, force=False):
|
||||
"""Add a GreasemonkeyScript to this manager.
|
||||
|
||||
Args:
|
||||
force: Fetch and overwrite any dependancies which are
|
||||
already locally cached.
|
||||
"""
|
||||
if script.requires:
|
||||
log.greasemonkey.debug(
|
||||
"Deferring script until requirements are "
|
||||
"fulfilled: {}".format(script.name))
|
||||
self._get_required_scripts(script, force)
|
||||
else:
|
||||
self._add_script(script)
|
||||
|
||||
def _add_script(self, script):
|
||||
if script.run_at == 'document-start':
|
||||
self._run_start.append(script)
|
||||
elif script.run_at == 'document-end':
|
||||
self._run_end.append(script)
|
||||
elif script.run_at == 'document-idle':
|
||||
self._run_idle.append(script)
|
||||
else:
|
||||
if script.run_at:
|
||||
log.greasemonkey.warning("Script {} has invalid run-at "
|
||||
"defined, defaulting to "
|
||||
"document-end"
|
||||
.format(script.name))
|
||||
# Default as per
|
||||
# https://wiki.greasespot.net/Metadata_Block#.40run-at
|
||||
self._run_end.append(script)
|
||||
log.greasemonkey.debug("Loaded script: {}".format(script.name))
|
||||
|
||||
def _required_url_to_file_path(self, url):
|
||||
requires_dir = os.path.join(_scripts_dir(), 'requires')
|
||||
if not os.path.exists(requires_dir):
|
||||
os.mkdir(requires_dir)
|
||||
return os.path.join(requires_dir, utils.sanitize_filename(url))
|
||||
|
||||
def _on_required_download_finished(self, script, download):
|
||||
self._in_progress_dls.remove(download)
|
||||
if not self._add_script_with_requires(script):
|
||||
log.greasemonkey.debug(
|
||||
"Finished download {} for script {} "
|
||||
"but some requirements are still pending"
|
||||
.format(download.basename, script.name))
|
||||
|
||||
def _add_script_with_requires(self, script, quiet=False):
|
||||
"""Add a script with pending downloads to this GreasemonkeyManager.
|
||||
|
||||
Specifically a script that has dependancies specified via an
|
||||
`@require` rule.
|
||||
|
||||
Args:
|
||||
script: The GreasemonkeyScript to add.
|
||||
quiet: True to suppress the scripts_reloaded signal after
|
||||
adding `script`.
|
||||
Returns: True if the script was added, False if there are still
|
||||
dependancies being downloaded.
|
||||
"""
|
||||
# See if we are still waiting on any required scripts for this one
|
||||
for dl in self._in_progress_dls:
|
||||
if dl.requested_url in script.requires:
|
||||
return False
|
||||
|
||||
# Need to add the required scripts to the IIFE now
|
||||
for url in reversed(script.requires):
|
||||
target_path = self._required_url_to_file_path(url)
|
||||
log.greasemonkey.debug(
|
||||
"Adding required script for {} to IIFE: {}"
|
||||
.format(script.name, url))
|
||||
with open(target_path, encoding='utf8') as f:
|
||||
script.add_required_script(f.read())
|
||||
|
||||
self._add_script(script)
|
||||
if not quiet:
|
||||
self.scripts_reloaded.emit()
|
||||
return True
|
||||
|
||||
def _get_required_scripts(self, script, force=False):
|
||||
required_dls = [(url, self._required_url_to_file_path(url))
|
||||
for url in script.requires]
|
||||
if not force:
|
||||
required_dls = [(url, path) for (url, path) in required_dls
|
||||
if not os.path.exists(path)]
|
||||
if not required_dls:
|
||||
# All the required files exist already
|
||||
self._add_script_with_requires(script, quiet=True)
|
||||
return
|
||||
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
|
||||
for url, target_path in required_dls:
|
||||
target = downloads.FileDownloadTarget(target_path,
|
||||
force_overwrite=True)
|
||||
download = download_manager.get(QUrl(url), target=target,
|
||||
auto_remove=True)
|
||||
download.requested_url = url
|
||||
self._in_progress_dls.append(download)
|
||||
if download.successful:
|
||||
self._on_required_download_finished(script, download)
|
||||
else:
|
||||
download.finished.connect(
|
||||
functools.partial(self._on_required_download_finished,
|
||||
script, download))
|
||||
|
||||
def scripts_for(self, url):
|
||||
"""Fetch scripts that are registered to run for url.
|
||||
|
||||
returns a tuple of lists of scripts meant to run at (document-start,
|
||||
document-end, document-idle)
|
||||
"""
|
||||
matcher = GreasemonkeyMatcher(url)
|
||||
if not matcher.is_greaseable:
|
||||
return MatchingScripts(url, [], [], [])
|
||||
return MatchingScripts(
|
||||
url=url,
|
||||
start=[script for script in self._run_start
|
||||
if matcher.matches(script)],
|
||||
end=[script for script in self._run_end
|
||||
if matcher.matches(script)],
|
||||
idle=[script for script in self._run_idle
|
||||
if matcher.matches(script)]
|
||||
)
|
||||
|
||||
def all_scripts(self):
|
||||
"""Return all scripts found in the configured script directory."""
|
||||
return self._run_start + self._run_end + self._run_idle
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize Greasemonkey support."""
|
||||
gm_manager = GreasemonkeyManager()
|
||||
objreg.register('greasemonkey', gm_manager)
|
||||
|
||||
try:
|
||||
os.mkdir(_scripts_dir())
|
||||
except FileExistsError:
|
||||
pass
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -22,26 +22,24 @@
|
||||
import collections
|
||||
import functools
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import html
|
||||
import enum
|
||||
from string import ascii_lowercase
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QUrl
|
||||
from PyQt5.QtWidgets import QLabel
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.config import config, style
|
||||
from qutebrowser.keyinput import modeman, modeparsers
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||
from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils
|
||||
|
||||
|
||||
Target = enum.Enum('Target', ['normal', 'current', 'tab', 'tab_fg', 'tab_bg',
|
||||
'window', 'yank', 'yank_primary', 'run', 'fill',
|
||||
'hover', 'download', 'userscript', 'spawn'])
|
||||
Target = usertypes.enum('Target', ['normal', 'current', 'tab', 'tab_fg',
|
||||
'tab_bg', 'window', 'yank', 'yank_primary',
|
||||
'run', 'fill', 'hover', 'download',
|
||||
'userscript', 'spawn'])
|
||||
|
||||
|
||||
class HintingError(Exception):
|
||||
@@ -67,10 +65,10 @@ class HintLabel(QLabel):
|
||||
|
||||
STYLESHEET = """
|
||||
QLabel {
|
||||
background-color: {{ conf.colors.hints.bg }};
|
||||
color: {{ conf.colors.hints.fg }};
|
||||
font: {{ conf.fonts.hints }};
|
||||
border: {{ conf.hints.border }};
|
||||
background-color: {{ color['hints.bg'] }};
|
||||
color: {{ color['hints.fg'] }};
|
||||
font: {{ font['hints'] }};
|
||||
border: {{ config.get('hints', 'border') }};
|
||||
padding-left: -3px;
|
||||
padding-right: -3px;
|
||||
}
|
||||
@@ -82,7 +80,7 @@ class HintLabel(QLabel):
|
||||
self.elem = elem
|
||||
|
||||
self.setAttribute(Qt.WA_StyledBackground, True)
|
||||
config.set_register_stylesheet(self)
|
||||
style.set_register_stylesheet(self)
|
||||
|
||||
self._context.tab.contents_size_changed.connect(self._move_to_elem)
|
||||
self._move_to_elem()
|
||||
@@ -102,7 +100,7 @@ class HintLabel(QLabel):
|
||||
matched: The part of the text which was typed.
|
||||
unmatched: The part of the text which was not typed yet.
|
||||
"""
|
||||
if (config.val.hints.uppercase and
|
||||
if (config.get('hints', 'uppercase') and
|
||||
self._context.hint_mode in ['letter', 'word']):
|
||||
matched = html.escape(matched.upper())
|
||||
unmatched = html.escape(unmatched.upper())
|
||||
@@ -110,7 +108,7 @@ class HintLabel(QLabel):
|
||||
matched = html.escape(matched)
|
||||
unmatched = html.escape(unmatched)
|
||||
|
||||
match_color = html.escape(config.val.colors.hints.match.fg)
|
||||
match_color = html.escape(config.get('colors', 'hints.fg.match'))
|
||||
self.setText('<font color="{}">{}</font>{}'.format(
|
||||
match_color, matched, unmatched))
|
||||
self.adjustSize()
|
||||
@@ -123,7 +121,7 @@ class HintLabel(QLabel):
|
||||
log.hints.debug("Frame for {!r} vanished!".format(self))
|
||||
self.hide()
|
||||
return
|
||||
no_js = config.val.hints.find_implementation != 'javascript'
|
||||
no_js = config.get('hints', 'find-implementation') != 'javascript'
|
||||
rect = self.elem.rect_on_view(no_js=no_js)
|
||||
self.move(rect.x(), rect.y())
|
||||
|
||||
@@ -133,7 +131,6 @@ class HintLabel(QLabel):
|
||||
self.deleteLater()
|
||||
|
||||
|
||||
@attr.s
|
||||
class HintContext:
|
||||
|
||||
"""Context namespace used for hinting.
|
||||
@@ -155,27 +152,25 @@ class HintContext:
|
||||
to_follow: The link to follow when enter is pressed.
|
||||
args: Custom arguments for userscript/spawn
|
||||
rapid: Whether to do rapid hinting.
|
||||
first_run: Whether the action is run for the 1st time in rapid hinting.
|
||||
add_history: Whether to add yanked or spawned link to the history.
|
||||
filterstr: Used to save the filter string for restoring in rapid mode.
|
||||
tab: The WebTab object we started hinting in.
|
||||
group: The group of web elements to hint.
|
||||
"""
|
||||
|
||||
all_labels = attr.ib(attr.Factory(list))
|
||||
labels = attr.ib(attr.Factory(dict))
|
||||
target = attr.ib(None)
|
||||
baseurl = attr.ib(None)
|
||||
to_follow = attr.ib(None)
|
||||
rapid = attr.ib(False)
|
||||
first_run = attr.ib(True)
|
||||
add_history = attr.ib(False)
|
||||
filterstr = attr.ib(None)
|
||||
args = attr.ib(attr.Factory(list))
|
||||
tab = attr.ib(None)
|
||||
group = attr.ib(None)
|
||||
hint_mode = attr.ib(None)
|
||||
first = attr.ib(False)
|
||||
def __init__(self):
|
||||
self.all_labels = []
|
||||
self.labels = {}
|
||||
self.target = None
|
||||
self.baseurl = None
|
||||
self.to_follow = None
|
||||
self.rapid = False
|
||||
self.add_history = False
|
||||
self.filterstr = None
|
||||
self.args = []
|
||||
self.tab = None
|
||||
self.group = None
|
||||
self.hint_mode = None
|
||||
|
||||
def get_args(self, urlstr):
|
||||
"""Get the arguments, with {hint-url} replaced by the given URL."""
|
||||
@@ -208,7 +203,7 @@ class HintActions:
|
||||
Target.window: usertypes.ClickTarget.window,
|
||||
Target.hover: usertypes.ClickTarget.normal,
|
||||
}
|
||||
if config.val.tabs.background:
|
||||
if config.get('tabs', 'background-tabs'):
|
||||
target_mapping[Target.tab] = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target_mapping[Target.tab] = usertypes.ClickTarget.tab
|
||||
@@ -244,18 +239,7 @@ class HintActions:
|
||||
if url.scheme() == 'mailto':
|
||||
flags |= QUrl.RemoveScheme
|
||||
urlstr = url.toString(flags)
|
||||
|
||||
new_content = urlstr
|
||||
|
||||
# only second and consecutive yanks are to append to the clipboard
|
||||
if context.rapid and not context.first_run:
|
||||
try:
|
||||
old_content = utils.get_clipboard(selection=sel)
|
||||
except utils.ClipboardEmptyError:
|
||||
pass
|
||||
else:
|
||||
new_content = os.linesep.join([old_content, new_content])
|
||||
utils.set_clipboard(new_content, selection=sel)
|
||||
utils.set_clipboard(urlstr, selection=sel)
|
||||
|
||||
msg = "Yanked URL to {}: {}".format(
|
||||
"primary selection" if sel else "clipboard",
|
||||
@@ -306,7 +290,8 @@ class HintActions:
|
||||
user_agent = context.tab.user_agent()
|
||||
|
||||
# FIXME:qtwebengine do this with QtWebEngine downloads?
|
||||
download_manager = objreg.get('qtnetwork-download-manager')
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window=self._win_id)
|
||||
download_manager.get(url, qnam=qnam, user_agent=user_agent,
|
||||
prompt_download_directory=prompt)
|
||||
|
||||
@@ -436,9 +421,9 @@ class HintManager(QObject):
|
||||
if hint_mode == 'number':
|
||||
chars = '0123456789'
|
||||
else:
|
||||
chars = config.val.hints.chars
|
||||
min_chars = config.val.hints.min_chars
|
||||
if config.val.hints.scatter and hint_mode != 'number':
|
||||
chars = config.get('hints', 'chars')
|
||||
min_chars = config.get('hints', 'min-chars')
|
||||
if config.get('hints', 'scatter') and hint_mode != 'number':
|
||||
return self._hint_scattered(min_chars, chars, elems)
|
||||
else:
|
||||
return self._hint_linear(min_chars, chars, elems)
|
||||
@@ -458,17 +443,8 @@ class HintManager(QObject):
|
||||
# Short hints are the number of hints we can possibly show which are
|
||||
# (needed - 1) digits in length.
|
||||
if needed > min_chars:
|
||||
total_space = len(chars) ** needed
|
||||
# Calculate short_count naively, by finding the avaiable space and
|
||||
# dividing by the number of spots we would loose by adding a
|
||||
# short element
|
||||
short_count = math.floor((total_space - len(elems)) /
|
||||
short_count = math.floor((len(chars) ** needed - len(elems)) /
|
||||
len(chars))
|
||||
# Check if we double counted above to warrant another short_count
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3242
|
||||
if total_space - (short_count * len(chars) +
|
||||
(len(elems) - short_count)) >= len(chars) - 1:
|
||||
short_count += 1
|
||||
else:
|
||||
short_count = 0
|
||||
|
||||
@@ -627,30 +603,22 @@ class HintManager(QObject):
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.hint,
|
||||
'HintManager.start')
|
||||
|
||||
if self._context.first:
|
||||
self._fire(strings[0])
|
||||
return
|
||||
# to make auto_follow == 'always' work
|
||||
# to make auto-follow == 'always' work
|
||||
self._handle_auto_follow()
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
|
||||
star_args_optional=True, maxsplit=2)
|
||||
@cmdutils.argument('win_id', win_id=True)
|
||||
def start(self, # pylint: disable=keyword-arg-before-vararg
|
||||
group=webelem.Group.all, target=Target.normal,
|
||||
*args, win_id, mode=None, add_history=False, rapid=False,
|
||||
first=False):
|
||||
def start(self, rapid=False, group=webelem.Group.all, target=Target.normal,
|
||||
*args, win_id, mode=None, add_history=False):
|
||||
"""Start hinting.
|
||||
|
||||
Args:
|
||||
rapid: Whether to do rapid hinting. With rapid hinting, the hint
|
||||
mode isn't left after a hint is followed, so you can easily
|
||||
open multiple links. This is only possible with targets
|
||||
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
rapid: Whether to do rapid hinting. This is only possible with
|
||||
targets `tab` (with background-tabs=true), `tab-bg`,
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
add_history: Whether to add the spawned or yanked link to the
|
||||
browsing history.
|
||||
first: Click the first hinted element without prompting.
|
||||
group: The element types to hint.
|
||||
|
||||
- `all`: All clickable elements.
|
||||
@@ -663,7 +631,7 @@ class HintManager(QObject):
|
||||
- `normal`: Open the link.
|
||||
- `current`: Open the link in the current tab.
|
||||
- `tab`: Open the link in a new tab (honoring the
|
||||
`tabs.background_tabs` setting).
|
||||
background-tabs setting).
|
||||
- `tab-fg`: Open the link in a new foreground tab.
|
||||
- `tab-bg`: Open the link in a new background tab.
|
||||
- `window`: Open the link in a new window.
|
||||
@@ -681,7 +649,7 @@ class HintManager(QObject):
|
||||
mode: The hinting mode to use.
|
||||
|
||||
- `number`: Use numeric hints.
|
||||
- `letter`: Use the chars in the hints.chars setting.
|
||||
- `letter`: Use the chars in the hints->chars settings.
|
||||
- `word`: Use hint words based on the html elements and the
|
||||
extra words.
|
||||
|
||||
@@ -702,7 +670,7 @@ class HintManager(QObject):
|
||||
"""
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
tab = tabbed_browser.widget.currentWidget()
|
||||
tab = tabbed_browser.currentWidget()
|
||||
if tab is None:
|
||||
raise cmdexc.CommandError("No WebView available yet!")
|
||||
|
||||
@@ -714,10 +682,10 @@ class HintManager(QObject):
|
||||
if rapid:
|
||||
if target in [Target.tab_bg, Target.window, Target.run,
|
||||
Target.hover, Target.userscript, Target.spawn,
|
||||
Target.download, Target.normal, Target.current,
|
||||
Target.yank, Target.yank_primary]:
|
||||
Target.download, Target.normal, Target.current]:
|
||||
pass
|
||||
elif target == Target.tab and config.val.tabs.background:
|
||||
elif (target == Target.tab and
|
||||
config.get('tabs', 'background-tabs')):
|
||||
pass
|
||||
else:
|
||||
name = target.name.replace('_', '-')
|
||||
@@ -725,7 +693,7 @@ class HintManager(QObject):
|
||||
"target {}!".format(name))
|
||||
|
||||
if mode is None:
|
||||
mode = config.val.hints.mode
|
||||
mode = config.get('hints', 'mode')
|
||||
|
||||
self._check_args(target, *args)
|
||||
self._context = HintContext()
|
||||
@@ -734,7 +702,6 @@ class HintManager(QObject):
|
||||
self._context.rapid = rapid
|
||||
self._context.hint_mode = mode
|
||||
self._context.add_history = add_history
|
||||
self._context.first = first
|
||||
try:
|
||||
self._context.baseurl = tabbed_browser.current_url()
|
||||
except qtutils.QtValueError:
|
||||
@@ -753,7 +720,7 @@ class HintManager(QObject):
|
||||
return self._context.hint_mode
|
||||
|
||||
def _handle_auto_follow(self, keystr="", filterstr="", visible=None):
|
||||
"""Handle the auto_follow option."""
|
||||
"""Handle the auto-follow option."""
|
||||
if visible is None:
|
||||
visible = {string: label
|
||||
for string, label in self._context.labels.items()
|
||||
@@ -762,7 +729,7 @@ class HintManager(QObject):
|
||||
if len(visible) != 1:
|
||||
return
|
||||
|
||||
auto_follow = config.val.hints.auto_follow
|
||||
auto_follow = config.get('hints', 'auto-follow')
|
||||
|
||||
if auto_follow == "always":
|
||||
follow = True
|
||||
@@ -779,8 +746,8 @@ class HintManager(QObject):
|
||||
self._context.to_follow = list(visible.keys())[0]
|
||||
|
||||
if follow:
|
||||
# apply auto_follow_timeout
|
||||
timeout = config.val.hints.auto_follow_timeout
|
||||
# apply auto-follow-timeout
|
||||
timeout = config.get('hints', 'auto-follow-timeout')
|
||||
keyparsers = objreg.get('keyparsers', scope='window',
|
||||
window=self._win_id)
|
||||
normal_parser = keyparsers[usertypes.KeyMode.normal]
|
||||
@@ -804,9 +771,9 @@ class HintManager(QObject):
|
||||
label.show()
|
||||
else:
|
||||
# element doesn't match anymore -> hide it, unless in rapid
|
||||
# mode and hide_unmatched_rapid_hints is false (see #1799)
|
||||
# mode and hide-unmatched-rapid-hints is false (see #1799)
|
||||
if (not self._context.rapid or
|
||||
config.val.hints.hide_unmatched_rapid_hints):
|
||||
config.get('hints', 'hide-unmatched-rapid-hints')):
|
||||
label.hide()
|
||||
except webelem.Error:
|
||||
pass
|
||||
@@ -826,8 +793,6 @@ class HintManager(QObject):
|
||||
else:
|
||||
self._context.filterstr = filterstr
|
||||
|
||||
log.hints.debug("Filtering hints on {!r}".format(filterstr))
|
||||
|
||||
visible = []
|
||||
for label in self._context.all_labels:
|
||||
try:
|
||||
@@ -929,32 +894,22 @@ class HintManager(QObject):
|
||||
except HintingError as e:
|
||||
message.error(str(e))
|
||||
|
||||
if self._context is not None:
|
||||
self._context.first_run = False
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab',
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', hide=True,
|
||||
modes=[usertypes.KeyMode.hint])
|
||||
def follow_hint(self, select=False, keystring=None):
|
||||
def follow_hint(self, keystring=None):
|
||||
"""Follow a hint.
|
||||
|
||||
Args:
|
||||
select: Only select the given hint, don't necessarily follow it.
|
||||
keystring: The hint to follow, or None.
|
||||
"""
|
||||
if keystring is None:
|
||||
if self._context.to_follow is None:
|
||||
raise cmdexc.CommandError("No hint to follow")
|
||||
elif select:
|
||||
raise cmdexc.CommandError("Can't use --select without hint.")
|
||||
else:
|
||||
keystring = self._context.to_follow
|
||||
elif keystring not in self._context.labels:
|
||||
raise cmdexc.CommandError("No hint {}!".format(keystring))
|
||||
|
||||
if select:
|
||||
self.handle_partial_key(keystring)
|
||||
else:
|
||||
self._fire(keystring)
|
||||
self._fire(keystring)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
@@ -983,7 +938,7 @@ class WordHinter:
|
||||
|
||||
def ensure_initialized(self):
|
||||
"""Generate the used words if yet uninitialized."""
|
||||
dictionary = config.val.hints.dictionary
|
||||
dictionary = config.get("hints", "dictionary")
|
||||
if not self.words or self.dictionary != dictionary:
|
||||
self.words.clear()
|
||||
self.dictionary = dictionary
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -19,127 +19,214 @@
|
||||
|
||||
"""Simple history which gets written to disk."""
|
||||
|
||||
import os
|
||||
import time
|
||||
import contextlib
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer, pyqtSignal
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject
|
||||
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
|
||||
debug, standarddir, qtutils)
|
||||
from qutebrowser.misc import objects, sql
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.utils import (utils, objreg, standarddir, log, qtutils,
|
||||
usertypes, message)
|
||||
from qutebrowser.misc import lineparser, objects
|
||||
|
||||
|
||||
# increment to indicate that HistoryCompletion must be regenerated
|
||||
_USER_VERSION = 2
|
||||
class Entry:
|
||||
|
||||
"""A single entry in the web history.
|
||||
|
||||
Attributes:
|
||||
atime: The time the page was accessed.
|
||||
url: The URL which was accessed as QUrl.
|
||||
redirect: If True, don't save this entry to disk
|
||||
"""
|
||||
|
||||
def __init__(self, atime, url, title, redirect=False):
|
||||
self.atime = float(atime)
|
||||
self.url = url
|
||||
self.title = title
|
||||
self.redirect = redirect
|
||||
qtutils.ensure_valid(url)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, constructor=True, atime=self.atime,
|
||||
url=self.url_str(), title=self.title,
|
||||
redirect=self.redirect)
|
||||
|
||||
def __str__(self):
|
||||
atime = str(int(self.atime))
|
||||
if self.redirect:
|
||||
atime += '-r' # redirect flag
|
||||
elems = [atime, self.url_str()]
|
||||
if self.title:
|
||||
elems.append(self.title)
|
||||
return ' '.join(elems)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.atime == other.atime and
|
||||
self.title == other.title and
|
||||
self.url == other.url and
|
||||
self.redirect == other.redirect)
|
||||
|
||||
def url_str(self):
|
||||
"""Get the URL as a lossless string."""
|
||||
return self.url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, line):
|
||||
"""Parse a history line like '12345 http://example.com title'."""
|
||||
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")
|
||||
|
||||
url = QUrl(url)
|
||||
if not url.isValid():
|
||||
raise ValueError("Invalid URL: {}".format(url.errorString()))
|
||||
|
||||
# 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 cls(atime, url, title, redirect=redirect)
|
||||
|
||||
|
||||
class CompletionHistory(sql.SqlTable):
|
||||
class WebHistory(QObject):
|
||||
|
||||
"""History which only has the newest entry for each URL."""
|
||||
"""The global history of visited pages.
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__("CompletionHistory", ['url', 'title', 'last_atime'],
|
||||
constraints={'url': 'PRIMARY KEY',
|
||||
'title': 'NOT NULL',
|
||||
'last_atime': 'NOT NULL'},
|
||||
parent=parent)
|
||||
self.create_index('CompletionHistoryAtimeIndex', 'last_atime')
|
||||
This is a little more complex as you'd expect so the history can be read
|
||||
from disk async while new history is already arriving.
|
||||
|
||||
self.history_dict is the main place where the history is stored, in an
|
||||
OrderedDict (sorted by time) of URL strings mapped to Entry objects.
|
||||
|
||||
class WebHistory(sql.SqlTable):
|
||||
While reading from disk is still ongoing, the history is saved in
|
||||
self._temp_history instead, and then appended to self.history_dict once
|
||||
that's fully populated.
|
||||
|
||||
"""The global history of visited pages."""
|
||||
All history which is new in this session (rather than read from disk from a
|
||||
previous browsing session) is also stored in self._new_history.
|
||||
self._saved_count tracks how many of those entries were already written to
|
||||
disk, so we can always append to the existing data.
|
||||
|
||||
# All web history cleared
|
||||
history_cleared = pyqtSignal()
|
||||
# one url cleared
|
||||
url_cleared = pyqtSignal(QUrl)
|
||||
Attributes:
|
||||
history_dict: An OrderedDict of URLs read from the on-disk history.
|
||||
_lineparser: The AppendLineParser used to save the history.
|
||||
_new_history: A list of Entry items of the current session.
|
||||
_saved_count: How many HistoryEntries have been written to disk.
|
||||
_initial_read_started: Whether async_read was called.
|
||||
_initial_read_done: Whether async_read has completed.
|
||||
_temp_history: OrderedDict of temporary history entries before
|
||||
async_read was called.
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
|
||||
constraints={'url': 'NOT NULL',
|
||||
'title': 'NOT NULL',
|
||||
'atime': 'NOT NULL',
|
||||
'redirect': 'NOT NULL'},
|
||||
parent=parent)
|
||||
self.completion = CompletionHistory(parent=self)
|
||||
if sql.Query('pragma user_version').run().value() < _USER_VERSION:
|
||||
self.completion.delete_all()
|
||||
if not self.completion:
|
||||
# either the table is out-of-date or the user wiped it manually
|
||||
self._rebuild_completion()
|
||||
self.create_index('HistoryIndex', 'url')
|
||||
self.create_index('HistoryAtimeIndex', 'atime')
|
||||
self._contains_query = self.contains_query('url')
|
||||
self._between_query = sql.Query('SELECT * FROM History '
|
||||
'where not redirect '
|
||||
'and not url like "qute://%" '
|
||||
'and atime > :earliest '
|
||||
'and atime <= :latest '
|
||||
'ORDER BY atime desc')
|
||||
Signals:
|
||||
add_completion_item: Emitted before a new Entry is added.
|
||||
Used to sync with the completion.
|
||||
arg: The new Entry.
|
||||
item_added: Emitted after a new Entry is added.
|
||||
Used to tell the savemanager that the history is dirty.
|
||||
arg: The new Entry.
|
||||
cleared: Emitted after the history is cleared.
|
||||
"""
|
||||
|
||||
self._before_query = sql.Query('SELECT * FROM History '
|
||||
'where not redirect '
|
||||
'and not url like "qute://%" '
|
||||
'and atime <= :latest '
|
||||
'ORDER BY atime desc '
|
||||
'limit :limit offset :offset')
|
||||
add_completion_item = pyqtSignal(Entry)
|
||||
item_added = pyqtSignal(Entry)
|
||||
cleared = pyqtSignal()
|
||||
async_read_done = pyqtSignal()
|
||||
|
||||
def __init__(self, hist_dir, hist_name, parent=None):
|
||||
super().__init__(parent)
|
||||
self._initial_read_started = False
|
||||
self._initial_read_done = False
|
||||
self._lineparser = lineparser.AppendLineParser(hist_dir, hist_name,
|
||||
parent=self)
|
||||
self.history_dict = collections.OrderedDict()
|
||||
self._temp_history = collections.OrderedDict()
|
||||
self._new_history = []
|
||||
self._saved_count = 0
|
||||
objreg.get('save-manager').add_saveable(
|
||||
'history', self.save, self.item_added)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, length=len(self))
|
||||
|
||||
def __contains__(self, url):
|
||||
return self._contains_query.run(val=url).value()
|
||||
def __iter__(self):
|
||||
return iter(self.history_dict.values())
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _handle_sql_errors(self):
|
||||
try:
|
||||
yield
|
||||
except sql.SqlError as e:
|
||||
if e.environmental:
|
||||
message.error("Failed to write history: {}".format(e.text()))
|
||||
else:
|
||||
raise
|
||||
def __len__(self):
|
||||
return len(self.history_dict)
|
||||
|
||||
def _rebuild_completion(self):
|
||||
data = {'url': [], 'title': [], 'last_atime': []}
|
||||
# select the latest entry for each url
|
||||
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
|
||||
'WHERE NOT redirect and url NOT LIKE "qute://back%" '
|
||||
'GROUP BY url ORDER BY atime asc')
|
||||
for entry in q.run():
|
||||
data['url'].append(self._format_completion_url(QUrl(entry.url)))
|
||||
data['title'].append(entry.title)
|
||||
data['last_atime'].append(entry.atime)
|
||||
self.completion.insert_batch(data, replace=True)
|
||||
sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run()
|
||||
def async_read(self):
|
||||
"""Read the initial history."""
|
||||
if self._initial_read_started:
|
||||
log.init.debug("Ignoring async_read() because reading is started.")
|
||||
return
|
||||
self._initial_read_started = True
|
||||
|
||||
with self._lineparser.open():
|
||||
for line in self._lineparser:
|
||||
yield
|
||||
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
try:
|
||||
entry = Entry.from_str(line)
|
||||
except ValueError as e:
|
||||
log.init.warning("Invalid history entry {!r}: {}!".format(
|
||||
line, e))
|
||||
continue
|
||||
|
||||
# This de-duplicates history entries; only the latest
|
||||
# entry for each URL is kept. If you want to keep
|
||||
# information about previous hits change the items in
|
||||
# old_urls to be lists or change Entry to have a
|
||||
# list of atimes.
|
||||
self._add_entry(entry)
|
||||
|
||||
self._initial_read_done = True
|
||||
self.async_read_done.emit()
|
||||
|
||||
for entry in self._temp_history.values():
|
||||
self._add_entry(entry)
|
||||
self._new_history.append(entry)
|
||||
if not entry.redirect:
|
||||
self.add_completion_item.emit(entry)
|
||||
self._temp_history.clear()
|
||||
|
||||
def _add_entry(self, entry, target=None):
|
||||
"""Add an entry to self.history_dict or another given OrderedDict."""
|
||||
if target is None:
|
||||
target = self.history_dict
|
||||
url_str = entry.url_str()
|
||||
target[url_str] = entry
|
||||
target.move_to_end(url_str)
|
||||
|
||||
def get_recent(self):
|
||||
"""Get the most recent history entries."""
|
||||
return self.select(sort_by='atime', sort_order='desc', limit=100)
|
||||
old = self._lineparser.get_recent()
|
||||
return old + [str(e) for e in self._new_history]
|
||||
|
||||
def entries_between(self, earliest, latest):
|
||||
"""Iterate non-redirect, non-qute entries between two timestamps.
|
||||
|
||||
Args:
|
||||
earliest: Omit timestamps earlier than this.
|
||||
latest: Omit timestamps later than this.
|
||||
"""
|
||||
self._between_query.run(earliest=earliest, latest=latest)
|
||||
return iter(self._between_query)
|
||||
|
||||
def entries_before(self, latest, limit, offset):
|
||||
"""Iterate non-redirect, non-qute entries occurring before a timestamp.
|
||||
|
||||
Args:
|
||||
latest: Omit timestamps more recent than this.
|
||||
limit: Max number of entries to include.
|
||||
offset: Number of entries to skip.
|
||||
"""
|
||||
self._before_query.run(latest=latest, limit=limit, offset=offset)
|
||||
return iter(self._before_query)
|
||||
def save(self):
|
||||
"""Save the history to disk."""
|
||||
new = (str(e) for e in self._new_history[self._saved_count:])
|
||||
self._lineparser.new_data = new
|
||||
self._lineparser.save()
|
||||
self._saved_count = len(self._new_history)
|
||||
|
||||
@cmdutils.register(name='history-clear', instance='web-history')
|
||||
def clear(self, force=False):
|
||||
@@ -155,33 +242,21 @@ class WebHistory(sql.SqlTable):
|
||||
if force:
|
||||
self._do_clear()
|
||||
else:
|
||||
message.confirm_async(yes_action=self._do_clear,
|
||||
title="Clear all browsing history?")
|
||||
message.confirm_async(self._do_clear, title="Clear all browsing "
|
||||
"history?")
|
||||
|
||||
def _do_clear(self):
|
||||
with self._handle_sql_errors():
|
||||
self.delete_all()
|
||||
self.completion.delete_all()
|
||||
self.history_cleared.emit()
|
||||
|
||||
def delete_url(self, url):
|
||||
"""Remove all history entries with the given url.
|
||||
|
||||
Args:
|
||||
url: URL string to delete.
|
||||
"""
|
||||
qurl = QUrl(url)
|
||||
qtutils.ensure_valid(qurl)
|
||||
self.delete('url', self._format_url(qurl))
|
||||
self.completion.delete('url', self._format_completion_url(qurl))
|
||||
self.url_cleared.emit(qurl)
|
||||
self._lineparser.clear()
|
||||
self.history_dict.clear()
|
||||
self._temp_history.clear()
|
||||
self._new_history.clear()
|
||||
self._saved_count = 0
|
||||
self.cleared.emit()
|
||||
|
||||
@pyqtSlot(QUrl, QUrl, str)
|
||||
def add_from_tab(self, url, requested_url, title):
|
||||
"""Add a new history entry as slot, called from a BrowserTab."""
|
||||
if any(url.scheme() in ('data', 'view-source') or
|
||||
(url.scheme(), url.host()) == ('qute', 'back')
|
||||
for url in (url, requested_url)):
|
||||
if url.scheme() == 'data' or requested_url.scheme() == 'data':
|
||||
return
|
||||
if url.isEmpty():
|
||||
# things set via setHtml
|
||||
@@ -204,154 +279,23 @@ class WebHistory(sql.SqlTable):
|
||||
(hidden in completion)
|
||||
atime: Override the atime used to add the entry
|
||||
"""
|
||||
if not url.isValid():
|
||||
if not url.isValid(): # pragma: no cover
|
||||
# the no cover pragma is a WORKAROUND for this not being covered in
|
||||
# old Qt versions.
|
||||
log.misc.warning("Ignoring invalid URL being added to history")
|
||||
return
|
||||
|
||||
if 'no-sql-history' in objreg.get('args').debug_flags:
|
||||
return
|
||||
|
||||
atime = int(atime) if (atime is not None) else int(time.time())
|
||||
|
||||
with self._handle_sql_errors():
|
||||
self.insert({'url': self._format_url(url),
|
||||
'title': title,
|
||||
'atime': atime,
|
||||
'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):
|
||||
"""Parse a history line like '12345 http://example.com title'."""
|
||||
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
|
||||
if atime is None:
|
||||
atime = time.time()
|
||||
entry = Entry(atime, url, title, redirect=redirect)
|
||||
if self._initial_read_done:
|
||||
self._add_entry(entry)
|
||||
self._new_history.append(entry)
|
||||
self.item_added.emit(entry)
|
||||
if not entry.redirect:
|
||||
self.add_completion_item.emit(entry)
|
||||
else:
|
||||
raise ValueError("2 or 3 fields expected")
|
||||
|
||||
# http://xn--pple-43d.com/ with
|
||||
# https://bugreports.qt.io/browse/QTBUG-60364
|
||||
if url in ['http://.com/', 'https://.com/',
|
||||
'http://www..com/', 'https://www..com/']:
|
||||
return None
|
||||
|
||||
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):
|
||||
return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
|
||||
def _format_completion_url(self, url):
|
||||
return url.toString(QUrl.RemovePassword)
|
||||
|
||||
@cmdutils.register(instance='web-history', debug=True)
|
||||
def debug_dump_history(self, dest):
|
||||
"""Dump the history to a file in the old pre-SQL format.
|
||||
|
||||
Args:
|
||||
dest: Where to write the file to.
|
||||
"""
|
||||
dest = os.path.expanduser(dest)
|
||||
|
||||
lines = ('{}{} {} {}'
|
||||
.format(int(x.atime), '-r' * x.redirect, x.url, x.title)
|
||||
for x in self.select(sort_by='atime', sort_order='asc'))
|
||||
|
||||
try:
|
||||
with open(dest, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(lines))
|
||||
message.info("Dumped history to {}".format(dest))
|
||||
except OSError as e:
|
||||
raise cmdexc.CommandError('Could not write history: {}'.format(e))
|
||||
self._add_entry(entry, target=self._temp_history)
|
||||
|
||||
|
||||
def init(parent=None):
|
||||
@@ -360,9 +304,10 @@ def init(parent=None):
|
||||
Args:
|
||||
parent: The parent to use for WebHistory.
|
||||
"""
|
||||
history = WebHistory(parent=parent)
|
||||
history = WebHistory(hist_dir=standarddir.data(), hist_name='history',
|
||||
parent=parent)
|
||||
objreg.register('web-history', history)
|
||||
|
||||
if objects.backend == usertypes.Backend.QtWebKit: # pragma: no cover
|
||||
if objects.backend == usertypes.Backend.QtWebKit:
|
||||
from qutebrowser.browser.webkit import webkithistory
|
||||
webkithistory.init(history)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,8 +24,7 @@ import binascii
|
||||
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
||||
from qutebrowser.config import configfiles
|
||||
from qutebrowser.utils import log, usertypes
|
||||
from qutebrowser.utils import log, objreg, usertypes
|
||||
from qutebrowser.misc import miscwidgets, objects
|
||||
|
||||
|
||||
@@ -68,8 +67,9 @@ class AbstractWebInspector(QWidget):
|
||||
|
||||
def _load_state_geometry(self):
|
||||
"""Load the geometry from the state file."""
|
||||
state_config = objreg.get('state-config')
|
||||
try:
|
||||
data = configfiles.state['geometry']['inspector']
|
||||
data = state_config['geometry']['inspector']
|
||||
geom = base64.b64decode(data, validate=True)
|
||||
except KeyError:
|
||||
# First start
|
||||
@@ -84,11 +84,10 @@ class AbstractWebInspector(QWidget):
|
||||
|
||||
def closeEvent(self, e):
|
||||
"""Save the geometry when closed."""
|
||||
state_config = objreg.get('state-config')
|
||||
data = bytes(self.saveGeometry())
|
||||
geom = base64.b64encode(data).decode('ASCII')
|
||||
configfiles.state['geometry']['inspector'] = geom
|
||||
|
||||
self.inspect(None)
|
||||
state_config['geometry']['inspector'] = geom
|
||||
super().closeEvent(e)
|
||||
|
||||
def inspect(self, page):
|
||||
@@ -96,9 +95,7 @@ class AbstractWebInspector(QWidget):
|
||||
raise NotImplementedError
|
||||
|
||||
def toggle(self, page):
|
||||
"""Show/hide the inspector."""
|
||||
if self._widget.isVisible():
|
||||
self.hide()
|
||||
else:
|
||||
self.inspect(page)
|
||||
self.show()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -19,13 +19,15 @@
|
||||
|
||||
"""Mouse handling for a browser tab."""
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log, usertypes, qtutils, objreg
|
||||
from qutebrowser.utils import message, log, usertypes
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||
|
||||
|
||||
class ChildEventFilter(QObject):
|
||||
|
||||
"""An event filter re-adding MouseEventFilter on ChildEvent.
|
||||
@@ -40,12 +42,11 @@ class ChildEventFilter(QObject):
|
||||
_widget: The widget expected to send out childEvents.
|
||||
"""
|
||||
|
||||
def __init__(self, eventfilter, widget, win_id, parent=None):
|
||||
def __init__(self, eventfilter, widget, parent=None):
|
||||
super().__init__(parent)
|
||||
self._filter = eventfilter
|
||||
assert widget is not None
|
||||
self._widget = widget
|
||||
self._win_id = win_id
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Act on ChildAdded events."""
|
||||
@@ -55,29 +56,6 @@ class ChildEventFilter(QObject):
|
||||
obj, child))
|
||||
assert obj is self._widget
|
||||
child.installEventFilter(self._filter)
|
||||
|
||||
if qtutils.version_check('5.11', compiled=False, exact=True):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
|
||||
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:
|
||||
child = event.child()
|
||||
log.mouse.debug("{}: removed child {}".format(obj, child))
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@@ -107,7 +85,7 @@ class MouseEventFilter(QObject):
|
||||
|
||||
def _handle_mouse_press(self, e):
|
||||
"""Handle pressing of a mouse button."""
|
||||
is_rocker_gesture = (config.val.input.rocker_gestures and
|
||||
is_rocker_gesture = (config.get('input', 'rocker-gestures') and
|
||||
e.buttons() == Qt.LeftButton | Qt.RightButton)
|
||||
|
||||
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
|
||||
@@ -141,7 +119,7 @@ class MouseEventFilter(QObject):
|
||||
return True
|
||||
|
||||
if e.modifiers() & Qt.ControlModifier:
|
||||
divider = config.val.zoom.mouse_divider
|
||||
divider = config.get('input', 'mouse-zoom-divider')
|
||||
if divider == 0:
|
||||
return False
|
||||
factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
|
||||
@@ -161,7 +139,7 @@ class MouseEventFilter(QObject):
|
||||
|
||||
def _handle_context_menu(self, _e):
|
||||
"""Suppress context menus if rocker gestures are turned on."""
|
||||
return config.val.input.rocker_gestures
|
||||
return config.get('input', 'rocker-gestures')
|
||||
|
||||
def _mousepress_insertmode_cb(self, elem):
|
||||
"""Check if the clicked element is editable."""
|
||||
@@ -175,12 +153,11 @@ class MouseEventFilter(QObject):
|
||||
|
||||
if elem.is_editable():
|
||||
log.mouse.debug("Clicked editable element!")
|
||||
if config.val.input.insert_mode.auto_enter:
|
||||
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click', only_if_normal=True)
|
||||
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element!")
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click', maybe=True)
|
||||
|
||||
@@ -202,7 +179,7 @@ class MouseEventFilter(QObject):
|
||||
'click-delayed', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element (delayed)!")
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed', maybe=True)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,7 +24,6 @@ import posixpath
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import objreg, urlutils, log, message, qtutils
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
@@ -43,7 +42,7 @@ def incdec(url, count, inc_or_dec):
|
||||
background: Open the link in a new background tab.
|
||||
window: Open the link in a new window.
|
||||
"""
|
||||
segments = set(config.val.url.incdec_segments)
|
||||
segments = set(config.get('general', 'url-incdec-segments'))
|
||||
try:
|
||||
new_url = urlutils.incdec_number(url, inc_or_dec, count,
|
||||
segments=segments)
|
||||
@@ -64,7 +63,6 @@ def path_up(url, count):
|
||||
raise Error("Can't go up!")
|
||||
for _i in range(0, min(count, path.count('/'))):
|
||||
path = posixpath.join(path, posixpath.pardir)
|
||||
path = posixpath.normpath(path)
|
||||
url.setPath(path)
|
||||
return url
|
||||
|
||||
@@ -82,13 +80,10 @@ def _find_prevnext(prev, elems):
|
||||
|
||||
# Then check for regular links/buttons.
|
||||
elems = [e for e in elems if e.tag_name() != 'link']
|
||||
option = 'prev_regexes' if prev else 'next_regexes'
|
||||
option = 'prev-regexes' if prev else 'next-regexes'
|
||||
if not elems:
|
||||
return None
|
||||
|
||||
# pylint: disable=bad-config-option
|
||||
for regex in getattr(config.val.hints, option):
|
||||
# pylint: enable=bad-config-option
|
||||
for regex in config.get('hints', option):
|
||||
log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern))
|
||||
for e in elems:
|
||||
text = str(e)
|
||||
@@ -136,6 +131,7 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
|
||||
window=win_id)
|
||||
|
||||
if window:
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
new_window = mainwindow.MainWindow(
|
||||
private=cur_tabbed_browser.private)
|
||||
new_window.show()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -59,7 +59,6 @@ def _js_slot(*args):
|
||||
def _decorator(method):
|
||||
@functools.wraps(method)
|
||||
def new_method(self, *args, **kwargs):
|
||||
"""Call the underlying function."""
|
||||
try:
|
||||
return method(self, *args, **kwargs)
|
||||
except:
|
||||
@@ -248,21 +247,10 @@ class PACFetcher(QObject):
|
||||
self._pac_url = url
|
||||
self._manager = QNetworkAccessManager()
|
||||
self._manager.setProxy(QNetworkProxy(QNetworkProxy.NoProxy))
|
||||
self._reply = self._manager.get(QNetworkRequest(url))
|
||||
self._reply.finished.connect(self._finish)
|
||||
self._pac = None
|
||||
self._error_message = None
|
||||
self._reply = None
|
||||
|
||||
def __eq__(self, other):
|
||||
# pylint: disable=protected-access
|
||||
return self._pac_url == other._pac_url
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, url=self._pac_url, constructor=True)
|
||||
|
||||
def fetch(self):
|
||||
"""Fetch the proxy from the remote URL."""
|
||||
self._reply = self._manager.get(QNetworkRequest(self._pac_url))
|
||||
self._reply.finished.connect(self._finish)
|
||||
|
||||
@pyqtSlot()
|
||||
def _finish(self):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -34,10 +34,6 @@ def init():
|
||||
QNetworkProxyFactory.setApplicationProxyFactory(proxy_factory)
|
||||
|
||||
|
||||
def shutdown():
|
||||
QNetworkProxyFactory.setApplicationProxyFactory(None)
|
||||
|
||||
|
||||
class ProxyFactory(QNetworkProxyFactory):
|
||||
|
||||
"""Factory for proxies to be used by qutebrowser."""
|
||||
@@ -48,7 +44,7 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
Return:
|
||||
None if proxy is correct, otherwise an error message.
|
||||
"""
|
||||
proxy = config.val.content.proxy
|
||||
proxy = config.get('network', 'proxy')
|
||||
if isinstance(proxy, pac.PACFetcher):
|
||||
return proxy.fetch_error()
|
||||
else:
|
||||
@@ -63,11 +59,8 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
Return:
|
||||
A list of QNetworkProxy objects in order of preference.
|
||||
"""
|
||||
proxy = config.val.content.proxy
|
||||
proxy = config.get('network', 'proxy')
|
||||
if proxy is configtypes.SYSTEM_PROXY:
|
||||
# On Linux, use "export http_proxy=socks5://host:port" to manually
|
||||
# set system proxy.
|
||||
# ref. http://doc.qt.io/qt-5/qnetworkproxyfactory.html#systemProxyForQuery
|
||||
proxies = QNetworkProxyFactory.systemProxyForQuery(query)
|
||||
elif isinstance(proxy, pac.PACFetcher):
|
||||
proxies = proxy.resolve(query)
|
||||
@@ -76,7 +69,7 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
for p in proxies:
|
||||
if p.type() != QNetworkProxy.NoProxy:
|
||||
capabilities = p.capabilities()
|
||||
if config.val.content.proxy_dns_requests:
|
||||
if config.get('network', 'proxy-dns-requests'):
|
||||
capabilities |= QNetworkProxy.HostNameLookupCapability
|
||||
else:
|
||||
capabilities &= ~QNetworkProxy.HostNameLookupCapability
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 Daniel Schadt
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -82,7 +82,7 @@ def fix_urls(asset):
|
||||
('viewer.css', 'qute://pdfjs/web/viewer.css'),
|
||||
('compatibility.js', 'qute://pdfjs/web/compatibility.js'),
|
||||
('locale/locale.properties',
|
||||
'qute://pdfjs/web/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'),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -20,26 +20,21 @@
|
||||
"""Download manager."""
|
||||
|
||||
import io
|
||||
import os.path
|
||||
import shutil
|
||||
import functools
|
||||
import collections
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug
|
||||
from qutebrowser.utils import message, usertypes, log, urlutils, utils
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
|
||||
|
||||
@attr.s
|
||||
class _RetryInfo:
|
||||
|
||||
request = attr.ib()
|
||||
manager = attr.ib()
|
||||
_RetryInfo = collections.namedtuple('_RetryInfo', ['request', 'manager'])
|
||||
|
||||
|
||||
class DownloadItem(downloads.AbstractDownloadItem):
|
||||
@@ -162,7 +157,6 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
QTimer.singleShot(0, lambda: self._die(reply.errorString()))
|
||||
|
||||
def _do_cancel(self):
|
||||
self._read_timer.stop()
|
||||
if self._reply is not None:
|
||||
self._reply.finished.disconnect(self._on_reply_finished)
|
||||
self._reply.abort()
|
||||
@@ -200,26 +194,13 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
|
||||
def _ask_confirm_question(self, title, msg):
|
||||
no_action = functools.partial(self.cancel, remove_data=False)
|
||||
url = 'file://{}'.format(self._filename)
|
||||
message.confirm_async(title=title, text=msg,
|
||||
yes_action=self._after_set_filename,
|
||||
no_action=no_action, cancel_action=no_action,
|
||||
abort_on=[self.cancelled, self.error], url=url)
|
||||
|
||||
def _ask_create_parent_question(self, title, msg,
|
||||
force_overwrite, remember_directory):
|
||||
no_action = functools.partial(self.cancel, remove_data=False)
|
||||
url = 'file://{}'.format(os.path.dirname(self._filename))
|
||||
message.confirm_async(title=title, text=msg,
|
||||
yes_action=(lambda:
|
||||
self._after_create_parent_question(
|
||||
force_overwrite,
|
||||
remember_directory)),
|
||||
no_action=no_action, cancel_action=no_action,
|
||||
abort_on=[self.cancelled, self.error], url=url)
|
||||
abort_on=[self.cancelled, self.error])
|
||||
|
||||
def _set_fileobj(self, fileobj, *, autoclose=True):
|
||||
"""Set the file object to write the download to.
|
||||
""""Set the file object to write the download to.
|
||||
|
||||
Args:
|
||||
fileobj: A file-like object.
|
||||
@@ -307,14 +288,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
"""Handle QNetworkReply errors."""
|
||||
if code == QNetworkReply.OperationCanceledError:
|
||||
return
|
||||
|
||||
if self._reply is None:
|
||||
error = "Unknown error: {}".format(
|
||||
debug.qenum_key(QNetworkReply, code))
|
||||
else:
|
||||
error = self._reply.errorString()
|
||||
|
||||
self._die(error)
|
||||
self._die(self._reply.errorString())
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_read_timer_timeout(self):
|
||||
@@ -389,11 +364,11 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
_networkmanager: A NetworkManager for generic downloads.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id=None, tab_id=None,
|
||||
private=config.val.content.private_browsing, parent=self)
|
||||
win_id=win_id, tab_id=None,
|
||||
private=config.get('general', 'private-browsing'), parent=self)
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
def get(self, url, *, user_agent=None, **kwargs):
|
||||
@@ -409,7 +384,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
"""
|
||||
if not url.isValid():
|
||||
urlutils.invalid_url_error(url, "start download")
|
||||
return None
|
||||
return
|
||||
req = QNetworkRequest(url)
|
||||
if user_agent is not None:
|
||||
req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
|
||||
@@ -437,8 +412,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
mhtml.start_download_checked, tab=tab))
|
||||
message.global_bridge.ask(question, blocking=False)
|
||||
|
||||
def get_request(self, request, *, target=None,
|
||||
suggested_fn=None, **kwargs):
|
||||
def get_request(self, request, *, target=None, **kwargs):
|
||||
"""Start a download with a QNetworkRequest.
|
||||
|
||||
Args:
|
||||
@@ -454,9 +428,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
||||
QNetworkRequest.AlwaysNetwork)
|
||||
|
||||
if suggested_fn is not None:
|
||||
pass
|
||||
elif request.url().scheme().lower() != 'data':
|
||||
if request.url().scheme().lower() != 'data':
|
||||
suggested_fn = urlutils.filename_from_url(request.url())
|
||||
else:
|
||||
# We might be downloading a binary blob embedded on a page or even
|
||||
@@ -508,7 +480,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
reply: The QNetworkReply to download.
|
||||
target: Where to save the download as downloads.DownloadTarget.
|
||||
auto_remove: Whether to remove the download even if
|
||||
downloads.remove_finished is set to -1.
|
||||
ui -> remove-finished-downloads is set to -1.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,28 +24,24 @@ Module attributes:
|
||||
_HANDLERS: The handlers registered via decorators.
|
||||
"""
|
||||
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import textwrap
|
||||
import mimetypes
|
||||
import urllib
|
||||
import collections
|
||||
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import pkg_resources
|
||||
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg, urlutils)
|
||||
objreg, usertypes, qtutils)
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
pyeval_output = ":pyeval was never called"
|
||||
spawn_output = ":spawn was never called"
|
||||
|
||||
|
||||
_HANDLERS = {}
|
||||
@@ -96,7 +92,7 @@ class Redirect(Exception):
|
||||
self.url = url
|
||||
|
||||
|
||||
class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
class add_handler: # pylint: disable=invalid-name
|
||||
|
||||
"""Decorator to register a qute://* URL handler.
|
||||
|
||||
@@ -116,7 +112,6 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
return function
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
"""Call the underlying function."""
|
||||
if self._backend is not None and objects.backend != self._backend:
|
||||
return self.wrong_backend_handler(*args, **kwargs)
|
||||
else:
|
||||
@@ -124,12 +119,13 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
|
||||
def wrong_backend_handler(self, url):
|
||||
"""Show an error page about using the invalid backend."""
|
||||
src = 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', src
|
||||
html = jinja.render('error.html',
|
||||
title="Error while opening qute://url",
|
||||
url=url.toDisplayString(),
|
||||
error='{} is not available with this '
|
||||
'backend'.format(url.toDisplayString()),
|
||||
icon='')
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
def data_for_url(url):
|
||||
@@ -141,30 +137,16 @@ def data_for_url(url):
|
||||
Return:
|
||||
A (mimetype, data) tuple.
|
||||
"""
|
||||
norm_url = url.adjusted(QUrl.NormalizePathSegments |
|
||||
QUrl.StripTrailingSlash)
|
||||
if norm_url != url:
|
||||
raise Redirect(norm_url)
|
||||
|
||||
path = url.path()
|
||||
host = url.host()
|
||||
query = urlutils.query_string(url)
|
||||
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
|
||||
log.misc.debug("url: {}, path: {}, host {}".format(
|
||||
url.toDisplayString(), path, host))
|
||||
if not path or not host:
|
||||
if path and not host:
|
||||
new_url = QUrl()
|
||||
new_url.setScheme('qute')
|
||||
# When path is absent, e.g. qute://help (with no trailing slash)
|
||||
if host:
|
||||
new_url.setHost(host)
|
||||
# When host is absent, e.g. qute:help
|
||||
else:
|
||||
new_url.setHost(path)
|
||||
|
||||
new_url.setHost(path)
|
||||
new_url.setPath('/')
|
||||
if query:
|
||||
new_url.setQuery(query)
|
||||
if new_url.host(): # path was a valid host
|
||||
raise Redirect(new_url)
|
||||
|
||||
@@ -178,7 +160,7 @@ def data_for_url(url):
|
||||
except OSError as e:
|
||||
# FIXME:qtwebengine how to handle this?
|
||||
raise QuteSchemeOSError(e)
|
||||
except QuteSchemeError:
|
||||
except QuteSchemeError as e:
|
||||
raise
|
||||
|
||||
assert mimetype is not None, url
|
||||
@@ -197,65 +179,95 @@ def qute_bookmarks(_url):
|
||||
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
|
||||
key=lambda x: x[0]) # Sort by name
|
||||
|
||||
src = jinja.render('bookmarks.html',
|
||||
title='Bookmarks',
|
||||
bookmarks=bookmarks,
|
||||
quickmarks=quickmarks)
|
||||
return 'text/html', src
|
||||
html = jinja.render('bookmarks.html',
|
||||
title='Bookmarks',
|
||||
bookmarks=bookmarks,
|
||||
quickmarks=quickmarks)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('tabs')
|
||||
def qute_tabs(_url):
|
||||
"""Handler for qute://tabs. Display information about all open tabs."""
|
||||
tabs = collections.defaultdict(list)
|
||||
for win_id, window in objreg.window_registry.items():
|
||||
if sip.isdeleted(window):
|
||||
continue
|
||||
tabbed_browser = objreg.get('tabbed-browser',
|
||||
scope='window',
|
||||
window=win_id)
|
||||
for tab in tabbed_browser.widgets():
|
||||
if tab.url() not in [QUrl("qute://tabs/"), QUrl("qute://tabs")]:
|
||||
urlstr = tab.url().toDisplayString()
|
||||
tabs[str(win_id)].append((tab.title(), urlstr))
|
||||
|
||||
src = jinja.render('tabs.html',
|
||||
title='Tabs',
|
||||
tab_list_by_window=tabs)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
def history_data(start_time, offset=None):
|
||||
"""Return history data.
|
||||
def history_data(start_time): # noqa
|
||||
"""Return history data
|
||||
|
||||
Arguments:
|
||||
start_time: select history starting from this timestamp.
|
||||
offset: number of items to skip
|
||||
start_time -- select history starting from this timestamp.
|
||||
"""
|
||||
# history atimes are stored as ints, ensure start_time is not a float
|
||||
start_time = int(start_time)
|
||||
hist = objreg.get('web-history')
|
||||
if offset is not None:
|
||||
entries = hist.entries_before(start_time, limit=1000, offset=offset)
|
||||
else:
|
||||
def history_iter(start_time, reverse=False):
|
||||
"""Iterate through the history and get items we're interested.
|
||||
|
||||
Arguments:
|
||||
reverse -- whether to reverse the history_dict before iterating.
|
||||
"""
|
||||
history = objreg.get('web-history').history_dict.values()
|
||||
if reverse:
|
||||
history = reversed(history)
|
||||
|
||||
# when history_dict is not reversed, we need to keep track of last item
|
||||
# so that we can yield its atime
|
||||
last_item = None
|
||||
|
||||
# end is 24hrs earlier than start
|
||||
end_time = start_time - 24*60*60
|
||||
entries = hist.entries_between(end_time, start_time)
|
||||
|
||||
return [{"url": e.url,
|
||||
"title": html.escape(e.title) or html.escape(e.url),
|
||||
"time": e.atime} for e in entries]
|
||||
for item in history:
|
||||
# Skip redirects
|
||||
# Skip qute:// links
|
||||
if item.redirect or item.url.scheme() == 'qute':
|
||||
continue
|
||||
|
||||
# Skip items out of time window
|
||||
item_newer = item.atime > start_time
|
||||
item_older = item.atime <= end_time
|
||||
if reverse:
|
||||
# history_dict is reversed, we are going back in history.
|
||||
# so:
|
||||
# abort if item is older than start_time+24hr
|
||||
# skip if item is newer than start
|
||||
if item_older:
|
||||
yield {"next": int(item.atime)}
|
||||
return
|
||||
if item_newer:
|
||||
continue
|
||||
else:
|
||||
# history_dict isn't reversed, we are going forward in history.
|
||||
# so:
|
||||
# abort if item is newer than start_time
|
||||
# skip if item is older than start_time+24hrs
|
||||
if item_older:
|
||||
last_item = item
|
||||
continue
|
||||
if item_newer:
|
||||
yield {"next": int(last_item.atime if last_item else -1)}
|
||||
return
|
||||
|
||||
# Use item's url as title if there's no title.
|
||||
item_url = item.url.toDisplayString()
|
||||
item_title = item.title if item.title else item_url
|
||||
item_time = int(item.atime * 1000)
|
||||
|
||||
yield {"url": item_url, "title": item_title, "time": item_time}
|
||||
|
||||
# if we reached here, we had reached the end of history
|
||||
yield {"next": int(last_item.atime if last_item else -1)}
|
||||
|
||||
if sys.hexversion >= 0x03050000:
|
||||
# On Python >= 3.5 we can reverse the ordereddict in-place and thus
|
||||
# apply an additional performance improvement in history_iter.
|
||||
# On my machine, this gets us down from 550ms to 72us with 500k old
|
||||
# items.
|
||||
history = history_iter(start_time, reverse=True)
|
||||
else:
|
||||
# On Python 3.4, we can't do that, so we'd need to copy the entire
|
||||
# history to a list. There, filter first and then reverse it here.
|
||||
history = reversed(list(history_iter(start_time, reverse=False)))
|
||||
|
||||
return list(history)
|
||||
|
||||
|
||||
@add_handler('history')
|
||||
def qute_history(url):
|
||||
"""Handler for qute://history. Display and serve history."""
|
||||
if url.path() == '/data':
|
||||
try:
|
||||
offset = QUrlQuery(url).queryItemValue("offset")
|
||||
offset = int(offset) if offset else None
|
||||
except ValueError as e:
|
||||
raise QuteSchemeError("Query parameter offset is invalid", e)
|
||||
# Use start_time in query or current time.
|
||||
try:
|
||||
start_time = QUrlQuery(url).queryItemValue("start_time")
|
||||
@@ -263,13 +275,52 @@ def qute_history(url):
|
||||
except ValueError as e:
|
||||
raise QuteSchemeError("Query parameter start_time is invalid", e)
|
||||
|
||||
return 'text/html', json.dumps(history_data(start_time, offset))
|
||||
return 'text/html', json.dumps(history_data(start_time))
|
||||
else:
|
||||
return 'text/html', jinja.render(
|
||||
'history.html',
|
||||
title='History',
|
||||
gap_interval=config.val.history_gap_interval
|
||||
)
|
||||
if (
|
||||
config.get('content', 'allow-javascript') and
|
||||
(objects.backend == usertypes.Backend.QtWebEngine or
|
||||
qtutils.is_qtwebkit_ng())
|
||||
):
|
||||
return 'text/html', jinja.render(
|
||||
'history.html',
|
||||
title='History',
|
||||
session_interval=config.get('ui', 'history-session-interval')
|
||||
)
|
||||
else:
|
||||
# Get current date from query parameter, if not given choose today.
|
||||
curr_date = datetime.date.today()
|
||||
try:
|
||||
query_date = QUrlQuery(url).queryItemValue("date")
|
||||
if query_date:
|
||||
curr_date = datetime.datetime.strptime(query_date,
|
||||
"%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
log.misc.debug("Invalid date passed to qute:history: " +
|
||||
query_date)
|
||||
|
||||
one_day = datetime.timedelta(days=1)
|
||||
next_date = curr_date + one_day
|
||||
prev_date = curr_date - one_day
|
||||
|
||||
# start_time is the last second of curr_date
|
||||
start_time = time.mktime(next_date.timetuple()) - 1
|
||||
history = [
|
||||
(i["url"], i["title"],
|
||||
datetime.datetime.fromtimestamp(i["time"]/1000),
|
||||
QUrl(i["url"]).host())
|
||||
for i in history_data(start_time) if "next" not in i
|
||||
]
|
||||
|
||||
return 'text/html', jinja.render(
|
||||
'history_nojs.html',
|
||||
title='History',
|
||||
history=history,
|
||||
curr_date=curr_date,
|
||||
next_date=next_date,
|
||||
prev_date=prev_date,
|
||||
today=datetime.date.today(),
|
||||
)
|
||||
|
||||
|
||||
@add_handler('javascript')
|
||||
@@ -289,25 +340,18 @@ def qute_javascript(url):
|
||||
@add_handler('pyeval')
|
||||
def qute_pyeval(_url):
|
||||
"""Handler for qute://pyeval."""
|
||||
src = jinja.render('pre.html', title='pyeval', content=pyeval_output)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('spawn-output')
|
||||
def qute_spawn_output(_url):
|
||||
"""Handler for qute://spawn-output."""
|
||||
src = jinja.render('pre.html', title='spawn output', content=spawn_output)
|
||||
return 'text/html', src
|
||||
html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('version')
|
||||
@add_handler('verizon')
|
||||
def qute_version(_url):
|
||||
"""Handler for qute://version."""
|
||||
src = jinja.render('version.html', title='Version info',
|
||||
version=version.version(),
|
||||
copyright=qutebrowser.__copyright__)
|
||||
return 'text/html', src
|
||||
html = jinja.render('version.html', title='Version info',
|
||||
version=version.version(),
|
||||
copyright=qutebrowser.__copyright__)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('plainlog')
|
||||
@@ -321,12 +365,13 @@ def qute_plainlog(url):
|
||||
if log.ram_handler is None:
|
||||
text = "Log output was disabled."
|
||||
else:
|
||||
level = QUrlQuery(url).queryItemValue('level')
|
||||
if not level:
|
||||
try:
|
||||
level = urllib.parse.parse_qs(url.query())['level'][0]
|
||||
except KeyError:
|
||||
level = 'vdebug'
|
||||
text = log.ram_handler.dump_log(html=False, level=level)
|
||||
src = jinja.render('pre.html', title='log', content=text)
|
||||
return 'text/html', src
|
||||
html = jinja.render('pre.html', title='log', content=text)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('log')
|
||||
@@ -340,24 +385,39 @@ def qute_log(url):
|
||||
if log.ram_handler is None:
|
||||
html_log = None
|
||||
else:
|
||||
level = QUrlQuery(url).queryItemValue('level')
|
||||
if not level:
|
||||
try:
|
||||
level = urllib.parse.parse_qs(url.query())['level'][0]
|
||||
except KeyError:
|
||||
level = 'vdebug'
|
||||
html_log = log.ram_handler.dump_log(html=True, level=level)
|
||||
|
||||
src = jinja.render('log.html', title='log', content=html_log)
|
||||
return 'text/html', src
|
||||
html = jinja.render('log.html', title='log', content=html_log)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('gpl')
|
||||
def qute_gpl(_url):
|
||||
"""Handler for qute://gpl. Return HTML content as string."""
|
||||
return 'text/html', utils.read_file('html/license.html')
|
||||
return 'text/html', utils.read_file('html/COPYING.html')
|
||||
|
||||
|
||||
@add_handler('help')
|
||||
def qute_help(url):
|
||||
"""Handler for qute://help."""
|
||||
try:
|
||||
utils.read_file('html/doc/index.html')
|
||||
except OSError:
|
||||
html = jinja.render(
|
||||
'error.html',
|
||||
title="Error while loading documentation",
|
||||
url=url.toDisplayString(),
|
||||
error="This most likely means the documentation was not generated "
|
||||
"properly. If you are running qutebrowser from the git "
|
||||
"repository, please run scripts/asciidoc2html.py. "
|
||||
"If you're running a released version this is a bug, please "
|
||||
"use :report to report it.",
|
||||
icon='')
|
||||
return 'text/html', html
|
||||
urlpath = url.path()
|
||||
if not urlpath or urlpath == '/':
|
||||
urlpath = 'index.html'
|
||||
@@ -366,143 +426,20 @@ def qute_help(url):
|
||||
if not docutils.docs_up_to_date(urlpath):
|
||||
message.error("Your documentation is outdated! Please re-run "
|
||||
"scripts/asciidoc2html.py.")
|
||||
|
||||
path = 'html/doc/{}'.format(urlpath)
|
||||
if not urlpath.endswith('.html'):
|
||||
try:
|
||||
bdata = utils.read_file(path, binary=True)
|
||||
except OSError as e:
|
||||
raise QuteSchemeOSError(e)
|
||||
mimetype, _encoding = mimetypes.guess_type(urlpath)
|
||||
assert mimetype is not None, url
|
||||
return mimetype, bdata
|
||||
|
||||
try:
|
||||
data = utils.read_file(path)
|
||||
except OSError:
|
||||
# No .html around, let's see if we find the asciidoc
|
||||
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:
|
||||
raise
|
||||
|
||||
preamble = textwrap.dedent("""
|
||||
There was an error loading the documentation!
|
||||
|
||||
This most likely means the documentation was not generated
|
||||
properly. If you are running qutebrowser from the git repository,
|
||||
please (re)run scripts/asciidoc2html.py and reload this page.
|
||||
|
||||
If you're running a released version this is a bug, please use
|
||||
:report to report it.
|
||||
|
||||
Falling back to the plaintext version.
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
|
||||
""")
|
||||
return 'text/plain', (preamble + asciidoc).encode('utf-8')
|
||||
if urlpath.endswith('.png'):
|
||||
return 'image/png', utils.read_file(path, binary=True)
|
||||
else:
|
||||
data = utils.read_file(path)
|
||||
return 'text/html', data
|
||||
|
||||
|
||||
@add_handler('backend-warning')
|
||||
def qute_backend_warning(_url):
|
||||
"""Handler for qute://backend-warning."""
|
||||
src = jinja.render('backend-warning.html',
|
||||
distribution=version.distribution(),
|
||||
Distribution=version.Distribution,
|
||||
version=pkg_resources.parse_version,
|
||||
title="Legacy backend warning")
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
def _qute_settings_set(url):
|
||||
"""Handler for qute://settings/set."""
|
||||
query = QUrlQuery(url)
|
||||
option = query.queryItemValue('option', QUrl.FullyDecoded)
|
||||
value = query.queryItemValue('value', QUrl.FullyDecoded)
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/727
|
||||
if option == 'content.javascript.enabled' and value == 'false':
|
||||
msg = ("Refusing to disable javascript via qute://settings "
|
||||
"as it needs javascript support.")
|
||||
message.error(msg)
|
||||
return 'text/html', b'error: ' + msg.encode('utf-8')
|
||||
|
||||
try:
|
||||
config.instance.set_str(option, value, save_yaml=True)
|
||||
return 'text/html', b'ok'
|
||||
except configexc.Error as e:
|
||||
message.error(str(e))
|
||||
return 'text/html', b'error: ' + str(e).encode('utf-8')
|
||||
|
||||
|
||||
@add_handler('settings')
|
||||
def qute_settings(url):
|
||||
"""Handler for qute://settings. View/change qute configuration."""
|
||||
if url.path() == '/set':
|
||||
return _qute_settings_set(url)
|
||||
|
||||
src = jinja.render('settings.html', title='settings',
|
||||
configdata=configdata,
|
||||
confget=config.instance.get_str)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('bindings')
|
||||
def qute_bindings(_url):
|
||||
"""Handler for qute://bindings. View keybindings."""
|
||||
bindings = {}
|
||||
defaults = config.val.bindings.default
|
||||
modes = set(defaults.keys()).union(config.val.bindings.commands)
|
||||
modes.remove('normal')
|
||||
modes = ['normal'] + sorted(list(modes))
|
||||
for mode in modes:
|
||||
bindings[mode] = config.key_instance.get_bindings_for(mode)
|
||||
|
||||
src = jinja.render('bindings.html', title='Bindings',
|
||||
bindings=bindings)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('back')
|
||||
def qute_back(url):
|
||||
"""Handler for qute://back.
|
||||
|
||||
Simple page to free ram / lazy load a site, goes back on focusing the tab.
|
||||
"""
|
||||
src = jinja.render(
|
||||
'back.html',
|
||||
title='Suspended: ' + urllib.parse.unquote(url.fragment()))
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('configdiff')
|
||||
def qute_configdiff(url):
|
||||
"""Handler for qute://configdiff."""
|
||||
if url.path() == '/old':
|
||||
try:
|
||||
return 'text/html', configdiff.get_diff()
|
||||
except OSError as e:
|
||||
error = (b'Failed to read old config: ' +
|
||||
str(e.strerror).encode('utf-8'))
|
||||
return 'text/plain', error
|
||||
else:
|
||||
data = config.instance.dump_userconfig().encode('utf-8')
|
||||
return 'text/plain', data
|
||||
|
||||
|
||||
@add_handler('pastebin-version')
|
||||
def qute_pastebin_version(_url):
|
||||
"""Handler that pastebins the version string."""
|
||||
version.pastebin_version()
|
||||
return 'text/plain', b'Paste called.'
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -19,37 +19,32 @@
|
||||
|
||||
"""Various utilities shared between webpage/webview subclasses."""
|
||||
|
||||
import os
|
||||
import html
|
||||
import netrc
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
import jinja2
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.utils import usertypes, message, log, objreg
|
||||
|
||||
|
||||
class CallSuper(Exception):
|
||||
|
||||
"""Raised when the caller should call the superclass instead."""
|
||||
|
||||
|
||||
def custom_headers(url):
|
||||
def custom_headers():
|
||||
"""Get the combined custom headers."""
|
||||
headers = {}
|
||||
dnt = b'1' if config.get('network', 'do-not-track') else b'0'
|
||||
headers[b'DNT'] = dnt
|
||||
headers[b'X-Do-Not-Track'] = dnt
|
||||
|
||||
dnt_config = config.instance.get('content.headers.do_not_track', url=url)
|
||||
if dnt_config is not None:
|
||||
dnt = b'1' if dnt_config else b'0'
|
||||
headers[b'DNT'] = dnt
|
||||
headers[b'X-Do-Not-Track'] = dnt
|
||||
config_headers = config.get('network', 'custom-headers')
|
||||
if config_headers is not None:
|
||||
for header, value in config_headers.items():
|
||||
headers[header.encode('ascii')] = value.encode('ascii')
|
||||
|
||||
conf_headers = config.instance.get('content.headers.custom', url=url)
|
||||
for header, value in conf_headers.items():
|
||||
headers[header.encode('ascii')] = value.encode('ascii')
|
||||
|
||||
accept_language = config.instance.get('content.headers.accept_language',
|
||||
url=url)
|
||||
accept_language = config.get('network', 'accept-language')
|
||||
if accept_language is not None:
|
||||
headers[b'Accept-Language'] = accept_language.encode('ascii')
|
||||
|
||||
@@ -65,48 +60,43 @@ def authentication_required(url, authenticator, abort_on):
|
||||
else:
|
||||
msg = '<b>{}</b> needs authentication'.format(
|
||||
html.escape(url.toDisplayString()))
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
answer = message.ask(title="Authentication required", text=msg,
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
abort_on=abort_on)
|
||||
if answer is not None:
|
||||
authenticator.setUser(answer.user)
|
||||
authenticator.setPassword(answer.password)
|
||||
return answer
|
||||
|
||||
|
||||
def javascript_confirm(url, js_msg, abort_on, *, escape_msg=True):
|
||||
def javascript_confirm(url, js_msg, abort_on):
|
||||
"""Display a javascript confirm prompt."""
|
||||
log.js.debug("confirm: {}".format(js_msg))
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
if config.get('ui', 'modal-js-dialog'):
|
||||
raise CallSuper
|
||||
|
||||
js_msg = html.escape(js_msg) if escape_msg else js_msg
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
js_msg)
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
html.escape(js_msg))
|
||||
ans = message.ask('Javascript confirm', msg,
|
||||
mode=usertypes.PromptMode.yesno,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
abort_on=abort_on)
|
||||
return bool(ans)
|
||||
|
||||
|
||||
def javascript_prompt(url, js_msg, default, abort_on, *, escape_msg=True):
|
||||
def javascript_prompt(url, js_msg, default, abort_on):
|
||||
"""Display a javascript prompt."""
|
||||
log.js.debug("prompt: {}".format(js_msg))
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
if config.get('ui', 'modal-js-dialog'):
|
||||
raise CallSuper
|
||||
if not config.val.content.javascript.prompt:
|
||||
if config.get('content', 'ignore-javascript-prompt'):
|
||||
return (False, "")
|
||||
|
||||
js_msg = html.escape(js_msg) if escape_msg else js_msg
|
||||
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
js_msg)
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
html.escape(js_msg))
|
||||
answer = message.ask('Javascript prompt', msg,
|
||||
mode=usertypes.PromptMode.text,
|
||||
default=default,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
abort_on=abort_on)
|
||||
|
||||
if answer is None:
|
||||
return (False, "")
|
||||
@@ -114,37 +104,19 @@ def javascript_prompt(url, js_msg, default, abort_on, *, escape_msg=True):
|
||||
return (True, answer)
|
||||
|
||||
|
||||
def javascript_alert(url, js_msg, abort_on, *, escape_msg=True):
|
||||
def javascript_alert(url, js_msg, abort_on):
|
||||
"""Display a javascript alert."""
|
||||
log.js.debug("alert: {}".format(js_msg))
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
if config.get('ui', 'modal-js-dialog'):
|
||||
raise CallSuper
|
||||
|
||||
if not config.val.content.javascript.alert:
|
||||
if config.get('content', 'ignore-javascript-alert'):
|
||||
return
|
||||
|
||||
js_msg = html.escape(js_msg) if escape_msg else js_msg
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
js_msg)
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
html.escape(js_msg))
|
||||
message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
|
||||
|
||||
def javascript_log_message(level, source, line, msg):
|
||||
"""Display a JavaScript log message."""
|
||||
logstring = "[{}:{}] {}".format(source, line, msg)
|
||||
# Needs to line up with the values allowed for the
|
||||
# content.javascript.log setting.
|
||||
logmap = {
|
||||
'none': lambda arg: None,
|
||||
'debug': log.js.debug,
|
||||
'info': log.js.info,
|
||||
'warning': log.js.warning,
|
||||
'error': log.js.error,
|
||||
}
|
||||
logger = logmap[config.val.content.javascript.log[level.name]]
|
||||
logger(logstring)
|
||||
abort_on=abort_on)
|
||||
|
||||
|
||||
def ignore_certificate_errors(url, errors, abort_on):
|
||||
@@ -157,7 +129,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
Return:
|
||||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
ssl_strict = config.instance.get('content.ssl_strict', url=url)
|
||||
ssl_strict = config.get('network', 'ssl-strict')
|
||||
log.webview.debug("Certificate errors {!r}, strict {}".format(
|
||||
errors, ssl_strict))
|
||||
|
||||
@@ -165,7 +137,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
assert error.is_overridable(), repr(error)
|
||||
|
||||
if ssl_strict == 'ask':
|
||||
err_template = jinja.environment.from_string("""
|
||||
err_template = jinja2.Template("""
|
||||
Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
|
||||
<ul>
|
||||
{% for err in errors %}
|
||||
@@ -175,16 +147,15 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
""".strip())
|
||||
msg = err_template.render(url=url, errors=errors)
|
||||
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
ignore = message.ask(title="Certificate errors - continue?", text=msg,
|
||||
mode=usertypes.PromptMode.yesno, default=False,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
abort_on=abort_on)
|
||||
if ignore is None:
|
||||
# prompt aborted
|
||||
ignore = False
|
||||
return ignore
|
||||
elif ssl_strict is False:
|
||||
log.webview.debug("ssl_strict is False, only warning about errors")
|
||||
log.webview.debug("ssl-strict is False, only warning about errors")
|
||||
for err in errors:
|
||||
# FIXME we might want to use warn here (non-fatal error)
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/114
|
||||
@@ -194,50 +165,35 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
return False
|
||||
else:
|
||||
raise ValueError("Invalid ssl_strict value {!r}".format(ssl_strict))
|
||||
raise utils.Unreachable
|
||||
raise AssertionError("Not reached")
|
||||
|
||||
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
||||
blocking=False):
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
"""Handle a feature permission request.
|
||||
|
||||
Args:
|
||||
url: The URL the request was done for.
|
||||
option: An option name to check.
|
||||
option: A (section, option) tuple for the option to check.
|
||||
msg: A string like "show notifications"
|
||||
yes_action: A callable to call if the request was approved
|
||||
no_action: A callable to call if the request was denied
|
||||
abort_on: A list of signals which interrupt the question.
|
||||
blocking: If True, ask a blocking question.
|
||||
|
||||
Return:
|
||||
The Question object if a question was asked (and blocking=False),
|
||||
None otherwise.
|
||||
The Question object if a question was asked, None otherwise.
|
||||
"""
|
||||
config_val = config.instance.get(option, url=url)
|
||||
config_val = config.get(*option)
|
||||
if config_val == 'ask':
|
||||
if url.isValid():
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
text = "Allow the website at <b>{}</b> to {}?".format(
|
||||
html.escape(url.toDisplayString()), msg)
|
||||
else:
|
||||
urlstr = None
|
||||
text = "Allow the website to {}?".format(msg)
|
||||
|
||||
if blocking:
|
||||
answer = message.ask(abort_on=abort_on, title='Permission request',
|
||||
text=text, url=urlstr,
|
||||
mode=usertypes.PromptMode.yesno)
|
||||
if answer:
|
||||
yes_action()
|
||||
else:
|
||||
no_action()
|
||||
return None
|
||||
else:
|
||||
return message.confirm_async(
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
cancel_action=no_action, abort_on=abort_on,
|
||||
title='Permission request', text=text, url=urlstr)
|
||||
return message.confirm_async(
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
cancel_action=no_action, abort_on=abort_on,
|
||||
title='Permission request', text=text)
|
||||
elif config_val:
|
||||
yes_action()
|
||||
return None
|
||||
@@ -262,6 +218,7 @@ def get_tab(win_id, target):
|
||||
elif target == usertypes.ClickTarget.window:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
window = mainwindow.MainWindow(private=tabbed_browser.private)
|
||||
window.show()
|
||||
win_id = window.win_id
|
||||
@@ -276,52 +233,15 @@ def get_tab(win_id, target):
|
||||
|
||||
def get_user_stylesheet():
|
||||
"""Get the combined user-stylesheet."""
|
||||
css = ''
|
||||
stylesheets = config.val.content.user_stylesheets
|
||||
filename = config.get('ui', 'user-stylesheet')
|
||||
|
||||
for filename in stylesheets:
|
||||
if filename is None:
|
||||
css = ''
|
||||
else:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
css += f.read()
|
||||
css = f.read()
|
||||
|
||||
if not config.val.scrolling.bar:
|
||||
if config.get('ui', 'hide-scrollbar'):
|
||||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||
|
||||
return css
|
||||
|
||||
|
||||
def netrc_authentication(url, authenticator):
|
||||
"""Perform authorization using netrc.
|
||||
|
||||
Args:
|
||||
url: The URL the request was done for.
|
||||
authenticator: QAuthenticator object used to set credentials provided.
|
||||
|
||||
Return:
|
||||
True if netrc found credentials for the URL.
|
||||
False otherwise.
|
||||
"""
|
||||
if 'HOME' not in os.environ:
|
||||
# We'll get an OSError by netrc if 'HOME' isn't available in
|
||||
# os.environ. We don't want to log that, so we prevent it
|
||||
# altogether.
|
||||
return False
|
||||
user, password = None, None
|
||||
try:
|
||||
net = netrc.netrc(config.val.content.netrc_file)
|
||||
authenticators = net.authenticators(url.host())
|
||||
if authenticators is not None:
|
||||
(user, _account, password) = authenticators
|
||||
except FileNotFoundError:
|
||||
log.misc.debug("No .netrc file found")
|
||||
except OSError as e:
|
||||
log.misc.exception("Unable to read the netrc file: {}".format(e))
|
||||
except netrc.NetrcParseError as e:
|
||||
log.misc.exception("Error when parsing the netrc file: {}".format(e))
|
||||
|
||||
if user is None:
|
||||
return False
|
||||
|
||||
authenticator.setUser(user)
|
||||
authenticator.setPassword(password)
|
||||
|
||||
return True
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -76,11 +76,11 @@ class SignalFilter(QObject):
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
try:
|
||||
tabidx = tabbed_browser.widget.indexOf(tab)
|
||||
tabidx = tabbed_browser.indexOf(tab)
|
||||
except RuntimeError:
|
||||
# The tab has been deleted already
|
||||
return
|
||||
if tabidx == tabbed_browser.widget.currentIndex():
|
||||
if tabidx == tabbed_browser.currentIndex():
|
||||
if log_signal:
|
||||
log.signals.debug("emitting: {} (tab {})".format(
|
||||
debug.dbg_signal(signal, args), tabidx))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2018 Antoni Boucher <bouanto@zoho.com>
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2017 Antoni Boucher <bouanto@zoho.com>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -26,8 +26,8 @@ to a file on shutdown, so it makes sense to keep them as strings here.
|
||||
"""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import html
|
||||
import os.path
|
||||
import functools
|
||||
import collections
|
||||
|
||||
@@ -77,9 +77,13 @@ class UrlMarkManager(QObject):
|
||||
|
||||
Signals:
|
||||
changed: Emitted when anything changed.
|
||||
added: Emitted when a new quickmark/bookmark was added.
|
||||
removed: Emitted when an existing quickmark/bookmark was removed.
|
||||
"""
|
||||
|
||||
changed = pyqtSignal()
|
||||
added = pyqtSignal(str, str)
|
||||
removed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Initialize and read quickmarks."""
|
||||
@@ -117,6 +121,7 @@ class UrlMarkManager(QObject):
|
||||
"""
|
||||
del self.marks[key]
|
||||
self.changed.emit()
|
||||
self.removed.emit(key)
|
||||
|
||||
|
||||
class QuickmarkManager(UrlMarkManager):
|
||||
@@ -128,6 +133,7 @@ class QuickmarkManager(UrlMarkManager):
|
||||
- self.marks maps names to URLs.
|
||||
- changed gets emitted with the name as first argument and the URL as
|
||||
second argument.
|
||||
- removed gets emitted with the name as argument.
|
||||
"""
|
||||
|
||||
def _init_lineparser(self):
|
||||
@@ -161,7 +167,7 @@ class QuickmarkManager(UrlMarkManager):
|
||||
"Add quickmark:", usertypes.PromptMode.text,
|
||||
functools.partial(self.quickmark_add, urlstr),
|
||||
text="Please enter a quickmark name for<br/><b>{}</b>".format(
|
||||
html.escape(url.toDisplayString())), url=urlstr)
|
||||
html.escape(url.toDisplayString())))
|
||||
|
||||
@cmdutils.register(instance='quickmark-manager')
|
||||
def quickmark_add(self, url, name):
|
||||
@@ -187,12 +193,13 @@ class QuickmarkManager(UrlMarkManager):
|
||||
"""Really set the quickmark."""
|
||||
self.marks[name] = url
|
||||
self.changed.emit()
|
||||
self.added.emit(name, url)
|
||||
log.misc.debug("Added quickmark {} for {}".format(name, url))
|
||||
|
||||
if name in self.marks:
|
||||
message.confirm_async(
|
||||
title="Override existing quickmark?",
|
||||
yes_action=set_mark, default=True, url=url)
|
||||
yes_action=set_mark, default=True)
|
||||
else:
|
||||
set_mark()
|
||||
|
||||
@@ -236,6 +243,7 @@ class BookmarkManager(UrlMarkManager):
|
||||
- self.marks maps URLs to titles.
|
||||
- changed gets emitted with the URL as first argument and the title as
|
||||
second argument.
|
||||
- removed gets emitted with the URL as argument.
|
||||
"""
|
||||
|
||||
def _init_lineparser(self):
|
||||
@@ -280,11 +288,12 @@ class BookmarkManager(UrlMarkManager):
|
||||
|
||||
if urlstr in self.marks:
|
||||
if toggle:
|
||||
self.delete(urlstr)
|
||||
del self.marks[urlstr]
|
||||
return False
|
||||
else:
|
||||
raise AlreadyExistsError("Bookmark already exists!")
|
||||
else:
|
||||
self.marks[urlstr] = title
|
||||
self.changed.emit()
|
||||
self.added.emit(title, urlstr)
|
||||
return True
|
||||
|
||||