Compare commits

..

6 Commits

Author SHA1 Message Date
Florian Bruhin
55a88ceea6 Release v1.0.2 2017-10-17 07:44:14 +02:00
Florian Bruhin
d4bf04d2c8 Bump up yaml timeout a bit
(cherry picked from commit 1a7612e559)
2017-10-17 06:29:00 +02:00
Florian Bruhin
cb527913dc Show better error message when trying to toggle with :set
(cherry picked from commit d8384ced0a)
2017-10-17 06:27:07 +02:00
Florian Bruhin
ddfa82345c Fix HTML escaping in completion
(cherry picked from commit e766fe14fc)
2017-10-16 12:27:45 +02:00
Florian Bruhin
45c75d5e04 Fix setting monospace fonts with None values
Fixes #3130

(cherry picked from commit 2a65cadb67)
2017-10-16 06:18:29 +02:00
Florian Bruhin
9404c61f10 Add SQLITE_READONLY to environmental errors
(cherry picked from commit fa4a66f7b3)
2017-10-15 21:10:31 +02:00
512 changed files with 8205 additions and 23693 deletions

View File

@@ -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-pyqt59
- 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%'

View File

@@ -12,7 +12,6 @@ exclude_lines =
def __repr__
raise AssertionError
raise NotImplementedError
raise utils\.Unreachable
if __name__ == ["']__main__["']:
[xml]

30
.flake8
View File

@@ -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 '#'
@@ -22,33 +19,32 @@ 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/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
# FIXME:conf
tests/unit/completion/test_models.py : +F821
copyright-check = True
copyright-regexp = # Copyright [\d-]+ .*
copyright-min-file-size = 110

4
.github/CODEOWNERS vendored
View File

@@ -6,7 +6,3 @@ 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

View File

@@ -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/

View File

@@ -1,17 +1,9 @@
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:
See the full contribution docs for details:
include::../doc/contributing.asciidoc[]

19
.gitignore vendored
View File

@@ -25,20 +25,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

View File

@@ -13,33 +13,34 @@ 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,
# https://github.com/PyCQA/pylint/issues/1698
unsupported-membership-test,
unsupported-assignment-operation,
unsubscriptable-object
[BASIC]
function-rgx=[a-z_][a-z0-9_]{2,50}$
@@ -68,10 +69,10 @@ valid-metaclass-classmethod-first-arg=cls
[TYPECHECK]
ignored-modules=PyQt5,PyQt5.QtWebKit
ignored-classes=_CountingAttr
[IMPORTS]
# WORKAROUND
# 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

View File

@@ -14,24 +14,16 @@ matrix:
services: docker
- os: linux
env: TESTENV=py36-pyqt571
- os: linux
env: TESTENV=py36-pyqt58
- os: linux
python: 3.5
env: TESTENV=py35-pyqt571
env: TESTENV=py35-pyqt59
- os: linux
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
env: TESTENV=py36-pyqt59-cov
- os: osx
env: TESTENV=py37 OSX=sierra
osx_image: xcode9.2
env: TESTENV=py36 OSX=sierra
osx_image: xcode8.3
language: generic
# https://github.com/qutebrowser/qutebrowser/issues/2013
# - os: osx
@@ -59,16 +51,8 @@ matrix:
env: TESTENV=eslint
language: node_js
python: null
node_js: "lts/*"
- os: linux
language: generic
env: TESTENV=shellcheck
services: docker
node_js: node
fast_finish: true
allow_failures:
# https://github.com/qutebrowser/qutebrowser/issues/4055
- os: linux
env: TESTENV=py36-pyqt510
cache:
directories:

View File

@@ -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,14 +8,11 @@ graft icons
graft doc/img
graft misc/apparmor
graft misc/userscripts
graft misc/requirements
recursive-include scripts *.py *.sh *.js
recursive-include scripts *.py *.sh
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 requirements.txt
include tox.ini
include qutebrowser.py
@@ -23,21 +21,25 @@ 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 misc/appveyor_install.py
exclude misc/qutebrowser.spec
exclude misc/qutebrowser.nsi
exclude misc/qutebrowser.rcc
global-exclude __pycache__ *.pyc *.pyo

View File

@@ -9,11 +9,13 @@ 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/LICENSE"]
image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"]
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
@@ -42,8 +44,8 @@ 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"]
* 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]
@@ -89,7 +91,7 @@ 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
------------
@@ -97,7 +99,7 @@ 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:
* http://qt.io/[Qt] 5.7.1 or newer with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase in some distributions)
- QtSQL (part of qtbase in some distributions)
@@ -107,12 +109,12 @@ The following software and libraries are required to run qutebrowser:
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
(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://pyyaml.org/wiki/PyYAML[PyYAML]
* http://www.attrs.org/[attrs]
The following libraries are optional:
@@ -180,9 +182,7 @@ Active
* 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
https://github.com/cmcaine/tridactyl[Tridactyl] (in early development, working
on a https://bugzilla.mozilla.org/show_bug.cgi?id=1215061[better API] for
keyboard integration in Firefox).
@@ -200,6 +200,7 @@ main inspiration for qutebrowser)
http://www.vimperator.org/[Vimperator],
http://5digits.org/pentadactyl/[Pentadactyl],
https://github.com/akhodakivskiy/VimFx[VimFx],
https://github.com/shinglyu/QuantumVim[QuantumVim]
* Chrome/Chromium addons:
https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome],
https://github.com/jinzhu/vrome[Vrome]

View File

@@ -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

View File

@@ -15,638 +15,10 @@ 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
Fixes
~~~~~
- Fix workaround for black screens or crashes with Nvidia cards
@@ -664,7 +36,7 @@ Changed
v1.0.1
------
Fixed
Fixes
~~~~~
- Fixed starting after customizing `fonts.tabs` or `fonts.debug_console`.
@@ -702,9 +74,6 @@ Major changes
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
~~~~~
@@ -1544,7 +913,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 +1113,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.

View File

@@ -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 `&lt;3` footnote:[Of course, that says `<3` in HTML.] contributors!
This document contains guidelines for contributing to qutebrowser, as well as
@@ -49,8 +44,8 @@ be easy to solve]
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 +85,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
----------------
@@ -115,10 +100,16 @@ Currently, the following tox environments are available:
- `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].
* `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].
* `eslint`: Run http://eslint.org/[ESLint] javascript checker.
@@ -203,8 +194,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
@@ -390,7 +381,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]]
@@ -479,6 +470,7 @@ 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.
- `hide=True`: Hide the argument from the documentation.
- `completion`: A completion function (see `qutebrowser.completions.models.*`)
to use when completing arguments for the given command.
- `choices`: The allowed string choices for the argument.
@@ -581,23 +573,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
-----------------
@@ -694,17 +669,19 @@ New PyQt release
~~~~~~~~~~~~~~~~
* 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.
* Update changelog (remove *(unreleased)*).
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
* Update changelog (remove *(unreleased)*).
* Run tests again.
* Commit.
* Create annotated git tag (`git tag -s "v1.$x.$y" -m "Release v1.$x.$y"`).
@@ -714,11 +691,9 @@ qutebrowser release
* 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`
* Linux: Run `python3 scripts/dev/build_release.py --upload v1.$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 v1.X.Y` (replace X/Y by hand).
* macOS: Run `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).
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed.
* Announce to qutebrowser and qutebrowser-announce mailinglist.

View File

@@ -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
@@ -175,13 +146,13 @@ For QtWebKit:
For QtWebEngine:
. Make sure your versions of PyQt and Qt are 5.8 or higher.
. Use `dictcli.py` script to install dictionaries.
. Use `install_dict.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']"`
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 +162,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
@@ -212,40 +183,13 @@ Why takes it longer to open an URL in qutebrowser than in chromium?::
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`
@@ -272,33 +216,10 @@ And then re-emerging qtwebengine with: +
emerge -1 qtwebengine
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.
+
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
----
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.

File diff suppressed because it is too large Load Diff

View File

@@ -3,28 +3,34 @@ 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.
information elsewhere might be outdated. **If you had an old configuration
around and upgraded, this page will automatically open once**. To view it at a
later time, use the `:help` command.
qutebrowser's config files
--------------------------
Migrating older configurations
------------------------------
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.
qutebrowser does no automatic migration for the new configuration. However,
there's a special link:qute://configdiff/old[configdiff] page in qutebrowser,
which will show you the changes you did in your old configuration, compared to
the old defaults.
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.
Other changes in default settings:
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.
- `<Up>` and `<Down>` in the completion now navigate through command history
instead of selecting completion items. You can get back the old behavior by
doing:
+
----
:bind -f -m command <Up> completion-item-focus prev
:bind -f -m command <Down> completion-item-focus 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.
[[autoconfig]]
Configuring qutebrowser via the user interface
----------------------------------------------
@@ -47,10 +53,6 @@ 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
@@ -72,7 +74,6 @@ link:commands.html#config-clear[`:config-clear`] to reset the entire configurati
and link:commands.html#config-cycle[`:config-cycle`] to cycle a setting between
different values.
[[configpy]]
Configuring qutebrowser via config.py
-------------------------------------
@@ -136,6 +137,7 @@ prefix to preserve backslashes) or a Python regex object:
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -159,26 +161,6 @@ To read a setting, use the `config.get` method:
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
~~~~~~~~~~~~
@@ -224,14 +206,13 @@ 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:
By default, all customization done via `:set`, `:bind` and `:unbind` is
temporary as soon as a `config.py` exists. The settings done that way are always
saved in the `autoconfig.yml` file, but you'll need to explicitly load it in
your `config.py` by doing:
.config.py:
[source,python]
@@ -256,14 +237,11 @@ 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.configdir`. Similarily, you can get the qutebrowser data directory via
`config.datadir`.
This gives you a https://docs.python.org/3/library/pathlib.html[`pathlib.Path`
@@ -273,7 +251,7 @@ get a string:
.config.py:
[source,python]
----
print(str(config.configdir / 'config.py'))
print(str(config.configdir / 'config.py')
----
Handling errors
@@ -368,46 +346,15 @@ def bind_chained(key, *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:
If you use an editor with flake8 integration which complains about `c` and `config` being undefined, you can use:
[source,python]
----
# pylint: disable=C0111
c = c # noqa: F821 pylint: disable=E0602,C0103
config = config # noqa: F821 pylint: disable=E0602,C0103
c = c # noqa: F821
config = config # noqa: F821
----
For type annotation support (note that those imports aren't guaranteed to be
@@ -415,44 +362,8 @@ 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
config = config # type: ConfigAPI # noqa: F821
c = c # type: ConfigContainer # noqa: F821
----
[[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.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 989 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -21,7 +21,7 @@ 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://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or via_ipca
https://github.com/pyenv/pyenv[pyenv], but nobody tried that yet.
If you get qutebrowser running on those distributions, please
@@ -35,51 +35,30 @@ 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
Debian Stretch / Ubuntu 17.04 and newer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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):
Install the dependencies via apt-get:
----
# apt install ./python3-pypeg2_*_all.deb
# apt install ./qutebrowser*.deb
# apt install python-tox python3-{lxml,pyqt5,sip,jinja2,pygments,yaml,attr} python3-pyqt5.qt{webengine,quick,opengl,sql} libqt5sql5-sqlite
----
For an update after the initial install, you only need to download/install the
qutebrowser package.
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].
Debian Testing / Ubuntu 18.04
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On Debian Testing, qutebrowser is in the official repositories, and you can
install it with apt:
Install the packages:
----
# apt install qutebrowser
# dpkg -i python3-pypeg2_*_all.deb
# dpkg -i qutebrowser_*_all.deb
----
Additional hints
~~~~~~~~~~~~~~~~
Some additional hints:
- Alternatively, you can <<tox,install qutebrowser via tox>> to get a newer
QtWebEngine version.
@@ -87,44 +66,31 @@ Additional hints
`:help` command:
+
----
# apt install --no-install-recommends asciidoc source-highlight
# apt-get 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].
Debian experimental, or from http://repo.paretje.be/unstable/[this repository]
for Debian Stretch.
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
+
----
# apt install gstreamer1.0-plugins-{bad,base,good,ugly}
# apt-get 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 for Fedora 22 and newer.
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
-----
It's also recommended to install `python3-qt5-webengine` and start with `--backend
webengine` to use the new backend. v1.0.0 (which is not in the Fedora repos
currently) uses QtWebEngine by default.
On Archlinux
------------
@@ -159,16 +125,14 @@ If video or sound don't work with QtWebKit, try installing the gstreamer plugins
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.
The Gentoo packages (even the live version) are lagging behind a lot and are
effectively unmaintained. If you want to create and maintain an official
qutebrowser overlay for Gentoo, please mailto:mail@qutebrowser.org[get in
touch.]
qutebrowser is available in the main repository and can be installed with:
It's recommended to <<tox,install qutebrowser via tox>> instead.
----
# emerge -av qutebrowser
----
To use QtWebKit instead of QtWebEngine, you'll need a newer QtWebKit using
To get an up-to-date QtWebKit, you can use
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild].
If video or sound don't work with QtWebKit, try installing the gstreamer
@@ -225,10 +189,6 @@ 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:
@@ -244,21 +204,6 @@ Or alternatively, use the ports system :
# 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
----------
@@ -276,10 +221,6 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrows
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]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -291,11 +232,6 @@ PS C:\> Install-Package qutebrowser
----
C:\> choco install qutebrowser
----
* Scoop's client
----
C:\> scoop bucket add extras
C:\> scoop install qutebrowser
----
Manual install
~~~~~~~~~~~~~~
@@ -325,10 +261,6 @@ 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:
@@ -382,8 +314,8 @@ $ git clone https://github.com/qutebrowser/qutebrowser.git
$ cd qutebrowser
----
Installing dependencies (including Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Installing depdendencies (including Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Then run tox inside the qutebrowser repository to set up a
https://docs.python.org/3/library/venv.html[virtual environment]:
@@ -392,10 +324,6 @@ 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
@@ -424,39 +352,20 @@ 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.
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
Python3 is in your PATH before running tox.
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`):
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 "$@"
----
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
~~~~~~~~

View File

@@ -22,9 +22,9 @@ 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.

View File

@@ -38,7 +38,7 @@ show it.
*-h*, *--help*::
show this help message and exit
*-B* 'BASEDIR', *--basedir* 'BASEDIR'::
*--basedir* 'BASEDIR'::
Base directory for all storage.
*-V*, *--version*::
@@ -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*::
@@ -87,7 +87,7 @@ show it.
*--nowindow*::
Don't show the main window.
*-T*, *--temp-basedir*::
*--temp-basedir*::
Use a temporary basedir.
*--no-err-windows*::
@@ -99,7 +99,7 @@ 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

View File

@@ -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:

View File

@@ -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:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -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."
};

View File

@@ -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/*))

View File

@@ -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">&lt;Ctrl-P&gt; - 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">&lt;Ctrl-N&gt; - 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">&lt;Ctrl-D&gt; - delete current item</flowPara></flowRoot> <rect
id="flowPara3935-9">&lt;Ctrl-N&gt; - 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

View File

@@ -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>

View File

@@ -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

View File

@@ -15,7 +15,7 @@ def get_data_files():
('../qutebrowser/img', 'img'),
('../qutebrowser/javascript', 'javascript'),
('../qutebrowser/html/doc', 'html/doc'),
('../qutebrowser/git-commit-id', '.'),
('../qutebrowser/git-commit-id', ''),
('../qutebrowser/config/configdata.yml', 'config'),
]
@@ -58,15 +58,14 @@ 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,

View File

@@ -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.

View File

@@ -1,3 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
check-manifest==0.37
check-manifest==0.35

View File

@@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
certifi==2018.4.16
certifi==2017.7.27.1
chardet==3.0.4
codecov==2.0.15
coverage==4.5.1
idna==2.7
requests==2.19.1
codecov==2.0.9
coverage==4.4.1
idna==2.6
requests==2.18.4
urllib3==1.22

View File

@@ -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.1
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-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
packaging==16.8
pep8-naming==0.4.1
pycodestyle==2.3.1
pydocstyle==1.1.1 # rq.filter: < 2.0.0
pyflakes==1.6.0
pyparsing==2.2.0
six==1.11.0
snowballstemmer==1.2.1
typing==3.6.4

View File

@@ -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

View File

@@ -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
setuptools==36.5.0
six==1.11.0
wheel==0.31.1
wheel==0.30.0

View File

@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
altgraph==0.15
altgraph==0.14
future==0.16.0
macholib==1.9
pefile==2017.11.5
PyInstaller==3.3.1
macholib==1.8
pefile==2017.9.3
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller

View File

@@ -1 +1,4 @@
PyInstaller
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
# remove @commit-id for scm installs
#@ replace: @.*# @develop#

View File

@@ -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.7.27.1
chardet==3.0.4
github3.py==1.1.0
idna==2.7
isort==4.3.4
github3.py==0.9.6
idna==2.6
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
requests==2.18.4
six==1.11.0
uritemplate==3.0.0
uritemplate.py==3.0.2
urllib3==1.22
wrapt==1.10.11

View File

@@ -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.7.27.1
chardet==3.0.4
github3.py==1.1.0
idna==2.7
isort==4.3.4
github3.py==0.9.6
idna==2.6
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.4
./scripts/dev/pylint_checkers
requests==2.19.1
requests==2.18.4
six==1.11.0
uritemplate==3.0.0
uritemplate.py==3.0.2
urllib3==1.22
wrapt==1.10.11

View File

@@ -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

View File

@@ -1,4 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
docutils==0.14
pyroma==2.3.1
pyroma==2.2

View File

@@ -12,6 +12,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
@@ -35,4 +41,8 @@ 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

View File

@@ -1,40 +1,39 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==18.1.0
attrs==17.2.0
beautifulsoup4==4.6.0
cheroot==6.3.2.post0
cheroot==5.8.3
click==6.7
# colorama==0.3.9
coverage==4.5.1
coverage==4.4.1
EasyProcess==0.2.3
fields==5.0.0
Flask==1.0.2
Flask==0.12.2
glob2==0.6
hunter==2.0.2
hypothesis==3.66.1
hunter==2.0.1
hypothesis==3.32.0
itsdangerous==0.24
# Jinja2==2.10
# Jinja2==2.9.6
Mako==1.0.7
# MarkupSafe==1.0
more-itertools==4.2.0
parse==1.8.4
parse==1.8.2
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
py==1.4.34
py-cpuinfo==3.3.0
pytest==3.2.3
pytest-bdd==2.18.2
pytest-benchmark==3.1.1
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.3
pytest-qt==2.2.1
pytest-repeat==0.4.1
pytest-rerunfailures==4.1
pytest-travis-fold==1.3.0
pytest-xvfb==1.1.0
pytest-rerunfailures==3.1
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
vulture==0.26
Werkzeug==0.12.2

View File

@@ -7,6 +7,7 @@ hypothesis
pytest
pytest-bdd
pytest-benchmark
pytest-catchlog
pytest-cov
pytest-faulthandler
pytest-instafail

View File

@@ -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.5.2
py==1.4.34
tox==2.9.1
virtualenv==15.1.0

View File

@@ -1 +1,4 @@
tox
# The latest tox release still depends on pluggy < 0.4...
pluggy==0.4.0

View File

@@ -1,3 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
vulture==0.28
vulture==0.26

View File

@@ -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}

View File

@@ -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

View File

@@ -1,5 +1,4 @@
#!/bin/sh
set -euo pipefail
#
# Behavior:
# Userscript for qutebrowser which will take the raw JSON text of the current
@@ -20,23 +19,29 @@ set -euo pipefail
#
# 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}
# format json using jq
FORMATTED_JSON="$(cat "$QUTE_TEXT" | jq '.')"
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"
# if jq command failed or formatted json is empty, assume failure and terminate
if [ $? -ne 0 ] || [ -z "$FORMATTED_JSON" ]; then
echo "Invalid json, aborting..."
exit 1
fi
# calculate the filesize of the json document
FILE_SIZE=$(ls -s --block-size=1048576 "$QUTE_TEXT" | cut -d' ' -f1)
# use pygments to pretty-up the json (syntax highlight) if file is less than 10MB
if [ "$FILE_SIZE" -lt "10" ]; then
FORMATTED_JSON="$(echo "$FORMATTED_JSON" | pygmentize -l json -f html -O full,style=$STYLE)"
fi
# create a temp file and write the formatted json to that file
TEMP_FILE="$(mktemp --suffix '.html')"
echo "$FORMATTED_JSON" > $TEMP_FILE
# send the command to qutebrowser to open the new file containing the formatted json
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"

View File

@@ -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")

View File

@@ -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

View File

@@ -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
}
@@ -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

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -1,6 +1,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
#!/usr/bin/env bash
# Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -17,12 +18,16 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Wrappers around Qt/PyQt code."""
#
# 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.
#
# pylint: disable=unused-import
# PyQt 5.11 comes with a bundled sip,
# for older PyQt versions it's a separate module.
try:
from PyQt5 import sip
except ImportError:
import sip
path=$(mktemp --tmpdir qutebrowser_XXXXXXXX.html)
curl "$QUTE_URL" > "$path"
urxvt -e vim "$path"
rm "$path"

View File

@@ -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}

View File

@@ -13,11 +13,7 @@
from __future__ import absolute_import
import codecs, os
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))

View File

@@ -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"

View File

@@ -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

View File

@@ -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.')

View File

@@ -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 --ytdl-raw-options=yes-playlist=}
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
js() {
cat <<EOF

View File

@@ -1,5 +1,4 @@
[pytest]
log_level = NOTSET
addopts = --strict -rfEw --faulthandler-timeout=90 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median
testpaths = tests
markers =
@@ -26,11 +25,7 @@ markers =
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: .*
@@ -55,16 +50,9 @@ qt_log_ignore =
^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 .*
^QSslSocket: cannot resolve *
^Incompatible version of OpenSSL
^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
^inotify_add_watch(".*") failed: "No space left on device"
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

View File

@@ -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.
#

View File

@@ -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__ = (1, 0, 2)
__version__ = '.'.join(str(e) for e in __version_info__)
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."

View File

@@ -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.
#

View File

@@ -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.
#
@@ -51,7 +51,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:
@@ -64,7 +64,7 @@ from qutebrowser.completion.models import miscmodels
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import config, websettings, configfiles, configinit
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
@@ -95,7 +95,6 @@ def run(args):
log.init.debug("Initializing directories...")
standarddir.init(args)
utils.preload_resources()
log.init.debug("Initializing config...")
configinit.early_init(args)
@@ -340,7 +339,7 @@ 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:
if tabbed_browser.count() == 0:
log.init.debug("Opening start pages")
for url in config.val.url.start_pages:
tabbed_browser.tabopen(url)
@@ -418,6 +417,7 @@ def _init_modules(args, crash_handler):
args: The argparse namespace.
crash_handler: The CrashHandler instance.
"""
# pylint: disable=too-many-statements
log.init.debug("Initializing save manager...")
save_manager = savemanager.SaveManager(qApp)
objreg.register('save-manager', save_manager)
@@ -492,13 +492,6 @@ 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("Misc initialization...")
macros.init()
# Init backend-specific stuff
@@ -569,8 +562,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.
@@ -681,6 +674,7 @@ class Quitter:
@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.
@@ -772,8 +766,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()
@@ -829,7 +821,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):
@@ -842,11 +833,7 @@ class Application(QApplication):
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()))
open_url(e.url(), no_raise=True)
else:
return super().event(e)
@@ -882,9 +869,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 +892,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.

View File

@@ -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.
#

View File

@@ -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.
#
@@ -94,8 +94,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()
@@ -170,7 +176,8 @@ class HostBlocker:
self._config_blocked_hosts)
self._blocked_hosts = set()
self._done_count = 0
download_manager = objreg.get('qtnetwork-download-manager')
download_manager = objreg.get('qtnetwork-download-manager',
scope='window', window='last-focused')
for url in config.val.content.host_blocking.lists:
if url.scheme() == 'file':
filename = url.toLocalFile()
@@ -228,14 +235,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

View File

@@ -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,7 +19,6 @@
"""Base class for a wrapper over QWebView/QWebEngineView."""
import enum
import itertools
import attr
@@ -27,17 +26,11 @@ 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)
@@ -81,7 +74,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
@@ -100,30 +93,18 @@ 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)
class AbstractAction:
@@ -138,9 +119,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 +137,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:
@@ -288,10 +243,10 @@ 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)
@@ -365,18 +320,12 @@ class AbstractZoom(QObject):
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 +334,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 +388,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 +430,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
@@ -543,7 +485,6 @@ class AbstractHistory:
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))
@@ -552,7 +493,6 @@ class AbstractHistory:
raise WebTabError("At beginning of history.")
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))
@@ -634,33 +574,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 +605,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 +622,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 +635,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 +662,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 +674,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 +702,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,23 +723,6 @@ 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:
@@ -859,10 +745,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,9 +757,7 @@ 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())
@@ -893,6 +773,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 +786,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 +807,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.
@@ -980,6 +863,3 @@ class AbstractTab(QWidget):
except (AttributeError, RuntimeError) as exc:
url = '<{}>'.format(exc.__class__.__name__)
return utils.get_repr(self, tab_id=self.tab_id, url=url)
def is_deleted(self):
return sip.isdeleted(self._widget)

File diff suppressed because it is too large Load Diff

View File

@@ -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
@@ -103,8 +103,6 @@ def immediate_download_path(prompt_download_directory=None):
if not prompt_download_directory:
return download_dir()
return None
def _path_suggestion(filename):
"""Get the suggested file path.
@@ -139,8 +137,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 +163,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)
@@ -183,7 +179,7 @@ def transform_path(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
@@ -238,14 +234,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)
@@ -611,11 +604,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 +630,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 +657,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 +700,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)
@@ -997,7 +955,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')

View File

@@ -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.utils import qtutils, utils, objreg
from qutebrowser.qt import sip
def update_geometry(obj):

View File

@@ -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

View File

@@ -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,10 +22,8 @@
import collections
import functools
import math
import os
import re
import html
import enum
from string import ascii_lowercase
import attr
@@ -39,9 +37,10 @@ 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):
@@ -155,7 +154,6 @@ 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.
@@ -168,14 +166,12 @@ class HintContext:
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 get_args(self, urlstr):
"""Get the arguments, with {hint-url} replaced by the given URL."""
@@ -244,18 +240,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 +291,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)
@@ -404,6 +390,7 @@ class HintManager(QObject):
def _cleanup(self):
"""Clean up after hinting."""
# pylint: disable=not-an-iterable
for label in self._context.all_labels:
label.cleanup()
@@ -458,17 +445,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 +605,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
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 `tabs.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.
@@ -702,7 +672,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,8 +684,7 @@ 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:
pass
@@ -734,7 +703,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:
@@ -829,6 +797,7 @@ class HintManager(QObject):
log.hints.debug("Filtering hints on {!r}".format(filterstr))
visible = []
# pylint: disable=not-an-iterable
for label in self._context.all_labels:
try:
if self._filter_matches(filterstr, str(label.elem)):
@@ -929,32 +898,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):

View File

@@ -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.
#
@@ -21,9 +21,8 @@
import os
import time
import contextlib
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer, pyqtSignal
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
@@ -32,7 +31,7 @@ from qutebrowser.misc import objects, sql
# increment to indicate that HistoryCompletion must be regenerated
_USER_VERSION = 2
_USER_VERSION = 1
class CompletionHistory(sql.SqlTable):
@@ -52,11 +51,6 @@ class WebHistory(sql.SqlTable):
"""The global history of visited pages."""
# All web history cleared
history_cleared = pyqtSignal()
# one url cleared
url_cleared = pyqtSignal(QUrl)
def __init__(self, parent=None):
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
constraints={'url': 'NOT NULL',
@@ -93,22 +87,11 @@ class WebHistory(sql.SqlTable):
def __contains__(self, url):
return self._contains_query.run(val=url).value()
@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 _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')
'WHERE NOT redirect 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)
@@ -155,14 +138,12 @@ 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()
self.delete_all()
self.completion.delete_all()
def delete_url(self, url):
"""Remove all history entries with the given url.
@@ -174,14 +155,11 @@ class WebHistory(sql.SqlTable):
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)
@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
@@ -213,7 +191,7 @@ class WebHistory(sql.SqlTable):
atime = int(atime) if (atime is not None) else int(time.time())
with self._handle_sql_errors():
try:
self.insert({'url': self._format_url(url),
'title': title,
'atime': atime,
@@ -224,6 +202,11 @@ class WebHistory(sql.SqlTable):
'title': title,
'last_atime': atime
}, replace=True)
except sql.SqlError as e:
if e.environmental:
message.error("Failed to write history: {}".format(e.text()))
else:
raise
def _parse_entry(self, line):
"""Parse a history line like '12345 http://example.com title'."""
@@ -278,7 +261,6 @@ class WebHistory(sql.SqlTable):
return
def action():
"""Actually run the import."""
with debug.log_time(log.init, 'Import old history file to sqlite'):
try:
self._read(path)
@@ -351,7 +333,7 @@ class WebHistory(sql.SqlTable):
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))
raise cmdexc.CommandError('Could not write history: {}', e)
def init(parent=None):

View File

@@ -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.
#
@@ -87,8 +87,6 @@ class AbstractWebInspector(QWidget):
data = bytes(self.saveGeometry())
geom = base64.b64encode(data).decode('ASCII')
configfiles.state['geometry']['inspector'] = geom
self.inspect(None)
super().closeEvent(e)
def inspect(self, page):
@@ -96,9 +94,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()

View File

@@ -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
@@ -175,9 +153,8 @@ 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:

View File

@@ -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.
#
@@ -64,7 +64,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

View File

@@ -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:

View File

@@ -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."""
@@ -65,9 +61,6 @@ class ProxyFactory(QNetworkProxyFactory):
"""
proxy = config.val.content.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)

View File

@@ -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'),

View File

@@ -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,7 +20,6 @@
"""Download manager."""
import io
import os.path
import shutil
import functools
@@ -29,7 +28,7 @@ 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
@@ -162,7 +161,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 +198,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 +292,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,10 +368,10 @@ 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,
win_id=win_id, tab_id=None,
private=config.val.content.private_browsing, parent=self)
@pyqtSlot('QUrl')
@@ -409,7 +388,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)

View File

@@ -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,23 @@ Module attributes:
_HANDLERS: The handlers registered via decorators.
"""
import html
import json
import os
import time
import urllib.parse
import textwrap
import mimetypes
import urllib
import collections
import pkg_resources
from PyQt5.QtCore import QUrlQuery, QUrl
import qutebrowser
from qutebrowser.config import config, configdata, configexc, configdiff
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
objreg, urlutils)
objreg)
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 +91,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 +111,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 +118,12 @@ 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()))
return 'text/html', html
def data_for_url(url):
@@ -141,30 +135,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 +158,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,32 +177,11 @@ 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
@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
html = jinja.render('bookmarks.html',
title='Bookmarks',
bookmarks=bookmarks,
quickmarks=quickmarks)
return 'text/html', html
def history_data(start_time, offset=None):
@@ -242,9 +201,8 @@ def history_data(start_time, offset=None):
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]
return [{"url": e.url, "title": e.title or e.url, "time": e.atime}
for e in entries]
@add_handler('history')
@@ -265,6 +223,8 @@ def qute_history(url):
return 'text/html', json.dumps(history_data(start_time, offset))
else:
if not config.val.content.javascript.enabled:
return 'text/plain', b'JavaScript is required for qute://history'
return 'text/html', jinja.render(
'history.html',
title='History',
@@ -289,25 +249,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 +274,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,19 +294,20 @@ 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/LICENSE.html')
@add_handler('help')
@@ -368,14 +323,8 @@ def qute_help(url):
"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
if urlpath.endswith('.png'):
return 'image/png', utils.read_file(path, binary=True)
try:
data = utils.read_file(path)
@@ -417,12 +366,12 @@ def qute_help(url):
@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
html = jinja.render('backend-warning.html',
distribution=version.distribution(),
Distribution=version.Distribution,
version=pkg_resources.parse_version,
title="Legacy backend warning")
return 'text/html', html
def _qute_settings_set(url):
@@ -452,38 +401,10 @@ def qute_settings(url):
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
html = jinja.render('settings.html', title='settings',
configdata=configdata,
confget=config.instance.get_str)
return 'text/html', html
@add_handler('configdiff')
@@ -494,15 +415,8 @@ def qute_configdiff(url):
return 'text/html', configdiff.get_diff()
except OSError as e:
error = (b'Failed to read old config: ' +
str(e.strerror).encode('utf-8'))
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.'

View File

@@ -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,33 @@
"""Various utilities shared between webpage/webview subclasses."""
import os
import html
import netrc
from PyQt5.QtCore import QUrl
from qutebrowser.config import config
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
from qutebrowser.utils import usertypes, message, log, objreg, jinja
from qutebrowser.mainwindow import mainwindow
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_config = config.instance.get('content.headers.do_not_track', url=url)
dnt_config = config.val.content.headers.do_not_track
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
conf_headers = config.instance.get('content.headers.custom', url=url)
conf_headers = config.val.content.headers.custom
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.val.content.headers.accept_language
if accept_language is not None:
headers[b'Accept-Language'] = accept_language.encode('ascii')
@@ -65,33 +61,30 @@ 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:
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:
@@ -99,14 +92,12 @@ def javascript_prompt(url, js_msg, default, abort_on, *, escape_msg=True):
if not config.val.content.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,7 +105,7 @@ 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:
@@ -123,12 +114,10 @@ def javascript_alert(url, js_msg, abort_on, *, escape_msg=True):
if not config.val.content.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)
abort_on=abort_on)
def javascript_log_message(level, source, line, msg):
@@ -157,7 +146,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.val.content.ssl_strict
log.webview.debug("Certificate errors {!r}, strict {}".format(
errors, ssl_strict))
@@ -175,10 +164,9 @@ 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
@@ -194,11 +182,10 @@ 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:
@@ -208,36 +195,22 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on,
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.instance.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
@@ -287,41 +260,3 @@ def get_user_stylesheet():
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

View File

@@ -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))

View File

@@ -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
@@ -161,7 +161,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):
@@ -192,7 +192,7 @@ class QuickmarkManager(UrlMarkManager):
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()
@@ -280,7 +280,7 @@ 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!")

View File

@@ -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.
#
@@ -24,7 +24,6 @@ Module attributes:
SELECTORS: CSS selectors for different groups of elements.
"""
import enum
import collections.abc
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer
@@ -36,15 +35,13 @@ from qutebrowser.mainwindow import mainwindow
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
Group = enum.Enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
SELECTORS = {
Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, '
'frame, iframe, link, summary, [onclick], [onmousedown], '
'[role=link], [role=option], [role=button], img, '
# Angular 1 selectors
'[ng-click], [ngClick], [data-ng-click], [x-ng-click]'),
'frame, iframe, link, [onclick], [onmousedown], [role=link], '
'[role=option], [role=button], img'),
Group.links: 'a[href], area[href], link[href], [role=link][href]',
Group.images: 'img',
Group.url: '[src], [href]',
@@ -62,13 +59,6 @@ class Error(Exception):
pass
class OrphanedError(Error):
"""Raised when a webelement's parent has vanished."""
pass
class AbstractWebElement(collections.abc.MutableMapping):
"""A wrapper around QtWebKit/QtWebEngine web element.
@@ -230,7 +220,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
}
relevant_classes = classes[self.tag_name()]
for klass in self.classes():
if any(klass.strip().startswith(e) for e in relevant_classes):
if any([klass.strip().startswith(e) for e in relevant_classes]):
return True
return False
@@ -307,10 +297,6 @@ class AbstractWebElement(collections.abc.MutableMapping):
href_tags = ['a', 'area', 'link']
return self.tag_name() in href_tags and 'href' in self
def _requires_user_interaction(self):
"""Return True if clicking this element needs user interaction."""
raise NotImplementedError
def _mouse_pos(self):
"""Get the position to click/hover."""
# Click the center of the largest square fitting into the top/left
@@ -409,15 +395,14 @@ class AbstractWebElement(collections.abc.MutableMapping):
return
if click_target == usertypes.ClickTarget.normal:
if self.is_link() and not self._requires_user_interaction():
if self.is_link():
log.webelem.debug("Clicking via JS click()")
self._click_js(click_target)
elif self.is_editable(strict=True):
log.webelem.debug("Clicking via JS focus()")
self._click_editable(click_target)
if config.val.input.insert_mode.auto_enter:
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
'clicking input')
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
'clicking input')
else:
self._click_fake_event(click_target)
elif click_target in [usertypes.ClickTarget.tab,

View File

@@ -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.
#

View File

@@ -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.
#
@@ -28,10 +28,6 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
"""A wrapper over a QWebEngineCertificateError."""
def __init__(self, error):
super().__init__(error)
self.ignore = False
def __str__(self):
return self._error.errorDescription()
@@ -41,8 +37,5 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
self._error.error()),
string=str(self))
def url(self):
return self._error.url()
def is_overridable(self):
return self._error.isOverridable()

View File

@@ -1,48 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 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/>.
"""Filter for QtWebEngine cookies."""
from qutebrowser.config import config
from qutebrowser.utils import utils
def _accept_cookie(request):
"""Check whether the given cookie should be accepted."""
accept = config.val.content.cookies.accept
if accept == 'all':
return True
elif accept in ['no-3rdparty', 'no-unknown-3rdparty']:
return not request.thirdParty
elif accept == 'never':
return False
else:
raise utils.Unreachable
def install_filter(profile):
"""Install the cookie filter on the given profile.
On Qt < 5.11, the filter isn't installed.
"""
store = profile.cookieStore()
try:
store.setCookieFilter(_accept_cookie)
except AttributeError:
pass

View File

@@ -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,22 +19,20 @@
"""A request interceptor taking care of adblocking and custom headers."""
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
QWebEngineUrlRequestInfo)
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
from qutebrowser.config import config
from qutebrowser.browser import shared
from qutebrowser.utils import utils, log, debug
from qutebrowser.utils import utils, log
class RequestInterceptor(QWebEngineUrlRequestInterceptor):
"""Handle ad blocking and custom headers."""
def __init__(self, host_blocker, args, parent=None):
def __init__(self, host_blocker, parent=None):
super().__init__(parent)
self._host_blocker = host_blocker
self._args = args
def install(self, profile):
"""Install the interceptor on the given QWebEngineProfile."""
@@ -56,29 +54,15 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
Args:
info: QWebEngineUrlRequestInfo &info
"""
if 'log-requests' in self._args.debug_flags:
resource_type = debug.qenum_key(QWebEngineUrlRequestInfo,
info.resourceType())
navigation_type = debug.qenum_key(QWebEngineUrlRequestInfo,
info.navigationType())
log.webview.debug("{} {}, first-party {}, resource {}, "
"navigation {}".format(
bytes(info.requestMethod()).decode('ascii'),
info.requestUrl().toDisplayString(),
info.firstPartyUrl().toDisplayString(),
resource_type, navigation_type))
url = info.requestUrl()
# FIXME:qtwebengine only block ads for NavigationTypeOther?
if self._host_blocker.is_blocked(url):
if self._host_blocker.is_blocked(info.requestUrl()):
log.webview.info("Request to {} blocked by host blocker.".format(
url.host()))
info.requestUrl().host()))
info.block(True)
for header, value in shared.custom_headers(url=url):
for header, value in shared.custom_headers():
info.setHttpHeader(header, value)
user_agent = config.instance.get('content.headers.user_agent', url=url)
user_agent = config.val.content.headers.user_agent
if user_agent is not None:
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017-2018 Michal Siedlaczek <michal.siedlaczek@gmail.com>
# Copyright 2017 Michal Siedlaczek <michal.siedlaczek@gmail.com>
# This file is part of qutebrowser.
#
@@ -21,73 +21,26 @@
import glob
import os
import os.path
import re
import shutil
from PyQt5.QtCore import QLibraryInfo
from qutebrowser.utils import log, message, standarddir, qtutils
dict_version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
def version(filename):
"""Extract the version number from the dictionary file name."""
match = dict_version_re.match(filename)
if match is None:
message.warning(
"Found a dictionary with a malformed name: {}".format(filename))
return None
return tuple(int(n) for n in match.group('version').split('-'))
def dictionary_dir(old=False):
def dictionary_dir():
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
if qtutils.version_check('5.10', compiled=False) and not old:
datapath = standarddir.data()
else:
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
return os.path.join(datapath, 'qtwebengine_dictionaries')
def local_files(code):
"""Return all installed dictionaries for the given code.
def installed_file(code):
"""Return the installed dictionary for the given code.
The returned dictionaries are sorted by version, therefore the latest will
be the first element. The list will be empty if no dictionaries are found.
Return the filename of the installed dictionary or None
if the dictionary is not installed.
"""
pathname = os.path.join(dictionary_dir(), '{}*.bdic'.format(code))
matching_dicts = glob.glob(pathname)
versioned_dicts = []
for matching_dict in matching_dicts:
parsed_version = version(matching_dict)
if parsed_version is not None:
filename = os.path.basename(matching_dict)
log.config.debug('Found file for dict {}: {}'
.format(code, filename))
versioned_dicts.append((parsed_version, filename))
return [filename for version, filename
in sorted(versioned_dicts, reverse=True)]
def local_filename(code):
"""Return the newest installed dictionary for the given code.
Return the filename of the installed dictionary with the highest version
number or None if the dictionary is not installed.
"""
all_installed = local_files(code)
return os.path.splitext(all_installed[0])[0] if all_installed else None
def init():
"""Initialize the dictionary path if supported."""
if qtutils.version_check('5.10', compiled=False):
new_dir = dictionary_dir()
old_dir = dictionary_dir(old=True)
os.environ['QTWEBENGINE_DICTIONARIES_PATH'] = new_dir
try:
if os.path.exists(old_dir) and not os.path.exists(new_dir):
shutil.copytree(old_dir, new_dir)
except OSError:
log.misc.exception("Failed to copy old dictionaries")
if matching_dicts:
with_extension = os.path.basename(matching_dicts[0])
return os.path.splitext(with_extension)[0]
else:
return None

View File

@@ -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.
#

View File

@@ -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.
#
@@ -45,10 +45,6 @@ class DownloadItem(downloads.AbstractDownloadItem):
qt_item.downloadProgress.connect(self.stats.on_download_progress)
qt_item.stateChanged.connect(self._on_state_changed)
# Ensure wrapped qt_item is deleted manually when the wrapper object
# is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373
self.destroyed.connect(self._qt_item.deleteLater)
def _is_page_download(self):
"""Check if this item is a page (i.e. mhtml) download."""
return (self._qt_item.savePageFormat() !=
@@ -100,19 +96,9 @@ class DownloadItem(downloads.AbstractDownloadItem):
self._qt_item.cancel()
def retry(self):
state = self._qt_item.state()
if state != QWebEngineDownloadItem.DownloadInterrupted:
log.downloads.warning(
"Trying to retry download in state {}".format(
debug.qenum_key(QWebEngineDownloadItem, state)))
return
try:
self._qt_item.resume()
except AttributeError:
raise downloads.UnsupportedOperationError(
"Retrying downloads is unsupported with QtWebEngine on "
"Qt/PyQt < 5.10")
# https://bugreports.qt.io/browse/QTBUG-56840
raise downloads.UnsupportedOperationError(
"Retrying downloads is unsupported with QtWebEngine")
def _get_open_filename(self):
return self._filename
@@ -139,7 +125,6 @@ class DownloadItem(downloads.AbstractDownloadItem):
question = usertypes.Question()
question.title = title
question.text = msg
question.url = 'file://{}'.format(self._filename)
question.mode = usertypes.PromptMode.yesno
question.answered_yes.connect(self._after_set_filename)
question.answered_no.connect(no_action)
@@ -148,23 +133,6 @@ class DownloadItem(downloads.AbstractDownloadItem):
self.error.connect(question.abort)
message.global_bridge.ask(question, blocking=True)
def _ask_create_parent_question(self, title, msg,
force_overwrite, remember_directory):
no_action = functools.partial(self.cancel, remove_data=False)
question = usertypes.Question()
question.title = title
question.text = msg
question.url = 'file://{}'.format(os.path.dirname(self._filename))
question.mode = usertypes.PromptMode.yesno
question.answered_yes.connect(lambda:
self._after_create_parent_question(
force_overwrite, remember_directory))
question.answered_no.connect(no_action)
question.cancelled.connect(no_action)
self.cancelled.connect(question.abort)
self.error.connect(question.abort)
message.global_bridge.ask(question, blocking=True)
def _after_set_filename(self):
self._qt_item.setPath(self._filename)
self._qt_item.accept()

View File

@@ -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.
#
@@ -27,7 +27,7 @@ from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
from qutebrowser.utils import log, javascript, urlutils
from qutebrowser.utils import log, javascript
from qutebrowser.browser import webelem
@@ -47,7 +47,6 @@ class WebEngineElement(webelem.AbstractWebElement):
'class_name': str,
'rects': list,
'attributes': dict,
'caret_position': (int, type(None)),
}
assert set(js_dict.keys()).issubset(js_dict_types.keys())
for name, typ in js_dict_types.items():
@@ -100,8 +99,6 @@ class WebEngineElement(webelem.AbstractWebElement):
def _js_call(self, name, *args, callback=None):
"""Wrapper to run stuff from webelem.js."""
if self._tab.is_deleted():
raise webelem.OrphanedError("Tab containing element vanished")
js_code = javascript.assemble('webelem', name, self._id, *args)
self._tab.run_js_async(js_code, callback=callback)
@@ -135,13 +132,6 @@ class WebEngineElement(webelem.AbstractWebElement):
def set_value(self, value):
self._js_call('set_value', value)
def caret_position(self):
"""Get the text caret position for the current element.
If the element is not a text element, None is returned.
"""
return self._js_dict.get('caret_position', None)
def insert_text(self, text):
if not self.is_editable(strict=True):
raise webelem.Error("Element is not editable!")
@@ -198,13 +188,6 @@ class WebEngineElement(webelem.AbstractWebElement):
if self.is_text_input() and self.is_editable():
self._js_call('move_cursor_to_end')
def _requires_user_interaction(self):
baseurl = self._tab.url()
url = self.resolve_url(baseurl)
if url is None:
return True
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
def _click_editable(self, click_target):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
@@ -218,11 +201,11 @@ class WebEngineElement(webelem.AbstractWebElement):
def _click_js(self, _click_target):
# FIXME:qtwebengine Have a proper API for this
# pylint: disable=protected-access
view = self._tab._widget
settings = self._tab._widget.settings()
# pylint: enable=protected-access
attribute = QWebEngineSettings.JavascriptCanOpenWindows
could_open_windows = view.settings().testAttribute(attribute)
view.settings().setAttribute(attribute, True)
could_open_windows = settings.testAttribute(attribute)
settings.setAttribute(attribute, True)
# Get QtWebEngine do apply the settings
# (it does so with a 0ms QTimer...)
@@ -233,12 +216,6 @@ class WebEngineElement(webelem.AbstractWebElement):
QEventLoop.ExcludeUserInputEvents)
def reset_setting(_arg):
"""Set the JavascriptCanOpenWindows setting to its old value."""
try:
view.settings().setAttribute(attribute, could_open_windows)
except RuntimeError:
# Happens if this callback gets called during QWebEnginePage
# destruction, i.e. if the tab was closed in the meantime.
pass
settings.setAttribute(attribute, could_open_windows)
self._js_call('click', callback=reset_setting)

Some files were not shown because too many files have changed in this diff Show More